Uncovering JRedis: The Truth Behind Redis’s High‑Performance Architecture
The article dissects Redis’s internal architecture—its single‑threaded model, I/O multiplexing, specialized data structures, persistence mechanisms, Lua scripting, memory management, clustering, and monitoring—explaining how each design choice contributes to extreme performance and outlining the trade‑offs and best‑practice scenarios for using or avoiding Redis.
Single‑Threaded Execution Model
Redis runs all commands in a single OS thread. This design provides:
Native atomicity – every command executes without locks. Example:
// Atomic increment, no concurrency conflict
jedis.incr("counter");
jedis.incr("counter");No context‑switch overhead, eliminating a hidden performance killer.
Stable, predictable latency because there is no lock contention.
A simple mental model: commands are processed in the order they are received.
Cost of Single‑Threading
Long‑running commands block the entire server. For example, a blocking BLPOP that waits 10 seconds stalls all other operations:
// Blocking 10 seconds
jedis.blpop(10, "queue");During the wait, GET, INCR and every other request are delayed. Executing jedis.keys("*") on a database with tens of millions of keys caused a 30‑second freeze.
I/O Multiplexing
Redis uses epoll (Linux) or kqueue (BSD/macOS) to monitor thousands of sockets from a single thread. All connections are processed sequentially, achieving microsecond‑level command latency and easily supporting 100 k concurrent connections.
SET user:1 "John"
GET user:1
INCR counterPurpose‑Built Data Structures
String
Redis stores small integers as native int, short strings inline, and long strings in separate memory blocks, reducing overhead.
jedis.set("counter", "42");
jedis.incr("counter");List
Lists use a ziplist representation for small collections and fall back to a linked list for larger ones. Accessing a middle element is O(N) and can become a performance bottleneck:
jedis.lpush("queue", "task1", "task2");
jedis.rpop("queue");
// O(N) access
jedis.lindex("list", 50000);Sorted Set
Implemented with a skip‑list for ordering and a hash for fast lookup. Insertion and rank queries are O(log N), but memory usage roughly doubles because both structures are kept.
jedis.zadd("leaderboard", 9500, "player:123");
jedis.zrevrange("leaderboard", 0, 9);Hash
Hashes can store many field/value pairs under a single key. With ziplist encoding, memory consumption can be reduced by up to 60 % compared with storing each field as a separate string key.
jedis.hset("user:1", "name", "Tom");
jedis.hset("user:1", "age", "20");Persistence Mechanisms
RDB Snapshots
Configured with SAVE (e.g., SAVE 60 10000). Redis forks a child process to write a snapshot, which blocks the parent and can cause latency spikes on large datasets.
AOF Log
Enabled with appendonly yes and appendfsync everysec. Every write command is appended to a log file, causing unbounded growth; periodic BGREWRITEAOF rewrites the log to a compact form.
jedis.bgrewriteaof();Practical Persistence Choices
Cache‑only workloads – use RDB snapshots.
Session data – use AOF with appendfsync everysec.
Core data requiring durability – use AOF with appendfsync always plus replication.
Lua Scripting for Atomic Operations
A naïve read‑modify‑write sequence suffers from race conditions:
String count = jedis.get("rate:user:1");
if (Integer.parseInt(count) < 100) {
jedis.incr("rate:user:1");
}The same logic expressed as a Lua script runs atomically on the server, eliminates extra round‑trips, and supports complex logic:
local current = redis.call('GET', KEYS[1])
if current == false then current = 0 end
if tonumber(current) < tonumber(ARGV[1]) then
redis.call('INCR', KEYS[1])
return 1
else
return 0
endMemory Management
Fragmentation can double the RSS memory ( used_memory: 2.5GB vs used_memory_rss: 4.8GB). Enabling active defragmentation ( activedefrag yes) or performing periodic restarts mitigates the issue.
Eviction Policy
Typical configuration: maxmemory-policy allkeys-lru. Redis implements an approximate LRU algorithm, not a precise one.
Command Pipelining
Without pipelining, 1 000 commands generate 1 000 network round‑trips:
for (int i = 0; i < 1000; i++) {
jedis.set("k"+i, "v"+i);
}Using a pipeline collapses the round‑trips to a single batch, delivering up to a 100× throughput increase:
Pipeline p = jedis.pipelined();
for (int i = 0; i < 1000; i++) {
p.set("k"+i, "v"+i);
}
p.sync();Transactions
Redis transactions ( MULTI/EXEC) batch commands but do not provide rollback; they are not truly atomic. For required atomicity, Lua scripts are preferred.
Transaction t = jedis.multi();
t.set("k1","v1");
t.incr("c");
t.exec();Cluster Sharding
Keys are assigned to 16 384 hash slots using CRC16(key) % 16384. Multi‑key operations that span slots (e.g., MGET on different keys) may fail. Using hash tags forces keys onto the same slot:
jedis.mget("{user}:1", "{user}:2");Monitoring
Key metrics to watch:
Slow‑query log: jedis.slowlogGet() Memory usage and fragmentation
Cache hit rate
Number of evicted keys
Active connection count
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
LuTiao Programming
LuTiao Programming is a friendly community offering free programming lessons. We inspire learners to explore new ideas and technologies and quickly acquire job-ready skills.
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.
