• Learn SRE

SRE Simplicity

Why Simplicity Is a Reliability Strategy

Here’s a paradox: Complex systems are less reliable than simple systems.

You might think the opposite. If you add redundancy, monitoring, failover logic, and self-healing, aren’t you making the system more reliable?

Sometimes yes. But usually, you’re just adding accidental complexity that introduces new failure modes.

Simple = fewer things that can break.


Accidental vs. Essential Complexity

Essential complexity is inherent to the problem you’re solving.

Example: A distributed database system must handle network partitions. This is complex because the problem is complex, not because you’re a bad engineer.

Accidental complexity is introduced by your choices, and it’s often invisible.

Examples of accidental complexity:

  1. Over-engineered architecture. A startup with 10 engineers building like Google with Kubernetes, service meshes, and distributed tracing. You’re solving problems you don’t have yet.
  2. Too many technologies. A company using 15 different databases (PostgreSQL, MongoDB, Cassandra, Redis, DynamoDB, Elasticsearch, etc.) when 2–3 would suffice. Each new tool brings:
    • Another thing to learn
    • Another thing to monitor
    • Another thing that can break
    • Another on-call rotation
  3. Premature optimization. Optimizing for scale you don’t have yet. Building a system for 1 million requests/sec when you get 100 RPS.
  4. Clever, hard-to-understand code. A developer writes a 5-line function that does something clever but nobody else can understand it. When it breaks, it takes days to debug.
  5. Unclear ownership. A system where “everyone owns it, and therefore nobody owns it.” When it breaks, nobody knows who to call.
  6. Inconsistent patterns. One team uses async queues, another uses synchronous RPC, another uses pub/sub. Developers learning each system spend time on the transport mechanism, not the business logic.

The Cost of Accidental Complexity

Complexity is expensive:

Development cost:

Operational cost:

Business cost:


Simplicity Strategies

1. Start Simple, Add Complexity Only When Necessary

Your first deployment strategy: SSH into a server and run git pull && restart-service. Boring? Yes. Reliable? Also yes.

As you grow (10 servers, 100 servers), you automate. But you start simple.

Real example: Instagram started with a monolith on Ubuntu servers. They deployed code by hand. As they scaled, they built infrastructure incrementally, not all at once.

2. Own Your Dependencies

Every external tool (database, message queue, monitoring system) adds complexity:

Ask for each new tool: “Is this solving a problem we actually have, or a problem we think we might have?”

Bad reason to add a tool: “It’s trendy.”

Good reason: “Our current solution is at its limit, and we’ve measured that this new tool solves our bottleneck.”

3. Consistency Over Cleverness

If you have 5 ways to do the same thing, you’ve added 5× the complexity.

Example: Service-to-service communication

Bad:

Good:

Benefit: New engineers learn one pattern, not five. You write one debugging tool instead of five. You have one set of runbooks.

4. Observability Over Instrumentation

You don’t need logs of every variable and function call. You need meaningful logs.

Bad logging:

2024-01-15 10:23:45.123 DEBUG: Entering function get_user_by_id
2024-01-15 10:23:45.124 DEBUG: Checking cache
2024-01-15 10:23:45.125 DEBUG: Cache miss
2024-01-15 10:23:45.126 DEBUG: Querying database
2024-01-15 10:23:45.128 DEBUG: Got result
2024-01-15 10:23:45.129 DEBUG: Returning to caller

This is noise. It doesn’t help you debug production issues.

Good logging:

2024-01-15 10:23:45.123 action=get_user user_id=12345 cache_hit=false db_latency_ms=2 status=success

One line with structured data. You can query it, aggregate it, alert on it.

5. Explicit Over Implicit

If something is happening, someone should be able to understand why by reading the code.

Bad (implicit):

def process_order(order):
    if order.customer.vip:  # If VIP, ??
        order.priority = HIGH
    else:
        order.priority = NORMAL
    # ... do something with priority

Why do VIP customers get HIGH priority? Only the author knows and the next developer guesses.

Good (explicit):

def process_order(order):
    # VIP customers have guaranteed 1-hour fulfillment SLA,
    # so we prioritize their orders to meet the SLA.
    # See: https://wiki/orders/vip-sla
    if order.customer.vip:
        order.priority = HIGH
    else:
        order.priority = NORMAL

Now developers understand the why.


Measuring Simplicity

How do you know if your system is too complex?

Red flags:

  1. Onboarding time. New engineers take >2 weeks to deploy a simple change.
  2. Debugging time. A typical production issue takes >1 hour to root-cause.
  3. Deployment anxiety. Teams are afraid to deploy because “something might break.”
  4. Toil accumulation. More and more time spent on maintenance vs. features.
  5. Tool sprawl. More tools, more dashboards, more alerts (but not better visibility).
  6. Incident frequency. More incidents = more complexity introducing bugs.

Metrics to track:

If these are getting worse, your complexity is hurting you.


Key Takeaways

  1. Simple systems are more reliable. Fewer things to break, easier to understand, easier to debug.
  2. Accidental complexity is the real enemy. Not the essential complexity of the problem.
  3. Start simple, scale complexity only when needed. Don’t over-engineer from day one.
  4. Consistency beats cleverness. One way to do something is better than five ways.
  5. Measure the cost of complexity. If onboarding takes weeks, if incidents are frequent, your system is too complex.