Skip to content

mohidev-tech/redis-clone

🟥 redis-clone

A from-scratch Redis-protocol-compatible key/value server in Java 21 + Netty. Speaks RESP2, supports GET/SET/DEL/EXISTS/EXPIRE/TTL/KEYS/INCR/FLUSHALL/PING/ECHO. Eviction policies (LRU/LFU/FIFO), background expiration sweeper, ~500 LOC.

ci License: Apache 2.0 Java 21 Netty 4.1


What it does (real terminal output)

$ mvn -q -DskipTests package
$ java -jar target/redis-clone-1.0.0.jar 6379

# Talk to it with the real redis-cli:
$ redis-cli -p 6379
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> SET mykey "hello" EX 60
OK
127.0.0.1:6379> GET mykey
"hello"
127.0.0.1:6379> TTL mykey
(integer) 59
127.0.0.1:6379> INCR counter
(integer) 1
127.0.0.1:6379> INCR counter
(integer) 2
127.0.0.1:6379> KEYS *
1) "mykey"
2) "counter"
127.0.0.1:6379> DEL mykey
(integer) 1

It is a drop-in protocol-compatible replacement for the subset of Redis commands listed above. Real redis-cli connects without modification.


What it proves

Capability Where
RESP2 codec from scratch ProtocolParser — array-of-bulk-strings, bulk, simple, integer, error; inline-command fallback for redis-cli handshake quirks
Netty pipeline RedisServer boots an NioEventLoopGroup, one ClientHandler instance per connection (Netty handles framing + concurrency)
Thread-safe store DataStore uses ConcurrentHashMap for reads, synchronized for the set/delete/evict triad that has to atomically maintain memory accounting
Active + passive expiration RedisValue.isExpired() checks lazily on every access; background ScheduledExecutorService sweeps every second to release memory from cold expired keys
Pluggable eviction policies LRU (default), LFU, FIFO — switch by comparator. Evicts 25% of keys when memory exceeds limit
Bounded memory maxMemoryBytes arg + estimateSize heuristic per value type; eviction triggers before OOM

Quickstart

git clone https://github.com/mohidev-tech/redis-clone
cd redis-clone

mvn -DskipTests package
java -jar target/redis-clone-1.0.0.jar 6379

# In another terminal — drives the server with the actual redis-cli:
redis-cli -p 6379 PING

Docker

docker build -t redis-clone .
docker run -p 6379:6379 redis-clone

CLI args

java -jar redis-clone-1.0.0.jar [PORT] [MAX_MEMORY_BYTES]
                                   ↑ default 6379
                                        ↑ default 1_000_000_000 (1 GB)

Why you'd build this

Not to replace Redis. To understand Redis. The RESP protocol is small enough to implement in an afternoon, and doing so teaches:

  • How a binary protocol packs into TCP frames (length-prefixed bulk strings, CRLF terminators).
  • Why Redis chose single-threaded data-plane + multi-event-loop I/O.
  • How active+passive expiration trades memory for CPU.
  • Where the eviction policy decision lives (it's not just "LRU vs LFU" — it's "what's your access-pattern fingerprint").

Supported commands

Command Status
GET key
SET key value [EX seconds]
DEL key [key ...]
EXISTS key [key ...]
EXPIRE key seconds
TTL key ✅ (-1 = no expiry, -2 = missing)
KEYS pattern ✅ (glob: *, ?)
INCR key
FLUSHALL
PING
ECHO message
COMMAND ⚠️ Returns empty array (enough for redis-cli handshake)
Lists, sets, hashes, sorted sets, pub/sub, streams, persistence (RDB/AOF) ❌ Out of scope

Design choices

Choice Why
Netty SimpleChannelInboundHandler, not raw NIO Netty does framing, backpressure, and the event loop. Reimplementing those is its own portfolio project; for this one we focus on RESP + the store
ConcurrentHashMap for the hot read path Get/exists/ttl are lock-free. The synchronized set/delete/evict only contends when memory accounting changes
Estimate size by type, not by serializing Strings: 2 * length. Lists/sets/maps: count × heuristic-per-element. Wrong in absolute terms, right enough relative to itself — eviction triggers proportionally as data grows
Inline command fallback in the parser redis-cli sends inline PING\r\n during connect; without this fallback the handshake misses and the prompt shows a phantom error

Limitations (out of scope, deliberately)

  • No persistence. Data lives in process memory; restart = empty. RDB snapshots + AOF append-only file are real Redis features and out of scope for a learning exercise.
  • Single-node only. No replication, no cluster, no consistent hashing. Add MASTER/REPLICATION + sentinel later.
  • No auth. Anyone who can connect can read+write. Pair with a network policy.
  • No pipelining optimization. Pipelining works (Netty handles it) but we don't batch-write the response — small perf loss vs real Redis.

Contributing

PRs welcome. See CONTRIBUTING.md. Security issues: SECURITY.md.

License

Apache 2.0 — see LICENSE and NOTICE.

About

From-scratch Redis-protocol-compatible key/value server in Java 21 + Netty. RESP2, LRU/LFU/FIFO eviction, active+passive expiration

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors