A Redis-inspired, in-memory key-value store written in Go.
Supports concurrent clients, basic commands (SET, GET, DEL, EXPIRE, TTL), Pub/Sub, transactions (MULTI/EXEC/DISCARD), and durable persistence via RDB-style snapshots and an Append-Only File (AOF).
- TCP server handling multiple clients concurrently via goroutines
- Basic commands:
SET key valueGET keyDEL keyEXPIRE key secondsTTL key
- Pub/Sub:
PUBLISH channel messageSUBSCRIBE channelUNSUBSCRIBE channel
- Transactions:
MULTI,EXEC,DISCARD- Queued command execution
- Persistence:
- RDB-style snapshots every configurable interval (default 60 s)
- AOF (Append-Only File) logging of every write
- Recovery on startup: loads AOF (preferred) or snapshot
redis-go/
├── cmd/
│ └── redis-server/
│ └── main.go # Entrypoint: starts server
├── internal/
│ ├── store/
│ │ ├── store.go # Core in-memory map, Set/Get/Del
│ │ ├── expiration.go # EXPIRE, TTL, background cleaner
│ │ ├── pubsub.go # PUBLISH/SUBSCRIBE/UNSUBSCRIBE
│ │ ├── snapshot.go # RDB snapshot save/load + StartSnapshotting
│ │ └── aof.go # AOF replay on startup
│ └── protocol/
│ └── handler.go # TCP cmd parser & dispatch, transactions
├── pkg/
│ └── util/
│ └── escape.go # Newline escape/unescape for AOF
├── examples/
│ └── telnet_example.sh # Example interaction via telnet
├── go.mod
└── README.md- Go 1.18+
- (Optional) Docker & Docker CLI for containerized deployment
- A TCP client for testing:
telnet,nc(netcat), or similar
Build and run with Docker:
docker-compose up -d
# Exec into your running container:
docker exec -it redis-go sh
# Now you can connect
nc localhost 6379SET foo bar
OK
GET foo
bar
EXPIRE foo 5
(integer) 1
TTL foo
(integer) 5
# wait >5 seconds
GET foo
(nil)SET count 100
OK
GET count
100
DEL count
(integer) 1
DEL count
(integer) 0
GET count
(nil)MULTI
OK
SET a 1
QUEUED
SET b 2
QUEUED
GET a
QUEUED
EXEC
OK
OK
1Open two terminal sessions. In the first, subscribe to a channel:
# Terminal 1:
$ nc localhost 6379
SUBSCRIBE news
Subscribed to newsNow in the second terminal, publish a message to that channel:
# Terminal 2:
$ nc localhost 6379
PUBLISH news Hello World
(integer) 1The publisher gets (integer) 1 indicating one subscriber received the message. Over in Terminal 1, the subscriber sees:
message news Hello WorldThe subscriber can subscribe to multiple channels (one at a time with our implementation) and will receive messages for each. To stop receiving, unsubscribe:
# Terminal 1:
UNSUBSCRIBE news
Unsubscribed from newsAfter this, further publishes to "news" will not reach Terminal 1.
If you run some commands, then stop the server and restart it, the data should persist:
- On first run, do e.g. SET persistent 42.
- Stop the server (Ctrl+C).
- Restart the server (go run main.go again).
- Then GET persistent should still return 42 thanks to the AOF log (or snapshot) being loaded on startup.
You should see an appendonly.aof file growing with each write command, and a dump.rdb (snapshot) being updated every 60 seconds. This confirms that data is being persisted to disk. On restart, the code loaded the AOF and restored the state.
- Fork it
- Create feature branch (git checkout -b feature/YourFeature)
- Commit your changes (git commit -m "Add YourFeature")
- Push (git push origin feature/YourFeature)
- Open a Pull Request