How Ephemeral RAM-Only Messaging Works: Inside the Ring Buffer Architecture
When you send a message on almost any mainstream chat platform, a complex chain of persistent storage operations is triggered. Your message is serialized, written to a database write-ahead log (WAL), replicated across multiple storage nodes, indexed for search, and backed up to cold storage. Even when a service offers "disappearing messages," deleting data from a B-tree or LSM-tree on an SSD often leaves uncompacted blocks and filesystem artifacts behind.
sling.chat takes a fundamentally different systems engineering approach: what is never written to disk can never be recovered from disk. Instead of a database, every channel on sling.chat uses a fixed-size, in-memory Ring Buffer (circular buffer) written in C++20.
What Is a Circular Ring Buffer?
A ring buffer is a contiguous block of pre-allocated memory—such as a std::vector<Message> of fixed capacity $N$—managed by two logical pointers or indices: a head index pointing to the oldest element, and a size counter tracking how many elements are currently stored.
- Phase 1 (Filling the Buffer): While
size < capacity, each new message is written to index(head + size) % capacity, andsizeincrements by 1. - Phase 2 (Wrap-Around Overwrite): Once
size == capacity(for example, 100 messages), the buffer is full. When message #101 arrives, it directly overwrites the memory slot athead, andheadadvances to(head + 1) % capacity.
Because the underlying memory slots are reused circularly, inserting a new message operates in strict $O(1)$ constant time with zero dynamic heap re-allocations once the channel is active.
Thread Synchronization with C++20 std::shared_mutex
In a multi-threaded WebSocket server, dozens of clients may join a channel at the same moment to read the recent message history while other participants are actively broadcasting new messages. Protecting the ring buffer with a naive exclusive mutex would serialize all readers behind one another.
To maximize throughput, sling.chat's RingBuffer<T> template uses a reader-writer lock (std::shared_mutex):
- Concurrent Readers (
std::shared_lock): When a user joins#generalor switches to#tech, the server takes a shared read lock to produce a chronological snapshot. Any number of threads can hold a shared lock simultaneously without blocking each other. - Exclusive Writers (
std::unique_lock): When a message passes SHA-256 Proof-of-Work verification, the worker thread acquires a brief exclusive lock for the few nanoseconds required to move theMessagestruct into the circular slot and advance the index.
Performance and Privacy Benefits
- Sub-Millisecond Latency: Reading and writing to L3 cache and system RAM is orders of magnitude faster than waiting for disk I/O or network round-trips to an external database container.
- Predictable Memory Footprint: Because both the number of messages per channel and maximum payload length are bounded, memory usage cannot grow unboundedly over time.
- Guaranteed Ephemerality: Old messages are physically overwritten in RAM as the conversation progresses, and if the server container stops or restarts, the entire volatile memory space vanishes instantly.