Ответ
В моем последнем коммерческом проекте — платформе для онлайн-ивентов на Node.js — мы отслеживали активных пользователей в реальном времени. Пиковая нагрузка во время крупных трансляций достигала ~50 000 одновременных подключений через WebSocket.
Для подсчета и хранения этой метрики мы использовали комбинацию Redis (для скорости) и PostgreSQL (для персистентности и аналитики).
Пример реализации счетчика активных пользователей на Node.js с Socket.io и Redis:
const { createClient } = require('redis');
const { Server } = require('socket.io');
const redisClient = createClient();
await redisClient.connect();
const io = new Server(server);
io.on('connection', async (socket) => {
const userId = socket.handshake.auth.userId;
// Добавляем пользователя в Redis Set с ключом, имеющим TTL (например, на 5 минут)
await redisClient.sAdd('active_users:session', userId);
await redisClient.expire('active_users:session', 300);
// Получаем и эмитируем общее количество
const count = await redisClient.sCard('active_users:session');
io.emit('activeUsersCount', count);
socket.on('disconnect', async () => {
await redisClient.sRem('active_users:session', userId);
const newCount = await redisClient.sCard('active_users:session');
io.emit('activeUsersCount', newCount);
});
});
// Эндпоинт для получения текущего количества
app.get('/metrics/active-users', async (req, res) => {
const count = await redisClient.sCard('active_users:session');
res.json({ activeUsers: count });
});
Итоговые цифры по DAU/MAU мы агрегировали в PostgreSQL и визуализировали в Grafana, подключенной к этой БД.
Ответ 18+ 🔞
А, вспомнил! Был у меня один проект, ёпта, платформа для онлайн-ивентов. Сидим, значит, пилим на Node.js, всё вроде норм. А потом бац — ивент крупный, трансляция, народ повалил. И тут выясняется, что наш счётчик активных юзеров, который мы на коленке слепили, просто накрылся медным тазом. Под нагрузкой в ~50 000 одновременных сокетов он начал бздеть так, что мама не горюй. Каркалыга полная.
Пришлось, блядь, э бошка думай. Нужно было считать в реальном времени, чтобы на дашборде цифра прыгала, а потом ещё и статистику по DAU/MAU для аналитиков гонять. Доверия ебать ноль было к чистому PostgreSQL на такие пики, поэтому родили гибридную схему.
Суть в чём: всё, что горячее и должно быть быстрее пули, — в Redis. Он у нас как кэш-счётчик работал. А уже потом, по расписанию или событиям, данные агрегировались и на постоянное хранение в PostgreSQL сваливались. А из Постгре уже Grafana всё это дело красиво рисовала. Без неё — пизда рулю, бизнес ничего не понимает.
Вот, смотри, как примерно выглядел костяк этого цирка на Socket.io:
const { createClient } = require('redis');
const { Server } = require('socket.io');
const redisClient = createClient();
await redisClient.connect();
const io = new Server(server);
io.on('connection', async (socket) => {
const userId = socket.handshake.auth.userId;
// Добавляем пользователя в Redis Set с ключом, имеющим TTL (например, на 5 минут)
await redisClient.sAdd('active_users:session', userId);
await redisClient.expire('active_users:session', 300);
// Получаем и эмитируем общее количество
const count = await redisClient.sCard('active_users:session');
io.emit('activeUsersCount', count);
socket.on('disconnect', async () => {
await redisClient.sRem('active_users:session', userId);
const newCount = await redisClient.sCard('active_users:session');
io.emit('activeUsersCount', newCount);
});
});
// Эндпоинт для получения текущего количества
app.get('/metrics/active-users', async (req, res) => {
const count = await redisClient.sCard('active_users:session');
res.json({ activeUsers: count });
});
Фишка в том, что мы использовали Redis Set. Каждый новый коннект — добавляем айдишник в сет. На дисконнекте — удаляем. А чтобы сет не рос до бесконечности от забытых сессий, вешали TTL на ключ, скажем, на 5 минут. Это, конечно, упрощённый вариант, но ни хуя себе, как хорошо зашло. Главное — скорость, все операции атомарные.
А потом уже ночной джобой эти "активные сессии" из Redis перемалывались, считались уникальные юзеры за день/месяц и пихались в PostgreSQL. И всё это, блядь, светилось в Grafana. Бизнес смотрел на графики и думал, что мы ебушки-воробушки. А мы-то знаем, какой это был пиздопроёбищный геморрой всё отладить, чтобы под нагрузкой не посыпалось.