Chapter 11: db-mux — How It Works and Why It's 2.5× Faster
db-mux Write Path
// Application calls db-mux:
redis_connection_mux_set(mux, key, value, callback);
// Inside db-mux:
// 1. Get pre-allocated buffer from pool (NO heap allocation)
buffer_t *buf = buffer_pool_get(pool);
// 2. Build RESP3 wire format DIRECTLY into buffer
// (manual formatting, no snprintf, no SDS)
resp_write_array_header(buf, 3); // *3\r\n
resp_write_bulk_string(buf, "SET", 3); // $3\r\nSET\r\n
resp_write_bulk_string(buf, key, klen); // $N\r\nkey\r\n
resp_write_bulk_string(buf, val, vlen); // $N\r\nval\r\n
// 3. Hand pre-formatted buffer to hiredis (ZERO re-encoding)
redisAsyncFormattedCommand(ctx, cb, priv, buf->data, buf->len);
// hiredis just appends to obuf — no formatting work
// 4. Enable POLLOUT directly (no defer)
evl_fd_add_events(fd, POLLOUT);
// Total: 0 heap allocations per command
Why It's Faster: Side-by-Side
| Step | db-proxy | db-mux |
|---|---|---|
| Key serialization | malloc + copy | Write directly to pooled buffer |
| RESP formatting | hiredis does it (malloc SDS, snprintf) | App does it (no alloc, manual format) |
| Hand to hiredis | redisAsyncCommandArgv (re-formats) | redisAsyncFormattedCommand (just append) |
| EVL integration | Creates defer per write | Directly enables POLLOUT |
| Allocations/cmd | 2-3 | 0 |
| CPU at 300K/s | ~80% | ~29% |
The MSET Extension
// db-mux multicommand API (new for this feature):
mux_multi_t *multi = redis_connection_mux_multi_start(mux);
redis_connection_mux_multi_add(multi, "teid:123", teid_val);
redis_connection_mux_multi_add(multi, "ueip:456", ueip_val);
redis_connection_mux_multi_add(multi, "l2tp:789", l2tp_val);
redis_connection_mux_multi_exec(multi, callback);
// Builds ONE MSET command with all keys → ONE Redis command
// ONE callback when done (no pending counter needed)
Trade-off: Latency vs CPU
⚠️ db-mux Has Higher Per-Command Latency
db-mux buffers an entire chunk before poll handles any events. Responses pile up in the socket buffer. RTT is ~1000µs vs db-proxy's ~300µs at 100K/sec. But CPU is 2.5× lower. For PCG where CPU is the constraint (not individual command latency), this is the right trade-off.
Key Takeaways
- db-mux: zero heap allocations, pre-formatted RESP, no defers
- 2.5× more CPU-efficient than db-proxy
- Higher per-command latency (buffering effect) but lower CPU
- MSET multicommand API: batch multiple keys into one Redis command
- Guaranteed single TCP write per EVL iteration (no callback limit issue)