DDIA Chapter 1: Reliability, Scalability, Maintainability — in Go
Source: Designing Data-Intensive Applications, Chapter 1 by Martin Kleppmann. This post implements the concepts described in the book in idiomatic Go.
1. Reliability — The book says: systems should continue to work correctly even when faults occur.
DDIA lists three fault types: hardware faults, software errors, human errors. In Go, reliability means bounded concurrency, idempotency, and retry.
Go implementation — Fault-tolerant worker with retry (DDIA §1.1):
// DDIA: "Retry is a common way to handle transient faults"
func DoWithRetry(ctx context.Context, op func() error) error {
var lastErr error
backoff := 100 * time.Millisecond
for attempt := 0; attempt < 5; attempt++ {
if err := op(); err == nil {
return nil
} else {
lastErr = err
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(backoff):
backoff *= 2
}
}
return lastErr
}
Idempotency — DDIA stresses idempotent operations for safe retry:
// Store processed IDs to guarantee exactly-once effect
type IdempotentStore struct {
mu sync.Mutex
seen map[string]struct{}
}
func (s *IdempotentStore) Process(id string, fn func() error) error {
s.mu.Lock()
if _, ok := s.seen[id]; ok {
s.mu.Unlock()
return nil // already processed
}
s.mu.Unlock()
if err := fn(); err != nil {
return err
}
s.mu.Lock()
s.seen[id] = struct{}{}
s.mu.Unlock()
return nil
}
2. Scalability — The book says: scalability is about handling load growth.
DDIA defines load parameters, latency percentiles (p50, p95, p99), and throughput. In Go, we measure this.
Go implementation — Measuring scalability as DDIA describes:
// DDIA: "When you describe load, also measure response time percentiles"
func Measure(handler func(context.Context) error) (p50, p99 time.Duration) {
var latencies []time.Duration
for i := 0; i < 1000; i++ {
start := time.Now()
_ = handler(context.Background())
latencies = append(latencies, time.Since(start))
}
sort.Slice(latencies, func(i, j int) bool { return latencies[i] < latencies[j] })
return latencies[len(latencies)/2], latencies[int(float64(len(latencies))*0.99)]
}
Bounded concurrency — DDIA: uncontrolled load causes cascading failures:
// Use worker pool with bounded concurrency, not unbounded goroutines
jobs := make(chan Job, 1000)
var wg sync.WaitGroup
for w := 0; w < 100; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := range jobs {
_ = DoWithRetry(context.Background(), j.Do)
}
}()
}
3. Maintainability — DDIA: operability, simplicity, evolvability.
In Go, this translates to small interfaces, structured logging, and explicit configuration.
// Operability: expose metrics and structured logs
type Processor interface {
Process(ctx context.Context, id string) error
}
// Simplicity: no hidden globals, dependencies injected
func NewProcessor(store *IdempotentStore, logger *slog.Logger) Processor {
return &processor{store: store, log: logger}
}
Next: Chapter 2 — Data Models & Query Languages in Go (relational vs document model as per DDIA §2)