Реализация чата зрителей прямой трансляции в мобильном приложении
Чат прямой трансляции — это не просто «отправить сообщение». При 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 — не стейт, поэтому запись в него не вызывает ре-рендер. Раз в 250 мс сбрасываем накопленный батч в стейт одним 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). Очередь суперчатов — отдельный стейт, независимый от основного списка.
Реконнект и пропущенные сообщения
При обрыве соединения на 10 секунд пользователь пропустил N сообщений. Два подхода:
- Ignore gap — при реконнекте продолжаем с текущего момента. Проще, но пользователь видит "дырку" в чате.
-
Backfill — при реконнекте запрашиваем сообщения за период отсутствия. REST-эндпоинт
/chat/history?after=<timestamp>&limit=50. Ограничиваем лимитом — не грузим 5000 пропущенных сообщений.
Для живого чата вариант 1 приемлем. Пользователь понимает, что пропустил часть — это нормальное поведение для live.
Оценка
WebSocket-чат с батчингом, rate limiting и базовой модерацией в React Native: 3–5 недель. С системой донатов и историей сообщений: 5–8 недель.







