This is a compact Redis-like server written in Go. I built it as a learning project to understand the pieces that make Redis feel fast and simple: the protocol, the data model, and how replication and persistence are hooked together.
Redis is deceptively simple at the surface. Under the hood there's a tidy set of primitives that work together to provide low-latency operations and reliable replication. I wanted a codebase that shows those primitives in plain Go: no frameworks, just sockets, goroutines, and clear data structures.
This project helped me learn: TCP/RESP parsing, in-memory data structures, expirations, replication handshakes, and the RDB snapshot format.
global.MainContext(app/global/context.go) — Every client connection gets a MainContext. It bundles the active connection, the in-memory store, parsed command arguments, transaction state, and client-specific flags. That makes handlers small and focused.global.Memory(app/global/memory.go) — A threadsafe in-memory key/value store. It stores typed values (strings, lists, streams, zsets, ...), supports expiration, and acts as the single source of truth for the server process.
connection.HandleClient(app/connection/connection.go) accepts TCP connections and runs a small loop to parse RESP frames, update theMainContext, and dispatch commands to handlers inapp/commands/.- Handlers read
ctx.Args, operate onctx.Memory, and write RESP replies back toctx.Conn. Transactional state (MULTI/EXEC) and blocking commands are handled per-context so clients remain isolated.
Commands are implemented in app/commands/ and registered in the dispatcher. Each handler validates arguments, manipulates global.Memory, and composes RESP responses using helpers in app/utils/.
Persistence and replication are intentionally simple but practical:
- On startup the server can load an RDB file using
rdb.ReadFrom(app/rdb/rdb.go). That code parses the Redis RDB binary layout and rebuilds the in-memory structures (strings, lists, streams, zsets, expirations). The implementation is compact and written to be easy to follow rather than fully optimized. - For replication we implement a PSYNC-style handshake. When a replica connects, the master may stream an RDB snapshot followed by incremental commands. The RDB parser is used during that initial sync to populate the replica's
global.Memory. - Expirations read from the RDB are scheduled after loading so TTL behavior matches the persisted snapshot.
Why this matters: implementing a real RDB parser forces the code to face binary formats, type markers, and corner cases like compressed payloads and special encodings. It also gives a clean way to bootstrap replicas without implementing an append-only log.
Note: this project focuses on RDB snapshots for durability and replication. There is no full AOF implementation here — that would be a valuable extension.
Below is a concise list of what this server supports. It aims to mirror Redis commands where useful but keeps behavior intentionally simple.
- PING — check server is alive
- ECHO — echo a message
- SET — set a string value (supports PX for millisecond TTL)
- GET — get a string value
- TYPE — return the type of the value stored at a key
- INCR — increment an integer string atomically
- RPUSH / LPUSH — push elements to the tail/head
- LRANGE — return a sub-range of a list
- LLEN — list length
- LPOP — pop one or more elements from head
- BLPOP — blocking pop with timeout
- XADD — append an entry to a stream
- XRANGE — query entries by ID range
- XREAD — read entries (blocking/non-blocking)
- ZADD — add a member with a score (
app/commands/zset.go) - ZRANK — rank of a member
- ZRANGE — range by rank
- ZCARD — cardinality
- ZSCORE — score of a member
- ZREM — remove a member
- MULTI — begin transaction
- EXEC — execute queued commands
- DISCARD — cancel a transaction
- SUBSCRIBE / UNSUBSCRIBE — subscribe or unsubscribe from channels
- PUBLISH — publish a message to subscribers
- INFO — server info (role, replication state)
- REPLCONF — replication handshake options
- PSYNC — partial synchronization (initial RDB snapshot + offset)
- WAIT — wait for write propagation to replicas
- CONFIG — query configuration
- KEYS — list keys from the loaded RDB snapshot
- Networking: plain
netpackage with a tight RESP parser. - Concurrency: goroutines for connection handling, expiration timers, and replication streams.
- Data structures: typed values for strings, lists, streams and zsets; handlers are intentionally small and readable.
- Replication: PSYNC-style sync using RDB snapshots to bootstrap replicas, then command propagation.
- Tests: small unit and integration tests (where present) focus on correctness over performance.
Run the server and connect with any Redis client that speaks RESP:
go run app/main.go
# then, from another terminal, you can use redis-cli or telnet:
redis-cli -p 6379 PINGIf your environment uses a different port, check app/server_config.go.
- Add an AOF writer for command-based persistence.
- Implement more zset and stream commands for parity with Redis.
- Implement more data structures (geospatial, hash tables, etc.)
- Add benchmark and profiling harnesses to explore performance bottlenecks.