Real-time Detection of Redis Big Keys Using eBPF Without Modifying Application Code
This article presents a lightweight, zero‑code‑change solution that leverages eBPF uprobe tracing to monitor Redis in real time, capture client IP, command arguments, and response size, detect big‑key operations, and report them with minimal performance impact.
Redis big keys can cause network congestion, memory pressure, and master blockage, yet traditional RDB‑snapshot analysis is neither real‑time nor comprehensive. The article introduces an eBPF‑based approach that inserts kernel‑level hooks without altering Redis source code, enabling millisecond‑level detection of large responses.
Background
Frequent reads/writes of large keys fill network bandwidth, exceed maxmemory , and may trigger unwanted master‑slave failover. Existing daily RDB scans miss short‑lived keys and lack immediacy.
eBPF Overview
eBPF (Extended Berkeley Packet Filter) allows dynamic insertion of hooks in the Linux kernel. For user‑space programs like Redis, uprobe can attach to internal functions such as call and _addReplyToBufferOrList , capturing execution data without recompiling or restarting the service.
First Implementation
The kernel part uses three eBPF maps:
call_args_map stores the client pointer from call_entry .
reply_bytes_map accumulates response byte lengths in _addReplyToBufferOrList_entry .
bigkey_log_map forwards detected big‑key events to user space.
When the accumulated bytes exceed BIGKEY_THRESHOLD_BYTES , the program extracts the client’s socket fd, command arguments, and reports them.
SEC("uprobe//redis-server:call")
int BPF_UPROBE(callEntry) { ... }
SEC("uprobe//redis-server:_addReplyToBufferOrList")
int BPF_UPROBE(_addReplyToBufferOrList_entry) { ... }
SEC("uretprobe//redis-server:call")
int BPF_URETPROBE(call_exit) { ... }User‑Space Collector
A Go program using github.com/cilium/ebpf reads events from bigkey_log_map , decodes the BigkeyLog struct, resolves the client IP via /proc/net/tcp , and prints a line such as:
ip: 127.0.0.1:56762, args: COMMAND DOCS , bytes: 213589Performance Issue
The initial version incurs heavy overhead because _addReplyToBufferOrList_entry fires for every small write, causing frequent kernel‑user context switches and raising latency from ~1 ms to ~185 ms.
Second Implementation
Instead of counting each write, the optimized version records the buffer position ( bufpos ) and the tail of the reply list at call_entry , then computes the total response size at call_exit by subtracting the before‑state from the after‑state, eliminating the per‑write probe.
static __always_inline void fillPosData(struct client_data_pos* arg, struct client_t* client) { ... }
int BPF_URETPROBE(call_exit) { ... }Benchmarks show throughput improving from 269 req/s (185 ms avg latency) to 23 255 req/s (1.8 ms avg latency), a >99 % latency reduction.
Conclusion
The eBPF‑based big‑key detector demonstrates how kernel tracing can provide real‑time observability for Redis with negligible code changes, while also highlighting the importance of probe placement for performance. Readers are encouraged to explore further eBPF projects such as bpftime for user‑space uprobe execution.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.