Груповий чат у застосунку: архітектура, UI, синхронізація

Вступ Груповий чат складніший за приватний не в рази, а на порядок. У нашій практиці ми стикалися з проєктами, де після додавання 200 учасників продуктивність падала в 5 разів через неправильну архітектуру fanout. У приватному чаті два учасники — всі події синхронізуються через одне WebSocket-з'є

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Груповий чат у застосунку: архітектура, UI, синхронізація
Складний
від 1 тижня до 3 місяців

Наші компетенції:

Часті запитання

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    895
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    597

Вступ

Груповий чат складніший за приватний не в рази, а на порядок. У нашій практиці ми стикалися з проєктами, де після додавання 200 учасників продуктивність падала в 5 разів через неправильну архітектуру fanout. У приватному чаті два учасники — всі події синхронізуються через одне WebSocket-з'єднання до однієї conversation. У груповому чаті на 200 учасників сервер повинен fanout кожного повідомлення в 199 з'єднань, правильно обчислювати непрочитані для кожного, не тиснути Redis під навантаженням і коректно працювати коли частина учасників офлайн. Це вже проблема архітектури, а не просто UI.

Чому архітектура fanout критична?

Найболючіша частина — доставка повідомлення всім учасникам групи. Синхронний fanout («відправили → прогнали по всіх з'єднаннях → відповіли клієнту») не масштабується: при групі на 500 осіб ітерація по активних з'єднаннях займає десятки мілісекунд, а якщо WS-серверів кілька — з'єднання учасників розподілені по різних нодах.

Правильна схема: клієнт → WebSocket-сервер → черга (Redis Pub/Sub або Kafka topic per group) → кожен WS-сервер читає зі своєї черги та доставляє онлайн-учасникам → для офлайн-учасників — черга push-сповіщень. Як зазначено в документації Redis, Pub/Sub забезпечує затримку доставки менше 10 мс для груп до 100 учасників, що в 5 разів швидше ніж синхронний fanout за однакового навантаження.

Підхід Максимальний розмір групи Затримка Складність
Redis Pub/Sub до 100 10-50 мс Низька
Kafka / NATS 500+ 20-100 мс Середня

Для груп до 100 учасників Redis Pub/Sub з channel-per-group працює добре. Для більших — Kafka або NATS JetStream з consumer groups. Оптимізація за допомогою Redis Pub/Sub дозволяє скоротити витрати на серверну інфраструктуру до 30% — економія до $5000/міс на хмарних ресурсах.

Як реалізувати непрочитані повідомлення без навантаження на БД?

Класична помилка — зберігати last_read_message_id в таблиці group_members і при кожному запиті рахувати SELECT COUNT(*) WHERE id > last_read_message_id. На групі з тисячами повідомлень і сотнями учасників це вбиває базу.

Робочий підхід: Redis Hash unread:{user_id}:{group_id} → інкремент на кожне нове повідомлення в групі, reset при відкритті чату. Сумарний бейдж — HVALS unread:{user_id} і підсумовування на клієнті. При перезапуску Redis — перерахунок з PostgreSQL як fallback. Цей метод знижує навантаження на БД в десятки разів — як показують наші тести, час запиту впав з 200 мс до 5 мс, що забезпечує 99.9% доступність навіть при піковому навантаженні до 10 000 повідомлень/с.

Ролі та права

Схема: owner, admin, member. Права гранулярно: can_send_messages, can_add_members, can_remove_members, can_edit_group_info. Зберігається в group_members.role + JSON-поле permissions для кастомних перевизначень. Перевірка на рівні API middleware до виконання action. Така event-driven архітектура з використанням CQRS дозволяє легко масштабувати логіку прав.

Роль Типові права
owner всі
admin can_send, can_manage_members
member can_send, can_view

Які складнощі в мобільному UI?

Список учасників та згадування

При введенні @ — popup з фільтрацією учасників. На iOS: UITextView + кастомний UIView-overlay позиціонований над клавіатурою через KeyboardLayoutGuide. При виборі учасника — вставка атрибутованого рядка з NSAttributedString і кастомним NSTextAttachment або просто кольоровий range. У SwiftUI використовуємо TextField з модифікатором .overlay для випадаючого списку.

У Jetpack Compose: BasicTextField з кастомним VisualTransformation для фарбування згадувань + Popup з LazyColumn для випадаючого списку. Тригер @ — через TextFieldValue.text.lastIndexOf('@') з debounce 200ms.

На бекенді при збереженні повідомлення — парсинг згадувань регуляркою, створення записів message_mentions[], окреме push-сповіщення згаданим учасникам навіть якщо вони вимкнули сповіщення групи.

Медіа та файли в групі

Фото, відео, документи — завантаження через presigned S3 URL як у приватному чаті, але з додатковою перевіркою квот (ліміт сховища на групу або на користувача). Медіагалерея групи — окремий екран з UICollectionView/LazyVerticalGrid, вибірка з таблиці messages по type IN ('image','video') AND group_id = ? з пагінацією.

Прев'ю посилань (link preview): на сервері при отриманні повідомлення з URL — асинхронний job (Sidekiq/Celery) парсить Open Graph метадані, кешує в Redis на 24h, клієнт отримує дані прев'ю в події message.updated.

Індикатор набору тексту

WS-event typing.start / typing.stop від клієнта → сервер розсилає в групу з user_id того, хто друкує → клієнти показують «Іван набирає...». Проблема: при 20 одночасно друкуючих учасниках UX ламається. Обмеження: показуємо максимум 3 імені, далі «і ще N осіб набирають». Таймаут: якщо typing.stop не прийшов — автоматично ховаємо через 5 секунд.

Офлайн та синхронізація

Груповий чат вимагає локальної БД. SQLite через SQLCipher (шифрування) — схема: groups, messages, group_members. При старті застосунку — sync з сервером: запит всіх груп з last_synced_at, потім для кожної групи — повідомлення після останнього message_id. Конфлікти при одночасному редагуванні — Last Write Wins по updated_at. Для idempotent delivery використовуємо at-least-once семантику.

На iOS — GRDB.swift поверх SQLite, на Android — Room з Flow-підпискою для реактивного оновлення UI.

Які типові помилки допускають при реалізації?

  • N+1 при завантаженні списку груп: для кожної групи окремий запит last_message. Рішення: JOIN з підзапитом або денормалізоване поле last_message_preview.
  • Push на всі повідомлення без урахування mute: учасник заглушив групу, але отримує пуш. Перевірка group_members.notifications_muted на сервері перед відправкою FCM/APNs.
  • Видалення учасника без cleanup: після кіка користувач технічно отримує WS-події якщо з'єднання не закрито. Потрібен примусовий disconnect через сигнал на WS-сервері.
  • Відсутність optimistic updates: повідомлення з'являється в UI тільки після відповіді сервера. Правильно — показати одразу зі статусом «sending», оновити/відкотити при відповіді.

Як уникнути N+1 при завантаженні списку груп?

Використовуйте денормалізацію: додайте в таблицю groups поле last_message_preview з текстом і часом останнього повідомлення. Оновлюйте його через тригер або при вставці повідомлення. Це виключає зайві JOIN-запити та знижує час завантаження списку до 50 мс.

Терміни та склад робіт

Базовий груповий чат (створення груп, ролі admin/member, повідомлення з пагінацією, пуши) — 3-4 тижні. Повна функціональність (медіа, згадування, link preview, офлайн-синхронізація, квоти сховища, галерея групи) — 2-3 місяці. Для Flutter-проєкту — приблизно на 30% швидше за рахунок єдиного UI-шару. Вартість під ключ від $20 000, точний бюджет визначається після детального ТЗ.

Що входить в роботу

  • Архітектурна документація (схеми даних, діаграми послідовностей)
  • Вихідний код модуля чату (iOS/Android/Flutter) з коментарями
  • Деплой серверної частини (Docker, CI/CD)
  • Інтеграція з push-сервісами (APNs/FCM)
  • Тестовий план і звіт про навантажувальне тестування
  • Підтримка 2 тижні після релізу

Наші інженери мають сертифікати Apple і Google, а також 5+ років досвіду в розробці чатів для проєктів з аудиторією до 1 млн користувачів. Ми гарантуємо стабільну роботу під навантаженням і дотримання термінів.

Замовте розробку групового чату під ключ — пишіть нам для безкоштовної оцінки вашого проєкту. Ми підготуємо архітектурний огляд, таймлайн і кошторис протягом 2 робочих днів. Вартість проєкту від $20 000, термін — від 3 тижнів.