Get started with memQ
memQ is a blazing-fast, Redis-inspired in-memory key-value store built from scratch in Go. It features LRU eviction, TTL, snapshot persistence, TCP server with RESP protocol, and an HTTP REST API.
Installation
Clone, configure, and run. Three steps to your own in-memory store.
Clone the repository
Start by cloning the memQ repository from GitHub.
git clone https://github.com/satwaraa/memQ.git
cd memQConfigure environment
Create a .env file in the project root with your settings.
CAPACITY=1000
TCP_PORT=6379
HTTP_PORT=8080Run the server
Start memQ with a single command. This launches the CLI, TCP server, and HTTP API simultaneously.
go run cmd/kvstore/main.goYou'll see the interactive CLI prompt: memQ>
Quick Start
Once memQ is running, you can interact with it three ways.
CLI
Interactive REPL with 15+ commands
TCP / netcat
Redis-compatible RESP protocol on port 6379
HTTP API
JSON REST endpoints on port 8080
memQ> SET user:1 "Alice"
OK
memQ> GET user:1
"Alice"
memQ> SETEX session:abc 3600 "token"
OK (expires in 3600s)
memQ> STATS
Keys: 2
Capacity: 1000
Hits: 1
Misses: 0
Evictions: 0Configuration
memQ reads configuration from a .env file or environment variables.
| Variable | Required | Default | Description |
|---|---|---|---|
| CAPACITY | Yes | — | Maximum number of keys in the store |
| TCP_PORT | Yes | — | Port for TCP server (RESP protocol) |
| HTTP_PORT | No | 8080 | Port for HTTP REST API |
| Memory | No* | — | Memory limit (alternative to CAPACITY) |
* Either CAPACITY or Memory must be provided.
CLI Reference
Full-featured interactive REPL with 15+ commands.
Key Operations
SET <key> <value> Set a key-value pair
GET <key> Get value by key
DELETE <key> Delete a key
EXISTS <key> Check if key exists (1/0)
KEYS List all keys
CLEAR Remove all keysTTL Commands
SETEX <key> <sec> <value> Set with expiration (seconds)
TTL <key> Get remaining time to live
EXPIRE <key> <seconds> Set expiration on existing keymemQ> SETEX cache:token 300 "abc123"
OK (expires in 300s)
memQ> TTL cache:token
285 (seconds)
memQ> EXPIRE cache:token 600
OK (expires in 600s)Persistence
SAVE Save snapshot to disk
LOAD Load snapshot from diskSnapshots are saved as JSON to memQ_data.json. Auto-save runs every minute. Data is also saved on graceful shutdown (Ctrl+C).
Utility
STATS Show hit/miss/eviction statistics
HELP Show all available commands
QUIT Exit CLI (auto-saves)TCP Server
Redis-compatible TCP server using RESP serialization. Connect with netcat or any Redis client library.
Connecting
# Via netcat
nc localhost 6379
# Send commands
SET name Alice
+OK
GET name
$5
Alice
PING
+PONGSupported Commands
All CLI commands are available over TCP, plus PING.
PING, SET, GET, DEL, EXISTS, SETEX, TTL,
EXPIRE, KEYS, SAVE, LOAD, CLEAR, STATS,
HELP, QUITHTTP REST API
Full JSON REST API on port 8080 (configurable via HTTP_PORT).
Key Endpoints
/keys/{key}Set a key-value pair. Optional TTL in seconds.
curl -X POST localhost:8080/keys/user:1 \
-H "Content-Type: application/json" \
-d '{"value": "Alice"}'
# With TTL
curl -X POST localhost:8080/keys/session:abc \
-H "Content-Type: application/json" \
-d '{"value": "token123", "ttl": 3600}'/keys/{key}Retrieve a value by key.
curl localhost:8080/keys/user:1
# Response: {"key": "user:1", "value": "Alice"}/keys/{key}Delete a key.
curl -X DELETE localhost:8080/keys/user:1
# Response: {"status": "OK", "key": "user:1"}/keysList all keys in the store.
curl localhost:8080/keys
# Response: {"keys": ["user:1", "session:abc"], "count": 2}Admin Endpoints
/statsGet store statistics.
{
"keys": 42,
"capacity": 1000,
"hits": 156,
"misses": 3,
"evictions": 0
}/saveTrigger manual snapshot to disk.
/loadLoad snapshot from disk.
Architecture
memQ is built with zero external dependencies for the core engine. All data structures are implemented from scratch.
Project Structure
memQ/
├── cmd/kvstore/main.go # Entry point
├── env/env.go # Configuration loader
├── internal/
│ ├── cli/cli.go # Interactive REPL
│ ├── protocol/resp.go # RESP serialization
│ ├── server/
│ │ ├── server.go # TCP server
│ │ └── http_server.go # HTTP REST API
│ └── store/
│ ├── store.go # Core store + LRU
│ ├── lru.go # Doubly-linked list
│ ├── ttl.go # TTL / expiry logic
│ └── persistence.go # Snapshot save/load
└── tests/ # Integration testsLRU Eviction
When the store reaches capacity, the Least Recently Used key is automatically evicted. The implementation uses a doubly-linked list + hashmap for O(1) operations.
┌─────────────────────────────────────────────────┐
│ HashMap: map[string]*Node → O(1) lookup │
│ │
│ LRU List (doubly-linked): │
│ HEAD ←→ Node ←→ Node ←→ ... ←→ TAIL │
│ (MRU) (LRU) │
│ │
│ On GET/SET: move node to HEAD │
│ On capacity full: evict TAIL │
└─────────────────────────────────────────────────┘Thread safety via sync.RWMutex — concurrent reads, exclusive writes.
TTL & Expiry
Keys support optional Time-To-Live. A background goroutine cleans expired keys every minute.
// Each Node stores:
type Node struct {
key string
value string
expireAt *time.Time // nil = no expiration
prev *Node
next *Node
}
// Lazy expiry on GET + background cleaner
// store.StartTTLCleaner(1 * time.Minute)Persistence
JSON snapshots preserve data across restarts. The LRU order is maintained during save/load.
{
"version": "1.0",
"capacity": 1000,
"entries": [
{
"key": "user:1",
"value": "Alice"
},
{
"key": "session:abc",
"value": "token123",
"expire_at": "2026-02-26T00:00:00Z"
}
]
}• Auto-save: Every 1 minute via background goroutine
• Graceful shutdown: Saves on Ctrl+C (SIGINT/SIGTERM)
• Load on startup: Automatically loads from snapshot file
• TTL preservation: Expired keys are skipped during load
Built with Go · View on GitHub