Skip to content

Latest commit

 

History

History
136 lines (118 loc) · 6.58 KB

File metadata and controls

136 lines (118 loc) · 6.58 KB

Architecture

High-Level Component Diagram

┌─────────────────────────────────────────────────────────────────┐
│                         redis-lite                              │
│                                                                 │
│  ┌────────────┐     ┌──────────────┐    ┌───────────────────┐  │
│  │  TCP Layer │────▶│  Connection  │───▶│  Command Parser   │  │
│  │  server.go │     │  Handler     │    │  (RESP v2 + Inline│  │
│  │  :6380     │     │  handler.go  │    │  parser.go)       │  │
│  └────────────┘     └──────────────┘    └────────┬──────────┘  │
│         │                                         │             │
│         │ goroutine per conn                      ▼             │
│         │                              ┌───────────────────┐    │
│         │                              │  Dispatcher       │    │
│         │                              │  dispatcher.go    │    │
│         │                              └──────┬────────────┘    │
│         │                                     │                 │
│         │               ┌─────────────────────┼──────────┐      │
│         │               ▼                     ▼          ▼      │
│         │    ┌──────────────────┐  ┌────────────────┐  ┌──────┐ │
│         │    │  Sharded Store   │  │  AOF Writer    │  │Metric│ │
│         │    │  (256 shards)    │  │  persistence/  │  │  s   │ │
│         │    │  store.go        │  │  aof.go        │  │      │ │
│         │    └──────┬───────────┘  └────────────────┘  └──────┘ │
│         │           │                                            │
│         │    ┌──────▼──────┐                                     │
│         │    │ Expiry       │                                     │
│         │    │ Worker       │                                     │
│         │    │ expiry/      │                                     │
│         └────┤ worker.go    │                                     │
│              └─────────────┘                                     │
└─────────────────────────────────────────────────────────────────┘

Sharded Locking Model

Key → FNV-1a hash → shard index (0–255)
                          │
                 ┌────────▼─────────┐
                 │  shard.mu        │  RWMutex
                 │  shard.data      │  map[string]*Entry
                 └──────────────────┘

Write ops (SET, DEL, EXPIRE, INCR):  shard.mu.Lock()
Read ops  (GET, EXISTS, TTL):        shard.mu.RLock()

Result: concurrent reads on different keys never block each other.
Concurrent writes only contend within the same shard (~1/256 probability).

Request Lifecycle

1. Client connects via TCP
2. Server.acceptLoop() → go handleConn(conn)
3. handleConn: parser.ReadCommand() — blocks on socket read
4. RESP or inline bytes decoded → Command{Name, Args}
5. dispatcher.Dispatch(cmd, writer)
   a. metrics.Record starts
   b. handler function called
   c. store operation executed (shard lock acquired/released)
   d. AOF.Write(tokens) enqueued (non-blocking channel)
   e. writer.WriteXxx() called
   f. metrics.Record ends
6. writer.Flush() — single syscall per command
7. Loop back to step 3

AOF Data Flow

Command arrives
     │
     ▼
Dispatcher (hot path)
     │  non-blocking channel send
     ▼
AOF.ch (buffered, 4096 entries)
     │
     ▼ (background goroutine)
aof.writeLoop()
     │  batch drain
     ▼
bufio.Writer (64 KiB buffer)
     │  Flush()
     ▼
OS kernel buffer
     │  Sync()
     ▼
Disk

Concurrency Model

Layer Mechanism Rationale
TCP accept Goroutine per conn M:N scheduling; cheap goroutines
Store reads RWMutex per shard Parallel reads, serialised writes per shard
Store writes RWMutex per shard 256-way sharding minimises contention
INCR counter shard.mu.Lock + CAS Single shard owns key; no global lock needed
Metrics atomic.Int64 Lock-free counters on the hot path
AOF writes Buffered channel Decouples disk I/O from request latency
Expiry sweep Dedicated goroutine Amortises deletion cost across 100ms windows

Failure Recovery Model

Startup sequence:
  1. Open AOF file (read-only)
  2. Decode RESP arrays one at a time
  3. For each: call dispatcher.Replay() → store operation (no re-persist)
  4. Partial/corrupt final record: silently discarded (io.EOF or parse error)
  5. Switch AOF file to APPEND mode for new writes

Crash safety:
  - AOF is flushed + synced after every batch
  - A crash between fsync and the next command means at most one
    command is lost (analogous to Redis appendfsync everysec)
  - File is opened with O_APPEND — no seek needed, atomic appends on Linux

Performance Benchmarks (reference, Apple M1, Go 1.22)

Benchmark ops/sec allocs/op
BenchmarkSetSerial ~8,000,000 1
BenchmarkGetSerial ~20,000,000 0
BenchmarkSetParallel ~30,000,000 1
BenchmarkGetParallel ~80,000,000 0
BenchmarkMixedReadWrite ~60,000,000 0

Run go test -bench=. -benchmem ./bench/ to reproduce.