Practical System Design: The Quiet Elegance of Boring Architecture

Practical System Design: The Quiet Elegance of Boring Architecture

Table of Contents

Junior engineers often assume that impressive system design requires a complex web of microservices, event streaming through Apache Kafka, CQRS patterns, and distributed consensus mechanisms running across every cluster.

Production reality at large technology companies demonstrates the opposite: Great system design looks completely underwhelming.

If your systems run for months without an outage, you never get paged at 3 AM, and adding a new feature feels surprisingly easy - you are working inside a well-designed architecture.

This post dissects practical system design lessons inspired by Sean Goedecke (Staff Engineer at GitHub), expanded with production experience in Go and distributed backend engineering.


1. Gall’s Law and the Complexity Paradox

In General Systemantics, John Gall formulated a fundamental law of systems:

“A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work.”

In backend engineering, the boundary between Software Design and System Design is clear:

  • Software Design: How you organize code inside-out (variables, functions, interfaces, structs, design patterns).
  • System Design: How you assemble independent components outside-in (application servers, relational databases, in-memory caches, message brokers, proxies).

The most dangerous trap in system design is over-engineering. When engineers see an architecture with twenty microservices, Kafka event buses, and multi-region cache tiers, they often think: “A lot of advanced system design is happening here!”

In reality, extreme complexity usually signals the absence of good design. Every component introduced adds a failure mode, network latency, and operational overhead.


2. Managing State: The Ultimate Physical Boundary

The hardest part of system design is not CPU utilization or memory footprint. It is State.

