Systems Architecture — Cheat Sheet¶
A living quick-reference. Add entries as you study. The goal is a document you'd actually open when making a design decision — not a glossary.
Table of Contents¶
- CAP Theorem
- Distributed Systems Patterns
- Messaging Delivery Semantics
- Microservices Communication Patterns
- Data Consistency Patterns
- Resilience Patterns
- Observability Quick Reference
- Architecture Decision Checklist
CAP Theorem¶
The CAP theorem states that a distributed data store can guarantee at most 2 of 3 properties simultaneously during a network partition:
| Property | Meaning | Practical implication |
|---|---|---|
| C — Consistency | Every read returns the most recent write or an error | All nodes see the same data at the same time |
| A — Availability | Every request receives a non-error response | The system always responds, even with stale data |
| P — Partition Tolerance | System continues operating when messages between nodes are delayed or lost | Required in any real distributed system |
[!IMPORTANT] In a real distributed network, P is non-negotiable — partitions happen. The real choice is: during a partition, do you prioritize C (consistency) or A (availability)?
CAP Placement of Common Databases¶
| Database | CAP Class | Behavior During Partition |
|---|---|---|
| Single-node PostgreSQL | CA | No partition (single node); no tolerance by definition |
| CockroachDB / Spanner | CP | Rejects writes that cannot achieve quorum |
| MongoDB (default write concern) | CP | Primary refuses writes; replica reads may be stale |
| Cassandra | AP | Accepts all writes; reconciles conflicts via last-write-wins |
| DynamoDB (eventually consistent) | AP | Accepts all writes; consistency is eventual |
| DynamoDB (strongly consistent reads) | CP for reads | Strong consistency with higher latency and cost |
| Redis Cluster | AP | Prefers availability; may lose recent writes on failover |
| etcd / ZooKeeper | CP | Rejects requests without a quorum |
PACELC Extension¶
PACELC extends CAP to also consider the latency/consistency trade-off during normal operation (no partition):
Example: DynamoDB is PA/EL — Available during partitions, Low latency (not strongly consistent) during normal operation.
Distributed Systems Patterns¶
Complete Pattern Reference Table¶
| Pattern | Problem | Solution | Trade-offs | When to Use |
|---|---|---|---|---|
| Saga (Choreography) | Distributed transaction without 2PC | Each service publishes events; other services react | Hard to trace; no central view | Loose coupling between services; simple flows |
| Saga (Orchestration) | Distributed transaction without 2PC | Central orchestrator sends commands; tracks state | Orchestrator is a coupling point | Complex flows needing visibility; need central state |
| Outbox Pattern | Dual-write consistency | Write to DB + outbox table in one TX; relay publishes | Polling delay; extra table | Any time you need to publish events from a DB write |
| CQRS | Read/write scaling mismatch | Separate read model from write model | Eventual consistency; more code | High read:write ratio; need custom read projections |
| Event Sourcing | Audit trail; temporal queries | Store events, not state; derive state by replaying | Storage growth; query complexity | Audit requirements; need to replay/project events |
| Circuit Breaker | Cascading failures from slow services | Stop calling a failing service; fail fast | False trips; state machine overhead | Any synchronous service call to a potentially slow dependency |
| Bulkhead | Resource exhaustion from one path | Isolate resource pools per service/path | More infrastructure; configuration overhead | Multi-tenant systems; critical vs non-critical paths |
| Retry with Backoff | Transient network failures | Retry with exponential delay + jitter | Increased latency on failure path | Idempotent operations with transient failure modes |
| Sidecar | Cross-cutting concerns per service | Co-located proxy handles auth/tracing/retries | Latency per hop; more processes | Service mesh adoption; uniform cross-cutting concerns |
| BFF | Multiple clients need different API shapes | One BFF per client type (mobile, web, etc.) | Code duplication; synchronization overhead | Mobile vs web have substantially different data needs |
| Strangler Fig | Incremental monolith migration | New functionality in new services; old code strangled out | Long parallel operation; routing complexity | Migrating a live monolith without big-bang rewrite |
| Sidecar + Service Mesh | Uniform policy enforcement at network layer | All traffic through mesh; policy in control plane | Operational complexity; learning curve | Organizations with 10+ services; need uniform mTLS/observability |
Messaging Delivery Semantics¶
| Semantic | Meaning | Risk | Best For |
|---|---|---|---|
| At-most-once | Message delivered 0 or 1 times | Messages can be lost | Metrics, logs — loss acceptable |
| At-least-once | Message delivered 1+ times | Messages can be duplicated | Most business events with idempotent consumers |
| Exactly-once | Message delivered exactly 1 time | Hardest to achieve; requires idempotency at consumer | Financial transactions; anything where duplicates cause data corruption |
[!NOTE] Exactly-once is often achieved via at-least-once delivery + idempotent consumers rather than true exactly-once transport. "True" exactly-once (Kafka Transactions) is available but adds latency and complexity.
Kafka-Specific Quick Reference¶
Producer → Topic (partitioned) → Consumer Group
│
└── Each partition consumed by one consumer in a group
└── Multiple consumer groups = independent processing
- Offset: position in a partition log; committed by consumer on successful processing
- Consumer Group: enables horizontal scaling; Kafka guarantees each partition to one consumer per group
- Replication Factor: how many broker copies; typical production: 3
acks=all: wait for all in-sync replicas to acknowledge → durability guarantee
Microservices Communication Patterns¶
Sync vs Async Decision Matrix¶
| Situation | Sync (REST/gRPC) | Async (Events/Queues) |
|---|---|---|
| Need immediate response | ✓ | ✗ |
| Temporal decoupling needed | ✗ | ✓ |
| Publisher needs consumer result | ✓ | ✗ (use request-reply) |
| Multiple consumers need same event | ✗ (fan-out is complex) | ✓ (pub/sub) |
| High throughput, bursty load | ✗ | ✓ (queue acts as buffer) |
| Simple CRUD | ✓ | ✗ (overkill) |
| Cross-service transaction | ✗ (tight coupling) | ✓ (Saga) |
gRPC vs REST for Internal Services¶
| Concern | gRPC | REST (JSON) |
|---|---|---|
| Performance | Higher (binary Protobuf, HTTP/2 multiplexing) | Lower (text JSON, HTTP/1.1 or HTTP/2) |
| Schema enforcement | Strong (.proto contracts) |
Weak (OpenAPI optional) |
| Code generation | Built-in | Optional (OpenAPI generators) |
| Browser support | Limited (needs grpc-web) | Native |
| Debugging | Harder (binary protocol) | Easy (curl, Postman) |
| Streaming | Native bidirectional streaming | Limited (SSE, WebSockets separate) |
| Best for | Internal service-to-service | Public APIs, browser clients |
Data Consistency Patterns¶
The Dual-Write Problem¶
Application
│
├──► Database (write succeeds)
│
└──► Message Broker (CRASH — write fails)
→ Event never published
→ Downstream services never notified
→ DATA INCONSISTENCY
Solutions in order of preference: 1. Outbox Pattern — write both in one DB transaction; relay publishes 2. Change Data Capture (CDC) — Debezium reads DB WAL; publishes events 3. Event Sourcing — the event IS the source of truth; no dual-write
Saga vs 2PC Comparison¶
| Property | Saga | Two-Phase Commit (2PC) |
|---|---|---|
| Blocking on failure | No (compensations run asynchronously) | Yes (participants block waiting for coordinator) |
| Consistency model | Eventual (intermediate states visible) | Strong (all-or-nothing) |
| Failure handling | Explicit compensating transactions | Coordinator-managed abort |
| Performance | High (no distributed lock) | Low (distributed lock held during prepare phase) |
| Complexity | High (must design compensations) | Medium (protocol is standard) |
| Coordinator crash | No global block | Can leave participants in uncertain state |
| Use when | Services are independent; eventual consistency acceptable | Strong consistency required; few participants |
Resilience Patterns¶
Circuit Breaker State Machine¶
failures >= threshold
Closed ─────────────────────────► Open
▲ │
│ probe succeeds │ cooldown expires
│ ▼
└─────────────────────────── Half-Open
(sends one probe request)
Retry Backoff Formula¶
# Exponential backoff with jitter
import random
def backoff_delay(attempt: int, base: float = 1.0, max_delay: float = 60.0) -> float:
# exponential: 1s, 2s, 4s, 8s, ...
delay = min(base * (2 ** attempt), max_delay)
# full jitter: random between 0 and delay
return random.uniform(0, delay)
Rule of thumb: Always add jitter to prevent thundering herd (many clients retrying at the exact same moment).
Observability Quick Reference¶
The Three Pillars¶
| Pillar | What It Answers | Tools |
|---|---|---|
| Metrics | Is anything wrong right now? (aggregate numbers) | Prometheus, Datadog, CloudWatch |
| Logs | What happened? (discrete events with context) | ELK Stack, Loki, CloudWatch Logs |
| Traces | Where did the latency come from? (request path across services) | Jaeger, Zipkin, AWS X-Ray, Datadog APM |
Distributed Trace Anatomy¶
TraceID: abc123
└── Span: API-Gateway (200ms total)
├── Span: Order-Service (150ms)
│ ├── Span: DB query (40ms)
│ └── Span: Inventory-Service call (80ms)
│ └── Span: DB query (30ms)
└── Span: Auth check (20ms)
- TraceID: one per request, propagated in HTTP headers (
traceparentin W3C standard) - SpanID: one per operation within a trace
- Parent SpanID: links child spans to parent
OpenTelemetry Trace Propagation Header (W3C)¶
traceparent: 00-{traceId}-{spanId}-{flags}
Example:
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
Architecture Decision Checklist¶
Use this when evaluating any architectural option:
- What problem does this solve? — stated as a problem, not as "we want to use X"
- What are the alternatives? — at least 2 other options considered
- What does this cost? — operational complexity, learning curve, infrastructure, latency
- What do we give up? — the explicit trade-off stated
- Is this reversible? — one-way door or two-way door?
- What is the failure mode? — how does this break, and what happens when it does?
- Who owns it? — which team is responsible for this component?
- How do we know if it's working? — what metrics/alerts prove this is healthy?