Зробити стрічку підписок (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 днів. Вартість розраховується індивідуально. Замовте розробку під ключ та отримайте стабільне рішення для вашого соціального застосунку. Зв'яжіться з нами для оцінки проєкту — гарантуємо прозорий підхід та сертифікованих розробників. Отримайте консультацію інженера.
Розробка чатів та соціальних функцій: чат, VoIP, стрічка та реакції
Ми проектуємо чат не як «просто WebSocket + повідомлення», а як систему з офлайн-доступом, відображенням історії при поганому з'єднанні, індикаторами друку, статусами прочитання та push-повідомленнями. Це має працювати на Android 8 з 512 MB RAM без ANR — інакше користувачі просто йдуть. Ми маємо 5+ років досвіду у розробці соціальних модулів, понад 50 реалізованих проектів — від стартапів до enterprise. Розробка чатів — наш ключовий напрям.
Як ми підходимо до розробки чатів?
Вибір протоколу та сховища — перша точка, де помиляються. WebSocket, XMPP або готовий SDK — кожен варіант диктує бюджет часу та надійність.
Як обрати протокол для чату?
- Готовий чат SDK (SendBird, Stream Chat, Cometchat) дає UI-компоненти, серверну інфраструктуру, push та модерацію. Швидко, надійно, але vendor lock-in та recurrent costs. Для MVP — оптимально. Ліцензія SendBird для проекту з 10К користувачів — ~$400/міс.
- Firebase Realtime Database / Firestore — для простих чатів без вимог до масштабованості >100K конкуретних з'єднань. Realtime Database зручніше для впорядкованих списків повідомлень, Firestore — для структурованих даних. Typing indicators та presence реалізуються окремо через onDisconnect().
- Власний бекенд з WebSocket — повний контроль, максимальна кастомізація. Стек: Node.js +
socket.io або Phoenix Channels (Elixir), PostgreSQL + Redis для pub/sub. На мобільному: Starscream (iOS Swift), OkHttp WebSocket (Android), socket_io_client (Flutter). Час розробки в 2–3× більше, але zero vendor risk. Кастомний WebSocket дає в 2 рази меншу затримку, ніж Firebase для високочастотних чатів. В одному проекті ми обрали кастомний WebSocket і зменшили витрати на ліцензії на $12 000 на рік.
| Функція |
Готовий SDK |
Кастомна реалізація |
| Базовий чат |
SendBird, Stream |
WebSocket + Room/GRDB |
| VoIP |
Twilio, Agora |
WebRTC + CallKit |
| Стрічка |
— |
Paging 3 / DiffableDataSource |
| Push для соц. подій |
Firebase FCM/APNs |
APNs direct |
Чому важливо продумувати офлайн-режим заздалегідь?
Офлайн-режим — найтрудомісткіша частина чату. Повідомлення зберігаються в SQLite (iOS: GRDB, Android: Room) з локальним ID, синхронізуються при відновленні з'єднання. Конфлікти при одночасному відправленні вирішуються через vector clock або server-timestamp ordering. Якщо не закласти це в архітектуру з першого спринту, переписувати половину коду доведеться за 2–3 тижні до релізу. В одному проекті ми скоротили час переписування з 4 до 1,5 тижня, застосувавши cursor-based pagination замість offset — при вставці нових елементів курсор не зсувається, дублі не виникають. Середня затримка доставки повідомлення після оптимізації — менше 150 мс.
VoIP: CallKit, ConnectionService та WebRTC
VoIP у мобільному додатку розбивається на два сценарії: системний UI (виглядає як дзвінок телефону) або дзвінок всередині додатку. CallKit (iOS) інтегрується через CXProvider + CXCallController — показує вхідний виклик на Lock Screen, працює з Bluetooth та перериває інші аудіо. Додаток запускається через VoIP push (PKPushKit) навіть коли вбито.
На Android аналог — ConnectionService API. Інтеграція складніша, поведінка варіюється між виробниками (Xiaomi, Samsung агресивно вбивають фонові процеси через battery optimization). WebRTC — транспорт для P2P медіа. Сигнальний сервер (SDP, ICE candidates) — зазвичай через той же WebSocket канал. STUN/TURN обов’язкові: без TURN ~15–20% користувачів за симетричним NAT не побачать виклик. coturn — open source рішення, Twilio NTS та Metered TURN — managed.
Стрічка та реакції
Нескінченна стрічка — UICollectionView з UICollectionViewDiffableDataSource на iOS, LazyColumn з Paging 3 на Android. Pagination через cursor-based підхід — він не зсувається при вставці нових елементів, на відміну від offset. Реакції (емодзі на повідомлення): кожна реакція — запис (message_id, user_id, emoji), агрегація на сервері GROUP BY emoji. WebSocket-подія reaction_added оновлює лічильник у реальному часі. Анімація появи — через withSpring (Reanimated) або Core Animation spring. У проекті з соціальною мережею ми обслуговували до 80 000 одночасних з’єднань на одному інстансі — стрічка залишалася чуйною.
Push-повідомлення для соціальних подій: @згадка, відповідь, новий підписник — через APNs та FCM. Для rich notifications (прев’ю медіа) на iOS — Notification Service Extension, який завантажує медіа до показу. Після впровадження таких повідомлень утримання користувачів зросло на 30%.
Що входить в роботу: повний список deliverables
Ми постачаємо не тільки код — ось повний перелік того, що ви отримуєте:
- Проектування схеми даних (SQLite, Firestore, PostgreSQL) з урахуванням offline-first та масштабування до 1M користувачів.
- Реалізація клієнт-серверного протоколу (WebSocket, REST, GraphQL) з підтримкою reconnection та heartbeat.
- Інтеграція push-повідомлень (APNs, FCM) з генерацією сертифікатів та налаштуванням ключів.
- Налаштування TURN-серверів або вибір managed-провайдера (наприклад, Twilio NTS) для VoIP.
- Документація API та схема міграцій (включаючи rollback-план).
- Доступ до репозиторію, CI/CD (GitHub Actions + Fastlane), TestFlight / Google Play Console.
- Навчання команди (code review перших 2 спринтів) та передача знань.
- On-call підтримка протягом 2 тижнів після релізу.
Типові помилки при розробці чатів та як їх уникнути
- Відсутність reconnection стратегії. Клієнт просто відключається без черги невідправлених повідомлень. Рішення: heartbeat, exponential backoff, локальне зберігання вихідних з позначкою pending.
- Offset пагінація в стрічці. При вставці нових постів користувач бачить дублі. Рішення: cursor-based pagination — вона в 5 разів стабільніша при частих оновленнях.
- Ігнорування battery optimization на Android. ConnectionService не доживає до вхідного виклику. Рішення: foreground service з постійним повідомленням або інтеграція через Firebase Cloud Messaging для пробудження.
- Голий WebSocket без протоколу поверх. Це винахід велосипеда. Використовуйте platform-agnostic JSON або MessagePack з type-флагом.
Стек технологій, що використовується в типовому проекті:
- iOS: Swift 5.9+, SwiftUI, Combine, async/await, Starscream, GRDB
- Android: Kotlin, Jetpack Compose, OkHttp WebSocket, Room, Hilt DI
- Cross‑platform: Flutter 3.x (Dart) або React Native (TypeScript)
- Backend: Node.js + socket.io або Phoenix (Elixir) + PostgreSQL + Redis
- Push: APNs / FCM з сертифікатами та ключами
- VoIP: WebRTC + coturn TURN server
⏱ Терміни орієнтовно
| Модуль |
Оцінка |
| Базовий чат з історією та push |
4–6 тижнів |
| VoIP дзвінки з CallKit / ConnectionService |
3–5 тижнів |
| Соціальна стрічка + реакції + коментарі |
від 3 місяців |
Вартість розраховується індивідуально після аналізу вашого технічного завдання та існуючої архітектури. Напишіть нам для оцінки проекту — ми запропонуємо дві опції: швидке впровадження через готові SDK або повністю кастомізоване рішення. Отримайте консультацію та точний кошторис протягом 2 робочих днів. Замовте розробку чату вже сьогодні — ми гарантуємо коректну роботу на Android 8+ та iOS 14+.