Зробити стрічку підписок (following feed) у мобільному застосунку, що працює за 100K+ підписників, — нетривіальне завдання. Простий SQL-запит SELECT * FROM posts WHERE author_id IN (SELECT followee_id FROM follows WHERE follower_id = :user) ORDER BY created_at DESC ламається при масштабуванні: на 1 млн підписників і 10 млн постів час відповіді перевищує 3 секунди. Додайте realtime, необхідність миттєвого старту та популярного автора з мільйоном підписників — і без продуманої архітектури не обійтися. Гібридна схема fan-out для звичайних та fan-in для зірок знижує витрати на інфраструктуру на 30-50%. Наша команда з 10+ років досвіду та 40+ проєктами у сфері соціальних застосунків гарантує рішення, яке витримає навантаження. Проведемо навантажувальне тестування k6 на 1000 rps — і тільки після цього здаємо проєкт.
Як вибрати архітектуру для розробки стрічки підписок: fan-in чи fan-out?
Два класичних підходи: fan-out on write (push) та fan-in on read (pull). При fan-out публікація поста одразу записується в стрічки всіх підписників — читання стає швидким, але запис дорогим для популярних авторів. Fan-in, навпаки, при запиті збирає пости з підписок — немає дублювання даних, але читання може бути повільнішим. На практиці застосовують гібрид: fan-out для звичайних користувачів (до 100K підписників у автора) та fan-in для «зірок» з мільйонами фоловерів. Поріг налаштовується. Для MVP достатньо fan-in:
SELECT p.*, u.name, u.avatar_url FROM posts p JOIN follows f ON p.author_id = f.followee_id JOIN users u ON p.author_id = u.id WHERE f.follower_id = :user_id AND p.created_at < :cursor ORDER BY p.created_at DESC LIMIT 20; Індекси: follows(follower_id), posts(author_id, created_at DESC). З таким запитом при 1 млн підписок час відповіді — 100-150 мс.
| Характеристика | Fan-out on write | Fan-in on read |
|---|---|---|
| Швидкість читання | O(1) — моментально | O(N) — може бути повільним при 1M+ підписок |
| Швидкість запису для зірки | Дуже дорого (мільйони копій) | O(1) — тільки запис поста |
| Зберігання | Багато дублювання (50+ копій на пост) | Мінімум дублювання |
| Навантаження на сервер | Висока на запис, низька на читання | Низька на запис, висока на читання |
| Масштабування | Складно для популярних авторів | Добре для будь-якої кількості підписників |
Cursor-based пагінація: переваги перед offset
Cursor-пагінація обробляє запит за 50 мс при таблиці в 10 млн рядків, тоді як OFFSET на 100-й сторінці — до 2 секунд. Використовуємо cursor = created_at останнього поста (ISO 8601): GET /feed?cursor=LAST_SEEN_POST_TIMESTAMP&limit=20 Відповідь: { items: [...], next_cursor: "...", has_more: true }.
Реалізація проста: індекс на (created_at) забезпечує O(log n). На відміну від OFFSET, cursor не чутливий до вставок — дублі не з'являються.
Як забезпечити миттєве завантаження стрічки?
Кешування на клієнті — відповідь. На iOS з Swift зберігаємо перші 50-100 постів стрічки в CoreData або Realm. При відкритті застосунку — миттєво показуємо кеш, одночасно запитуємо нові пости. Коли нові пости прийшли — тихо вставляємо їх на початок або показуємо банер. NSFetchedResultsController + NSDiffableDataSourceSnapshot для плавного оновлення без мерехтіння. На Android з Kotlin використовуємо Room + Paging 3 з RemoteMediator. Локальна база — джерело істини, RemoteMediator підвантажує дані з мережі в Room, Paging 3 рендерить з Room. На Flutter — Hive або Isar для локального кешу, flutter_bloc для управління станом сторінок.
| Платформа | Кешуюча технологія | Бібліотека для зображень | Переваги |
|---|---|---|---|
| iOS (Swift) | CoreData / Realm | Kingfisher | NSFetchedResultsController, DiffableDataSource |
| Android (Kotlin) | Room + Paging 3 | Coil | RemoteMediator, інтеграція з Compose |
| Flutter (Dart) | Hive / Isar | cached_network_image | Простота, fast startup |
Кеш дозволяє скоротити завантаження даних при старті на 80% та знизити витрати трафіку.
Realtime-оновлення: стратегії та реалізація
- Pull to refresh — користувач тягне вниз, запитуємо пости новіші за
firstPost.created_at. Найпростіший, працює скрізь. - WebSocket/SSE — сервер пушить нові пости клієнту. Показуємо банер «N нових постів» вгорі стрічки (як Twitter). Клієнт не вставляє їх автоматично — тільки по тапу на банер, інакше стрічка стрибає під пальцем.
- Long polling — компроміс без WebSocket.
На iOS WebSocket — URLSessionWebSocketTask. На Android — OkHttp WebSocket. На Flutter — web_socket_channel.
Налаштування realtime-оновлень через WebSocket
- Створіть WebSocket-з'єднання при вході в стрічку.
- Сервер надсилає подію
new_postз ID поста. - Клієнт відображає банер «X нових постів».
- По тапу на банер завантажуємо недостаючі пости через API.
Алгоритмічна стрічка
Хронологічна стрічка — базис. Якщо потрібна алгоритмічна (ранжування по engagement): зберігати score за кожен пост, перераховувати через воркер (BullMQ/Celery) при додаванні лайків/коментарів. Клієнт запитує стрічку з параметром sort=ranked. Для першого запуску — хронологічна, після набору даних — перемикання на алгоритмічну. Обидві стрічки як окремі вкладки (Reels vs Following у Instagram).
Скрол і продуктивність
UICollectionView з UICollectionViewCompositionalLayout та DiffableDataSource — золотий стандарт на iOS. Prefetch даних через UICollectionViewDataSourcePrefetching. Зображення — Kingfisher з кешуванням у пам'яті та на диску. На Android LazyColumn (Compose) або RecyclerView з ConcatAdapter. Зображення — Coil з rememberAsyncImagePainter. Головна причина смиканої прокрутки — декодування зображень на main thread. Kingfisher і Coil роблять це в background за замовчуванням. При кастомному завантаженні — DispatchQueue.global(qos: .userInitiated).async (iOS) або Dispatchers.IO (Android).
Що входить в роботу
- Архітектурна схема стрічки (вибір fan-in/fan-out/гібрид) під ваше навантаження.
- API з cursor-пагінацією та realtime-подіями.
- UI стрічки з кешем та плавною прокруткою.
- Навантажувальне тестування (k6) на сценарій «1000 запитів стрічки одночасно».
- Документація, код та підтримка при деплої.
Етапи розробки стрічки підписок
- Аналіз навантаження: очікувана кількість підписників, TPS, розмір поста.
- Вибір архітектури: fan-in/fan-out/гібрид, поріг для зірок.
- Проектування API: REST + WebSocket, cursor-пагінація.
- Реалізація UI: колекція/список з кешем та prefetch.
- Realtime-інтеграція: WebSocket/SSE, банер нових постів.
- Навантажувальне тестування: k6, 1000 rps, моніторинг.
- Деплой та моніторинг: CI/CD, логи, алерти.
Терміни
Базова стрічка з pull-to-refresh та пагінацією — 2-3 дні. З realtime WebSocket, кешем, алгоритмічним ранжуванням — 7-10 днів. Вартість розраховується індивідуально. Замовте розробку під ключ та отримайте стабільне рішення для вашого соціального застосунку. Зв'яжіться з нами для оцінки проєкту — гарантуємо прозорий підхід та сертифікованих розробників. Отримайте консультацію інженера.







