handmadeingo.dev

DDIA Chapter 1: Reliability, Scalability, Maintainability — in Go

Oct 10, 2026 • DDIA Series

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)