graph TD
    subgraph "Stateless Layer (Auto-recoverable)"
        App1["App Server 1 (Go)"]
        App2["App Server 2 (Go)"]
        PDF["PDF Generator Service"]
    end

    subgraph "Stateful Layer (High Risk & Manual Care)"
        DB[("`PostgreSQL Primary
        Single Write Owner`")]
        Redis[("`Redis Cache Cluster`")]
    end

    App1 -->|Read / Write API| DB
    App2 -->|Read / Write API| DB
    PDF -->|Stateless Job| App1

Isolating Stateful Components

  • Stateless Component: A service converting HTML into PDF or rendering static assets. If the process crashes or leaks memory, the container orchestrator (Kubernetes or systemd) kills and restarts it in 500ms. Everything recovers automatically.
  • Stateful Component: Relational databases (PostgreSQL, MySQL). If data gets corrupted or an unindexed query locks the primary table, you cannot simply restart a container. Recovery requires manual inspection, backup restoration, and database surgery.

The Golden Rule: Minimize stateful components. Avoid the anti-pattern where multiple services directly connect and write to the same database table.

Assign exactly one service as the owner of that table’s write path. All other services must mutate or query state through APIs or event streams published by that owner service.


3. Database Bottlenecks and Access Patterns

In 90% of web and mobile backends, the database is the primary bottleneck. Your Go application server can easily handle 50,000 requests per second in memory, but a single table lock in PostgreSQL will exhaust goroutine pools and cascade into an outage.

Tip

Composite Index Pattern: When creating multi-column indexes (such as WHERE tenant_id = ? AND status = ? AND created_at > ?), follow the Leftmost Prefix rule. Place columns with the highest cardinality first to narrow down the scanned index pages immediately.

The “Don’t Read Your Writes” Principle

A common anti-pattern is executing an INSERT or UPDATE statement and immediately issuing a separate SELECT query to fetch the updated record back to the client.

Under heavy traffic, this doubles round-trips to the database. Instead:

  1. Use RETURNING * in PostgreSQL to retrieve mutated columns in the same write statement.
  2. Reuse the in-memory domain entity already constructed before the write occurred.
sequenceDiagram
    autonumber
    actor Client
    participant App as Go App Server
    participant DB as PostgreSQL DB

    Note over Client,DB: Anti-Pattern (2 Round-trips)
    Client->>App: POST /orders
    App->>DB: INSERT INTO orders ...
    DB-->>App: OK (id=102)
    App->>DB: SELECT * FROM orders WHERE id=102
    DB-->>App: Order Object
    App-->>Client: 200 OK (Order)

    Note over Client,DB: Optimal Pattern (1 Round-trip)
    Client->>App: POST /orders
    App->>DB: INSERT INTO orders ... RETURNING *
    DB-->>App: Order Object
    App-->>Client: 200 OK (Order)

4. Hot Paths vs. Cold Paths: Resource Allocation

Large applications contain hundreds of endpoints, but their operational profiles differ drastically:

  • Hot Path: Payment processing, inventory deduction, telemetry ingestion. These endpoints handle 95% of traffic volume and directly determine business survival.
  • Cold Path: Avatar uploads, monthly invoice generation, shipping address updates. These account for 5% of traffic.
graph LR
    subgraph "Hot Path (Minimal Overhead)"
        UserReq["User Telemetry Event"] -->|Ingest API| Stream["Redis Streams / Queue"]
        Stream -->|Batch Worker| Influx["TimeSeries DB"]
    end

    subgraph "Cold Path (Standard CRUD)"
        AdminReq["Admin Settings Request"] -->|HTTP API| App["App Server"]
        App -->|Direct Query| RDBMS[("`PostgreSQL DB`")]
    end

Common Mistake: Spending days optimizing a cold path query while allowing the hot path to execute unindexed queries in a loop.

Allocate 80% of design effort to making hot paths dead simple, resilient, and decoupled from slow external dependencies.


5. Failure Modes and Production Resilience

In production, the question is never whether components will fail, but how they fail.

5.1 Fail-Open vs. Fail-Closed

Architectural decisions require deliberate failure defaults:

StrategyBehavior When Dependency (e.g., Redis) FailsTypical Use Cases
Fail-OpenBypass check, allow request throughRate Limiting, Analytics, Feature Flag evaluation
Fail-ClosedBlock request immediately with an errorAuthentication, Authorization, Payment Settlement

Warning

Example: If the Redis instance managing rate limits becomes unreachable, the rate limiter should Fail-Open to keep the core product accessible. If the authentication service fails, it must Fail-Closed to prevent unauthorized access.

5.2 Idempotency Keys in Go

When clients send payment or order creation requests over unstable networks, retries can duplicate transactions. Idempotency keys solve this at the transport boundary.

Here is a clean Go middleware implementation using Redis:

  • idempotency_middleware.go
  • fail_open_ratelimit.go
package middleware

import (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/redis/go-redis/v9"
)

type bodyLogWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}

func IdempotencyMiddleware(rdb *redis.Client) gin.HandlerFunc {
	return func(c *gin.Context) {
		key := c.GetHeader("X-Idempotency-Key")
		if key == "" {
			c.Next()
			return
		}

		ctx := c.Request.Context()
		redisKey := fmt.Sprintf("idempotency:%s", key)

		// 1. Attempt atomic lock acquisition
		success, err := rdb.SetNX(ctx, redisKey, "PROCESSING", 30*time.Second).Result()
		if err != nil {
			// Fail open on Redis network error if acceptable, or return 500
			c.Next()
			return
		}

		if !success {
			// Key exists: check current status
			val, _ := rdb.Get(ctx, redisKey).Result()
			if val == "PROCESSING" {
				c.JSON(http.StatusConflict, gin.H{"error": "Request is currently processing"})
				c.Abort()
				return
			}
			// Return cached response
			c.Data(http.StatusOK, "application/json", []byte(val))
			c.Abort()
			return
		}

		// Intercept response body
		blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
		c.Writer = blw

		c.Next()

		// Cache successful or business responses (< 500)
		if c.Writer.Status() < 500 {
			rdb.Set(context.Background(), redisKey, blw.body.String(), 24*time.Hour)
		} else {
			rdb.Del(context.Background(), redisKey)
		}
	}
}
package ratelimit

import (
	"context"
	"log/slog"
	"time"

	"github.com/redis/go-redis/v9"
)

type RateLimiter struct {
	rdb    *redis.Client
	limit  int
	window time.Duration
}

// Allow evaluates if a request should pass under Fail-Open semantics
func (rl *RateLimiter) Allow(ctx context.Context, userID string) bool {
	key := "ratelimit:" + userID
	
	// Keep timeout short to avoid stalling the request pipeline
	subCtx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
	defer cancel()

	count, err := rl.rdb.Incr(subCtx, key).Result()
	if err != nil {
		// Redis unreachable or timeout -> Fail-Open: Allow request
		slog.Warn("Rate limiter Redis failure, failing OPEN", "error", err, "user_id", userID)
		return true
	}

	if count == 1 {
		rl.rdb.Expire(subCtx, key, rl.window)
	}

	return count <= int64(rl.limit)
}

6. Unhappy Path Logging and p95/p99 Observability

Engineers frequently log on unexpected exceptions but stay silent when returning 422 Unprocessable Entity responses or rejecting payloads due to business validation.

graph TD
    Req[Incoming Request] --> Validate{Valid Payload?}
    Validate -->|No| LogUnhappy["Aggressive Log: Record exact condition failed"]
    LogUnhappy --> Resp422[Return 422 Unprocessable]
    Validate -->|Yes| Process[Process Core Logic]

Why Unhappy Path Logging Matters

When an enterprise customer reports that their requests are failing with 422 errors, you need to know immediately which condition among multiple validation checks was triggered.

Logging unhappy paths with structured metadata (Tenant ID, User ID, validation reason) cuts debugging time from hours to seconds.

The Average Latency Illusion

An endpoint reporting 30ms average latency looks healthy on dashboards. However, looking at tail latency paints a different picture:

  • Average: 30ms
  • p95: 180ms
  • p99: 2,400ms (2.4 seconds)

The 1% of users experiencing p99 latency are often your highest-tier customers with the largest data sets and highest transaction volumes. Relying on average metrics masks severe degradation for your most valuable users.


7. Killswitches and Feature Flags

As systems expand, runtime control becomes essential. Reliable architectures incorporate killswitches into background jobs and external integrations.

When running asynchronous data synchronizers or webhooks:

  • Avoid relying on a full CI/CD deployment cycle to stop a rogue background job during an incident.
  • Check a lightweight feature flag or Redis key at the start of each execution loop:
func (w *DataSyncWorker) Process(ctx context.Context) {
    if !w.featureFlags.IsEnabled("enable_salesforce_sync_worker") {
        slog.Info("Salesforce sync worker paused via killswitch")
        return
    }
    // Execute synchronization logic
}

Toggling a feature flag takes milliseconds, halting downstream load before database connection pools collapse.


8. Summary: The Power of Boring Architecture

Effective system design is not about adopting every emerging tool or constructing convoluted diagrams for conference presentations.

It is similar to plumbing: if you try to make it exciting, you will end up covered in waste.

The most resilient architectures rely on simple, proven foundations:

  1. Maintain stateless application tiers and isolate database write ownership.
  2. Optimize hot paths aggressively while keeping cold paths simple.
  3. Explicitly define Fail-Open vs. Fail-Closed policies.
  4. Use idempotency keys for write operations and runtime killswitches for automation.
  5. Track p95/p99 tail latency rather than relying on averages.

Build systems that look boring on paper and run reliably in production.

Share :

Related Posts

Graceful Shutdown: Benefits and Reasons to Have It

Graceful Shutdown: Benefits and Reasons to Have It

There was a time when our team deployed a new version at 11 pm. After running kubectl rollout restart, 30 seconds later, PagerDuty alerted: 200 502 errors in 5 seconds. Customers were placing orders when they encountered a timeout. We quickly rolled back, then checked the logs - it turned out that the old pod was terminated with SIGTERM, the HTTP server shut down immediately, and 50 requests being processed were cut off entirely.

Read More
Agent = Model + Harness: Do not put an F1 engine into a brakeless bus

Agent = Model + Harness: Do not put an F1 engine into a brakeless bus

At the end of last year, I created a bot that ran in the background to automatically read menus from partner restaurants sent in PDF/Excel format, then parsed them into a Go struct to load into the Menu Service database.

Read More
When to use cache and when not to

When to use cache and when not to

There is a production bug that I still remember vividly: user A cancels an order, but the app still displays “delivering” for the next 10 minutes. Support receives 30 tickets in one morning. The reason: cache TTL is 10 minutes, but no one invalidates it when the order status changes.

Read More