Реалізація чата зрителів прямої трансляції в мобільному додатку
Чат прямої трансляції — це не просто «відправити повідомлення». При 10 000 одночасних зрителів стандартний підхід з Firebase Firestore onSnapshot створює 10 000 відкритих слухачів, і ваш рахунок за Firebase улітає у космос. При 1000 — UX все ще зламаний, якщо повідомлення приходять швидше, ніж користувач читає.
Архітектура під навантаження
Для live-чата ключове рішення — fan-out на сервері, не на клієнті. Клієнт підписується на один WebSocket/SSE-канал та отримує агрегований потік. Не відкриває listener на кожне повідомлення.
Стек, який працює при 5000+ одночасних зрителів:
-
Транспорт: WebSocket (Socket.io або чистий
ws) або Server-Sent Events (SSE) - Буфер: Redis Pub/Sub для розповсюджування повідомлень між інстансами сервера
- Троттлінг: на сервері — не більше 50–100 повідомлень/сек в один канал, надлишок агрегується або відбрасується
- Клієнтський рендер: віртуалізований список з обмеженою глибиною (останні 100–200 повідомлень)
React Native: віртуалізований чат
FlatList з inverted — стандартне рішення для чата. Але при швидкому потоці (10+ повідомлень у секунду) setState на кожне повідомлення убиває FPS. Батчинг обов'язковий:
const BATCH_INTERVAL_MS = 250;
const [messages, setMessages] = useState<Message[]>([]);
const pendingRef = useRef<Message[]>([]);
useEffect(() => {
const socket = new WebSocket(CHAT_WS_URL);
socket.onmessage = (event) => {
const msg: Message = JSON.parse(event.data);
pendingRef.current.push(msg);
};
const flushInterval = setInterval(() => {
if (pendingRef.current.length === 0) return;
const batch = pendingRef.current.splice(0);
setMessages(prev => {
const updated = [...batch.reverse(), ...prev];
return updated.slice(0, 200); // тримаємо не більше 200 повідомлень у пам'яті
});
}, BATCH_INTERVAL_MS);
return () => {
clearInterval(flushInterval);
socket.close();
};
}, []);
pendingRef — не steyт, тому запис у нього не викликає ре-рендер. Раз у 250 мс сбрасуємо накопчений батч у steyт одним setState. На iPhone SE 2020 це утримує 60 FPS при потоці 30 повідомлень/сек.
Модерація та антиспам
Live-чат без модерації — магніт для спама. Мінімальний набір:
- Rate limiting на клієнті: кнопка відправки блокується на 2–3 секунди після відправки. Не замна серверному rate limiting, але знижує випадковий спам.
-
Фільтрація на сервері: бібліотека
bad-wordsабо кастомний regex перед записом у Redis. - Slow mode: налаштовуваний інтервал між повідомленнями для кожного користувача (30–60 сек для неверифікованих акаунтів).
- Мьют та бан: soft-бан через Redis SET з TTL, зберігати не у БД — навантаження занадто високе.
Донати та supers: виділені повідомлення
Суперчат (paid highlight) — окремий канал з іншим пріоритетом. Виділені повідомлення не беруть участь у троттлінгу, відображаються окремим компонентом поверх основного чата і залишаються видимими N секунд незалежно від швидкості потоку.
У React Native: абсолютно позиціонований View з анімацією появи/зникнення (Animated.timing або react-native-reanimated). Очередь суперчатів — окремий steyт, незалежний від основного списку.
Реконнект та пропущені повідомлення
При обриві з'єднання на 10 секунд користувач пропустив N повідомлень. Два підходи:
- Ignore gap — при реконнекті продовжуємо з поточного моменту. Простіше, але користувач бачить "дирку" у чаті.
-
Backfill — при реконнекті запрашуємо повідомлення за період відсутності. REST-endpoint
/chat/history?after=<timestamp>&limit=50. Обмежуємо лімітом — не грузимо 5000 пропущених повідомлень.
Для живого чата варіант 1 прийнятний. Користувач розумієу, що пропустив частину — це нормальне поведінку для live.
Оцінка
WebSocket-чат з батчингом, rate limiting та базовою модерацією у React Native: 3–5 тижнів. З системою донатів та історією повідомлень: 5–8 тижнів.







