This project is a Redis server implementation in Java, built from scratch as part of the Codecrafters Redis Challenge. It follows the client–server architecture and can accept requests from the Redis CLI over TCP sockets. Communication between client and server is handled using the RESP protocol.
src/→ Core Java implementation of the Redis server (for subsequent modules, prefix =main/java)/comparator→ Comparators for custom sorting logic/constants→ Public constants shared across the repository/domain→ DTO shared across the repository/enums→ Enums shared across the repository/handler→ List of command handlers that followregister-handlerdesign patterns/command/impl→ List of command-handler implementations/auth→ AuthHandler, AclHandler/core→ ConfigHandler, EchoHandler, GetHandler, IncrHandler, InfoHandler, KeysHandler, PingHandler, SetHandler, TypeHandler/geospatial→ GeoAddHandler, GeoDistHandler, GeoPosHandler, GeoSearchHandler/list→ BLPopHandler, LLenHandler, LPopHandler, LPushHandler, LRangeHandler, RPushHandler/pubsub→ PublishHandler, SubscribeHandler, UnsubscribeHandler/replication→ PsyncHandler, ReplConfigHandler, SaveHandler, WaitHandler/sortedset→ ZAddHandler, ZCardHandler, ZRangeHandler, ZRankHandler, ZRemHandler, ZScoreHandler/stream→ XAddHandler, XRangeHandler, XReadHandler/transaction→ DiscardHandler, ExecHandler, MultiHandler
CommandHandlerinterface → followregister-handlerdesign patterns
/job/impl→ List of job implementations to handle busy-loading job queues- HandshakeHandler
- PropagateHandler
- RespHandler
JobHandlerinterface → follow steps: register job → listen task → process task
/replication→ Follow these logics- MasterManager
- ReplicaClient
/service→ Several util classes- GeoUtils, HashUtils, RandomUtils, RDBLoaderUtils, RDBParser, RDBParserUtils, RedisLocalMap, RESPParser, RESPParserUtils, RESPUtils, ServerUtils, StreamUtils, StringUtils, SystemPropHelper
/stream→RedisInputStreamthat wraps aroundFilterInputStream
tests/→ Validation against Redis client commands
- Command: follow register-handler design patterns
- Replication architecture
- Replication configuration
- Replication handshake
-
Since your program acts as a Redis server, we can start with command
$ ./your_program.sh
-
As Redis follows client-server architecture, there are 2 ways to send requests to the Redis server
a. Per session: start a redis-client session to send CMD requests to the Redis server
$ redis-cli
b. Per request (2 ways):
- echo the CMD requests to
redis-cli- e.g.$ echo -e "PING" | redis-cli - write command directly - e.g.
$ redis-cli -p SET foo bar PX 100
- echo the CMD requests to
-
Command examples
# send 1 command PING in 1 request $ redis-cli echo -e "PING" | redis-cli PONG # send multiple commands PING in 1 request $ redis-cli echo -e "PING\nPING" | redis-cli PONG PONG # send 1 command PING in 1 request and concurrently $ redis-cli echo -e "PING" | redis-cli; echo -e "PING" | redis-cli PONG PONG # send 1 command ECHO in 1 request $ redis-cli echo -e "ECHO hey" | redis-cli # send 1 command SET in 1 request # then send 1 command GET in another request $ redis-cli echo -e "SET foo bar" | redis-cli OK $ redis-cli echo -e "GET foo" | redis-cli bar $ redis-cli echo -e "GET hello" | redis-cli (nil) # send 1 command SET in 1 request with expiry # then immediately send 1 command GET in another request $ redis-cli echo -e "SET foo bar PX 100" | redis-cli; echo -e "GET foo" | redis-cli OK "bar" # send 1 command SET in 1 request with expiry # then sleep for 0.2 sec then send 1 command GET in another request $ redis-cli echo -e "SET foo bar PX 100" | redis-cli; sleep 0.2; echo -e "GET foo" | redis-cli OK (nil) # send 1 command INFO in 1 request to a Master node about its replication setup $ redis-cli echo -e "info replication" | redis-cli role:master master_replid:qk7ah4jae1nyyyq979mbsgbta09rierunnq74158 master_repl_offset:0% # set up 1 replica node and 1 master node # send 1 command INFO in 1 request to a Replica node about its replication setup $ ./your_program.sh --port 6379 # master node $ ./your_program.sh --port 6380 --replicaof "localhost 6379" $ redis-cli -p 6380 info replication role:slave master_replid:sou6zwzlo3ixngivzijxwb023ju8ratd53xy5heg master_repl_offset:0 # set up 1 replica node and 1 master node # send 1 command SET in 1 request to a Master node # then send 1 command GET in 1 request to a Replica node $ ./your_program.sh --port 6379 # master node $ ./your_program.sh --port 6380 --replicaof "localhost 6379" $ redis-cli -p 6379 SET foo bar OK $ redis-cli -p 6380 GET foo "bar" # set up 2 replicas node and 1 master node # send 1 command SET in 1 request to a Master node # then send 1 command GET in 1 request to 1st Replica node # then send 1 command GET in 1 request to 2nd Replica node $ ./your_program.sh --port 6379 # master node $ ./your_program.sh --port 6380 --replicaof "localhost 6379" $ ./your_program.sh --port 6381 --replicaof "localhost 6379" $ redis-cli -p 6379 SET foo bar OK $ redis-cli -p 6380 GET foo "bar" $ redis-cli -p 6381 GET foo "bar" # set up 2 replicas node and 1 master node # send WAIT command in 1 request to a Master node $ ./your_program.sh --port 6379 # master node $ ./your_program.sh --port 6380 --replicaof "localhost 6379" $ ./your_program.sh --port 6381 --replicaof "localhost 6379" $ redis-cli WAIT 3 2000 # wait up to 2s then return the total number of replicas (integer) 2 # set up 2 replicas node and 1 master node # send SET command then WAIT 2nd command in 1 request to a Master node $ ./your_program.sh --port 6379 # master node $ ./your_program.sh --port 6380 --replicaof "localhost 6379" $ ./your_program.sh --port 6381 --replicaof "localhost 6379" $ redis-cli SET foo 123; redis-cli WAIT 1 500 # must wait until either 1 replica has processed previous commands or 500ms have passed $ redis-cli SET bar 456; redis-cli WAIT 2 500 # must wait until either 1 replica has processed previous commands or 500ms have passed # send 1 command SET in 1 request then send TYPE in another request $ redis-cli SET some_key "foo" OK $ redis-cli TYPE some_key "string" # send 1 command XADD in 1 request then send TYPE in another request $ redis-cli XADD stream_key 0-1 foo bar "0-1" $ redis-cli TYPE stream_key "stream" # send a sequence of commands: SET, INCR, INCR for number type $ redis-cli SET foo 5 "OK" $ redis-cli INCR foo (integer) 6 $ redis-cli INCR foo (integer) 7 # send a sequence of commands: SET, INCR, INCR for non-number type $ redis-cli SET foo bar "OK" $ redis-cli INCR foo (error) ERR value is not an integer or out of range # send a sequence of commands within 1 redis-session: MULTI, QUEUED, EXEC $ redis-cli > MULTI OK > SET foo 41 QUEUED > INCR foo QUEUED > EXEC 1) OK 2) (integer) 42 # send a sequence of commands within 1 redis-session: MULTI, EXEC $ redis-cli > MULTI OK > EXEC (empty array) # send a sequence of commands within 1 redis-session: MULTI, SET, DISCARD $ redis-cli > MULTI OK > SET foo 41 QUEUED > DISCARD OK > DISCARD (error) ERR DISCARD without MULTI # send a sequence of commands within 1 redis-session: MULTI, SET, INCR, SET, EXEC # failures within transactions $ redis-cli > MULTI OK > SET foo xyz QUEUED > INCR foo QUEUED > SET bar 7 > EXEC 1) OK 2) (error) ERR value is not an integer or out of range 3) OK
-
Unit Tests
-
Command to run tests is
$ mvn test -Dtest="handler.command.impl.**" -q 2>&1 -
Expected result is
[INFO] [INFO] Results: [INFO] [INFO] Tests run: 284, Failures: 0, Errors: 0, Skipped: 0 [INFO]
-
-
Stress Test
-
Analysis
The Redis Serialization Protocol (RESP) is the core communication protocol between Redis clients and servers.
References:
Key properties of RESP:
Binary-safe: Works with arbitrary byte sequences.Length-prefixed: Encodes data with size information for efficient parsing.Simple & human-readable: Easy for developers to debug.Fast to parse: Optimized for high-performance servers.
Redis replication is the foundation for high availability and failover.
-
A Master node handles both read and write requests, while Replica nodes serve read-only requests.
-
When clients send write requests to the Master, it propagates the changes to all connected Replicas as a stream of bytes.
There are two main aspects of replication:
-
Full sync vs. Partial sync: - After a disconnection or crash, a Replica may request a partial sync if its local state is still within the Master's backlog buffer. - If not possible, the Replica requests a full sync, where the Master generates an RDB snapshot and transfers it as a byte stream.
-
Asynchronous vs. Synchronous syncing: - In asynchronous replication, the Master sends updates in the background without waiting for Replica acknowledgments. - In synchronous replication, the Master waits for acknowledgments from N Replicas before confirming a write.
Redis replication relies on a well-defined ID and offset system to track dataset versions and synchronize efficiently.
-
Replication ID and Offset: Each Master maintains a unique
replication ID(40-character string) and anoffset. Every client write increases the Master's offset, which is used to determine how Replicas catch up. -
PSYNC Command: Replicas use
PSYNCto request synchronization. If their offset falls within the Master's backlog buffer, the Master sends only the missing byte stream. Otherwise, a full sync is performed. -
Replication Steps (Master → Replica):
- The Master spawns a background process to create an RDB snapshot, while buffering client commands in parallel.
- The snapshot (RDB file) is transferred to the Replica and loaded into memory.
- The buffered command stream is sent over (using RESP protocol) to bring the Replica fully up to date.
-
Efficiency: If multiple Replicas reconnect at once, the Master reuses a single background save to serve them all, reducing overhead.
-
Auto-reconnect: Replicas can automatically
reconnectandresynchronizewhen the Master–Replica link is interrupted.
Redis is widely used in distributed systems due to its simplicity and speed. Some common patterns:
-
Distributed Locks: Implemented with atomic commands like
INCRandDEL, often wrapped in Lua scripts for transactional guarantees. -
Pub/Sub (Lightweight Event Streaming): Similar in spirit to Kafka streams. Events are appended to logs and consumed by consumer groups, which distribute messages across workers in parallel.
-
Rate Limiting: Sliding window algorithms using atomic commands (
INCR,EXPIRE) can enforce request limits efficiently. -
Caching: Fast in-memory key–value store for reducing database load.
-
Real-time Dashboards: Using sorted sets (
ZSET) for leaderboards, ranking systems, or priority queues.
Like any distributed system, Redis comes with design tradeoffs and operational challenges:
- Redis keys are mapped to hash slots, which are distributed across nodes.
- If a node fails, consistent hashing allows reassigning only a small portion of slots to other healthy nodes instead of recomputing all keys.
- This reduces rehashing overhead and helps maintain cluster stability.
- A hot key is a key that receives disproportionately high traffic.
- If all requests hit the same node, it creates an unbalanced workload.
- Solution: Replicate or shard the hot key into multiple keys, allowing load to spread across nodes.
Built to learn low-level system design, networking, and distributed caching concepts — one command at a time.