Real-time fan-out uses channel membership cache, not per-message permission reads
Why it matters
Per-message database queries to determine channel membership collapse throughput to whatever the database can handle divided by messages per second. At modest scale — 50 messages per second across 10 channels — that is 50 synchronous permission queries in the hot path, turning a real-time feature into a slow polling system. ISO 25010 performance-efficiency and scalability require that read-hot paths use caching rather than per-operation round trips to persistent storage.
Severity rationale
Medium because per-message permission queries add a synchronous database round-trip for every broadcast, collapsing throughput at moderate message rates.
Remediation
Use the WebSocket framework's built-in room tracking — or an explicit in-memory/Redis membership map — as the fan-out source. Channel membership should be updated on subscribe/unsubscribe, not queried on send.
// Subscribe: update the membership cache once
socket.on('subscribe', async (channel: string) => {
if (await checkChannelAccess(socket.userId, channel)) {
socket.join(channel); // Socket.IO maintains membership in-process
}
});
// Send: fan out from the cache — zero DB queries
socket.on('send_message', (data: { channel: string; content: string }) => {
io.to(data.channel).emit('message', {
id: nanoid(),
content: data.content,
userId: socket.userId,
timestamp: Date.now(),
});
});
// Avoid: querying DB on every send
// const members = await db.select().from(channelMembers).where(...);
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Real-Time Communication Audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- iso-25010:2011 · performance-efficiency.time-behaviour — Time Behaviour — cached channel membership eliminates per-message database round-trips
Taxons
History
- 2026-04-18·v1.0.0·Initial import from community-realtime·automated