A minimalist Redis implementation built from scratch in Go. It supports concurrent client connections, speaks the RESP protocol, and uses mutex-protected memory to ensure data consistency under load.
- High Performance: Native Go implementation using goroutines for concurrent request handling.
- Thread Safe: Robust concurrency control using
sync.Mutexto protect shared maps. - RESP2 Protocol: Full support for Redis Serialization Protocol parsing and encoding.
- Automatic Cleanup: Background worker that periodically sweeps expired keys to free memory.
- Lazy Expiration: Expired keys are also cleared on-read to ensure consistency.
- Slow-Client Handling: Incomplete RESP chunks are buffered and re-parsed across reads, so fragmented or delayed client input is handled gracefully without dropping commands.
- Graceful Shutdown: On
SIGINT, all client goroutines and background workers are signalled via a shareddonechannel and waited on withsync.WaitGroupbefore the process exits.
| Command | Usage | Description |
|---|---|---|
PING |
PING |
Check server health (returns PONG) |
ECHO |
ECHO <msg> |
Returns the input message as a bulk string |
SET |
SET <key> <val> [EX s] [PX ms] |
Stores a string with optional TTL |
GET |
GET <key> |
Retrieves a value; handles lazy expiration |
RPUSH |
RPUSH <list> <val...> |
Appends one or more values to a list |
LRANGE |
LRANGE <list> <start> <stop> |
Returns a range of elements from a list |
DEL |
DEL <key> |
Deletes a key and returns 1 if it existed, 0 otherwise |
EXISTS |
EXISTS <key> |
Returns 1 if the key exists and is not expired, 0 otherwise |
SUBSCRIBE |
SUBSCRIBE <channel> |
Subscribes the client to the specified channel |
UNSUBSCRIBE |
UNSUBSCRIBE <channel> |
Removes the client from the specified channel |
PUBLISH |
PUBLISH <channel> <msg> |
Posts a message to the given channel |
.
├── app/
│ ├── main.go # Entry point — signal handling & graceful shutdown
│ ├── server.go # TCP accept loop & goroutine lifecycle management
│ ├── handle-request.go # Per-connection dispatcher & command routing
│ └── command-stream.go # Streaming RESP reader with partial-read buffering
├── commands/ # Individual command logic
│ ├── PING.go # Health-check command
│ ├── ECHO.go # Echo command
│ ├── set-get.go # SET / GET / DEL / EXISTS + in-store expiry sweep
│ ├── RPUSH.go # List append operation
│ ├── LRANGE.go # List range read operation
│ └── publish-subscribe.go # SUBSCRIBE / UNSUBSCRIBE / PUBLISH + dead-conn cleanup
├── background/
│ └── cleanup.go # Periodic background expiration sweep
├── helper/
│ ├── parse-RESP.go # Fast RESP2 array parsing
│ ├── encode-RESP-array.go # RESP encoding for responses
│ ├── filter.go # Bounded channel filter for subscribe streams
│ └── or-chan.go # Recursive `or-channel` fan-in for shutdown signals
├── types/
│ └── types.go # Thread-safe Storage, Store, Lists, StreamDict & CommandType structs
├── testing-scripts/ # Automated validation scripts
│ ├── set-get.sh # Basic String validation
│ ├── push-range.sh # List operation validation
│ ├── expiry.sh # TTL/Expiration logic testing
│ ├── DEL.sh # DEL command validation
│ ├── EXISTS.sh # EXISTS command validation
│ ├── SUBSCRIBE.sh # Pub/Sub subscription test (stays open)
│ ├── PUBLISH.sh # Pub/Sub single message test
│ ├── multi-PUBLISH.sh # Pub/Sub batch message stress test
│ ├── multi-pubsub.sh # Multi-subscriber / multi-publisher scenario test
│ ├── persist-connection.sh # Long-lived connection / pipelining test
│ └── slow-client.sh # Fragmented TCP / slow-client simulation
├── start_program.sh # Convenience build & run script
└── go.mod # Go module definition
go build -o redis-server ./app && ./redis-serverredis-cli ping
redis-cli set mykey "Hello Go" EX 60
redis-cli get mykeyWe include automated shell scripts to validate the implementation:
# Test basic SET/GET functionality
./testing-scripts/set-get.sh
# Test List operations (RPUSH/LRANGE)
./testing-scripts/push-range.sh
# Test Expiration and TTL logic
./testing-scripts/expiry.sh
# Test DEL & EXISTS commands
./testing-scripts/EXISTS.sh
# Test Pub/Sub (Run SUBSCRIBE in one terminal, PUBLISH in another)
./testing-scripts/SUBSCRIBE.sh
./testing-scripts/PUBLISH.sh "Your Message"
# Test batch publishing
./testing-scripts/multi-PUBLISH.sh
# Simulate a slow/fragmented client (sends SET byte-by-byte with 300ms delays)
./testing-scripts/slow-client.sh- Concurrency Control: Shared state (Maps) are protected by
sync.Mutexwithin theStoreandListsstructs to prevent race conditions during concurrent access. - Memory Management: Expiry is managed via
*time.Time. Anilpointer signifies no expiration. - Storage Separation: Strings and Lists are maintained in separate thread-safe maps, mirroring Redis' internal architecture for efficient type-specific operations.
- Pub/Sub Mechanism: Implemented using Go channels for real-time message delivery. Each channel is represented by a
Routecontaining a broadcast channel and a subscriber count. - Slow-Client Resilience: The request handler accumulates partial TCP reads into a temporary buffer and re-parses after each chunk. When the RESP parser detects an incomplete message (declared element count exceeds actually parsed elements), the read loop continues gathering data instead of rejecting the command. This allows the server to correctly handle clients that send data in small or delayed fragments over a single connection.
- Robustness: The RESP parser validates bulk string lengths to prevent corruption from malformed client input.
- Graceful Shutdown: A shared
donechannel is closed onSIGINT. Each client handler spawns a watcher goroutine that selects on bothdone(global shutdown) andclientDone(client disconnect), closing the connection and exiting cleanly in either case. Async.WaitGrouptree (main→Server→ per-client) ensures the process only exits after all goroutines have terminated, preventing memory leaks.