So You Think You Know What Distributed Software Architecture Is?
You don't.
Not until you've watched a production system melt down at 3 AM because a single microservice decided to take a nap. Not until you've explained to your CEO why the checkout pipeline went down — and no, it wasn't a server crash, it was design.
I'm Nishaant Dixit. I've been building distributed systems since 2018 at SIVARO. We process 200,000 events per second in production. I've made mistakes. Expensive ones.
Let me show you what I wish someone had shown me.
What is distributed software architecture? It's the practice of building a system as a collection of independent components that communicate over a network — but that definition is dangerously incomplete. The real answer is about failure, coordination, and trade-offs nobody talks about.
Why Most Definitions of "What Is Distributed Software Architecture?" Are Wrong
Ask ten engineers what distributed software architecture is. You'll get eleven answers.
The textbook says: a system where components located on networked computers communicate by passing messages. Technically correct. Practically useless.
Here's what it actually means:
- You design for failure. Every single component will fail. Discs die. Networks partition. Memory corrupts. The question isn't if — it's when.
- You give up on simple consistency. Two servers reading the same record at the same instant? They might see different values. Welcome to eventual consistency.
- You pay latency tax. Every network call adds 0.1-5ms. Do that 50 times in a request chain? Suddenly your "fast" API takes 200ms.
In 2021 at SIVARO, we shipped a "distributed" system that was really a monolith with network calls between modules. It worked... until one module saturated the network card. The entire system fell over.
That's not distributed architecture. That's a monolith with extra steps.
The Four Pillars (That Nobody Teaches You)
1. Stateless is a Lie
Every distributed system claims to be stateless. Bullshit.
Sessions exist. Caches exist. Database connections exist. The real skill is managing the state you can't avoid.
Consider a user authentication service. "Stateless" JWT tokens sound great — until you need to revoke a session. Then you need a blacklist. Which is state. So you build a Redis cache. Which can go down. So you replicate it. Now you've got a distributed state system.
Pattern that works: Put volatile state in Redis. Put persistent state in PostgreSQL. Never mix them. We learned this after a Redis crash wiped 300,000 active sessions in 2022.
python
# Bad: mixing fast state with critical state
session = redis.get(user_token)
if not session:
session = db.get_session(user_token) # Don't fallback to DB on cache miss
python
# Better: treat Redis as a performance layer, never single source of truth
session = db.get_session(user_token)
redis.setex(f"session:{user_token}", 3600, serialize(session))
2. Network Partitions Aren't Rare — They're Guaranteed
Amazon said in 2022 that AWS experienced 39 availability zone events in a single year. Each one is a potential partition.
Most people think: "My cloud provider handles this."
They don't. They handle infrastructure. Your application logic still needs to decide: do you serve stale data (AP) or reject requests (CP)?
We chose AP for our product catalog. A user seeing yesterday's prices for 30 seconds is fine. A user getting an error page isn't.
javascript
// Pseudocode for partition-tolerant catalog read
async function getProduct(id) {
try {
const primary = await readFromPrimaryRegion(id);
return primary;
} catch (networkError) {
// Partition detected — serve cached version
const cached = await readFromLocalCache(id);
if (cached) {
logger.warn(`Serving stale data for product ${id}`);
return { ...cached, stale: true };
}
// No cache either — fail fast, don't cascade
throw new ServiceUnavailableError("Catalog unavailable");
}
}
3. Your Database Choice Determines Your Architecture
Pick your DB first. Not your framework. Not your message queue. Your database.
PostgreSQL vs CockroachDB vs DynamoDB — each forces different trade-offs.
We ran a benchmark in 2023. 100 concurrent writers on a single PostgreSQL instance: 4,200 writes/sec. Same workload on a 3-node CockroachDB cluster: 1,100 writes/sec. But CockroachDB survives a node failure without downtime. PostgreSQL needs failover orchestration.
Most people think "I'll use PostgreSQL and figure out scaling later." That works until you hit 50,000 users. Then you're doing midnight schema migrations and cursing your past self.
My rule: If your reads outnumber writes 20:1 or more, use PostgreSQL with read replicas. If writes dominate or you need multi-region, go CockroachDB or Spanner.
4. Distributed Tracing Is Your Only Hope
You cannot debug a distributed system with logs alone. I tried. For six months. It was hell.
A user reports "checkout takes 30 seconds." You check logs: each service reports 200ms. Math says 200ms * 5 services = 1 second. Reality is 30 seconds. Why?
Because you're missing queue depth. Because two services are retrying silently. Because a slow service makes others wait — but those others still log their own latency, not the wait time.
We switched to OpenTelemetry in 2022. First week, we found a microservice making 47 redundant database calls per request. The developer who wrote it didn't know. The logs were "clean."
yaml
# OpenTelemetry collector config (simplified)
receivers:
otlp:
protocols:
grpc:
processors:
batch:
memory_limiter:
check_interval: 1s
limit_mib: 500
exporters:
jaeger:
endpoint: jaeger-collector:14250
prometheus:
endpoint: "0.0.0.0:8889"
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, memory_limiter]
exporters: [jaeger]
The Real Question: Monolith vs Microservices
Let me be blunt: 90% of companies shouldn't use microservices.
When NOT to distribute:
- Your team has fewer than 15 engineers
- Your product fits in a single database with under 100 tables
- Your traffic doesn't need to scale beyond one beefy server (e.g., 64GB RAM, 16 cores)
- You're still figuring out product-market fit
When to distribute:
- Multiple teams need to deploy independently
- You need different scaling characteristics per component (e.g., video transcoding vs auth)
- Regulatory requirements demand separate data domains
At SIVARO, we started as a monolith in 2018. Deliberately. We only extracted the first service in 2020 — the payment processor. And only because PCI compliance forced it.
Most people think microservices are about scaling. They're wrong. Microservices are about organizational scaling. Conway's law holds: you ship your org chart.
Patterns That Actually Work in Production
The Outbox Pattern
Distributed systems fail at exactly the worst moment: between writing to the database and sending a message.
We used to write to PostgreSQL, then publish to Kafka. If Kafka was down, the write was lost. Users got charged but never got confirmation emails. 400 angry customers in one afternoon.
The outbox pattern fixed this: write both the entity and the outbox message in a single database transaction. A separate process reads the outbox and publishes to Kafka.
sql
BEGIN;
INSERT INTO orders (id, user_id, total) VALUES ('123', 'abc', 59.99);
INSERT INTO outbox (event_type, payload, created_at)
VALUES ('order.created', '{"order_id":"123","amount":59.99}', NOW());
COMMIT;
Then a background worker:
python
def process_outbox():
for row in db.query("SELECT * FROM outbox WHERE processed=false LIMIT 100"):
try:
kafka.produce(row.event_type, row.payload)
db.execute("UPDATE outbox SET processed=true WHERE id=%s", row.id)
except KafkaError:
break # Will retry on next poll
The Circuit Breaker
Don't retry forever. Don't cascade failures.
We had a downstream service that went down for 14 minutes in 2021. Our caller kept retrying — 30,000 times per minute. The retries saturated the connection pool. Services upstream of us started failing too. A 14-minute outage turned into a 3-hour cascading disaster.
Circuit breakers prevent this. After 5 failures in 60 seconds, stop calling the downstream for 30 seconds. Let it recover.
python
from pybreaker import CircuitBreaker
inventory_breaker = CircuitBreaker(fail_max=5, reset_timeout=30)
def get_inventory(product_id):
try:
return inventory_breaker.call(inventory_service.check, product_id)
except CircuitBreakerError:
# Serve from cache or return default
return {"available": True, "count": -1, "fallback": True}
What Is Distributed Software Architecture? (The Actual Answer)
It's the discipline of designing systems where failure is the expected case.
It's understanding that a network call is a potential 5-second timeout — and designing so that timeout doesn't kill your entire user experience.
It's knowing that two servers can't agree on the current time (clock skew is real, up to 250ms between machines in the same rack).
It's accepting that "exactly once" delivery is a myth. You get "at least once" or "at most once." Choose wisely.
Here's the uncomfortable truth: A well-designed monolith on a single server will outperform a distributed system for 95% of workloads. You only distribute when you have to.
FAQ
What is distributed software architecture in simple terms?
It's building software as multiple independent parts that talk over a network. Like a team of specialists instead of one person doing everything. Each part can fail, scale, and deploy separately.
What's the difference between microservices and distributed architecture?
Microservices are one style of distributed architecture. There's also event-driven architecture, space-based architecture, and peer-to-peer. Microservices are the most popular because they map well to team boundaries.
When should I NOT use distributed architecture?
When your entire application fits on one server. When your team is small. When your traffic is predictable. When you're still validating product-market fit. Starting with a monolith and extracting services later is cheaper and faster.
How do you test distributed systems?
Integration tests with real network latency (use Toxiproxy to inject failures). Chaos engineering — randomly kill services in staging. Formal verification of critical consensus protocols. And distributed tracing in production — you can't debug what you can't see.
Is Kubernetes necessary for distributed systems?
No. Kubernetes handles deployment and scaling, but you can build distributed systems with simple daemon processes, systemd, and a load balancer. Kubernetes adds complexity. Use it when you need it.
What's the hardest part of distributed software architecture?
Observability. You can't SSH into 50 containers to read logs. You need structured logging, metrics, and traces working together. Most teams get two of three. All three is rare but necessary.
How do you handle database migrations in distributed systems?
Forward-compatible schema changes only. Add columns with defaults. Never rename — add a new column, migrate reads, then remove the old one. Use feature flags to control migration rollout. Expect downtime if you violate these.
What's your single piece of advice for someone starting with distributed architecture?
Start simple. One queue. One database. Two services max. Get it working. Add complexity only when you can measure the pain it solves. Premature distribution is the root of all evil.
Distributed software architecture isn't about using more servers. It's about accepting that servers fail and designing for that reality.
Start small. Test everything. Monitor ruthlessly.
You'll thank yourself at 3 AM.
Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.