What Does It Mean to Be Disaggregated? A Practitioner's Guide
I spent three years building a monolithic data pipeline at a fintech company we'll call LendFast. It processed 50,000 transactions a day. One database. One application server. One queue. Worked beautifully until it didn't.
The day it broke was a Tuesday. A single bad query cascaded through every tenant. All 200 customers went down simultaneously. The CEO stood at my desk at 3 PM and asked: "How is this not isolated?"
That question is the entire point of disaggregation.
Being disaggregated means you've taken what was one thing — one compute unit, one storage system, one state machine — and split it into parts that can fail, scale, and evolve independently. It's not about being "microservices" or "serverless." It's about accepting that what is distributed system architecture? isn't a question you ask after you design the system. It's the first question.
This guide is what I wish someone had handed me in 2021.
What Disaggregation Actually Is (And Isn't)
Most people think disaggregation means "break your monolith into microservices." They're wrong because that's a deployment pattern, not an architectural principle.
Being disaggregated means no single process owns more than one responsibility. Not one service. One responsibility.
At SIVARO, we define it this way: A system is disaggregated when you can upgrade, scale, or fail any component without touching another. Let me give you concrete examples.
A monolithic database is not disaggregated. The compute layer and storage layer are coupled. You scale them together, fail them together, upgrade them together.
A disaggregated database? Snowflake proved this in 2014. Compute and storage separate. You scale query capacity independently from data volume. I've seen customers cut costs 40% by right-sizing compute during low-traffic hours.
Here's what disaggregation is not:
- Not decoupling for the sake of it. If you split a service into two pieces that must always be deployed together, you've just added latency.
- Not every function gets its own container. That's chaos, not architecture.
- Not "we use Kafka so we're disaggregated." Kafka only decouples producers from consumers in time. It doesn't decouple state.
The hard question: what is the architecture of a distributed system that's truly disaggregated?
I'll answer it in two words: shared nothing.
The Three Axes of Disaggregation
After eight years of building data infrastructure, I've found three dimensions where disaggregation matters. Miss one, and your system isn't truly disaggregated.
Compute vs. Storage
This is the obvious one. Separate your processing from your persistence.
Here's a pattern I use at SIVARO:
python
# Not disaggregated
class MonolithicProcessor:
def __init__(self):
self.db = PostgreSQL("connection_string")
self.cache = {}
def process(self, event):
# Compute and storage in same process
self.cache[event.id] = event
self.db.write(event)
result = self.compute(event)
self.db.write(result)
return result
# Disaggregated
import redis
import asyncpg
class ComputeWorker:
def __init__(self):
self.storage = StorageClient() # Separate process/service
self.cache = RedisCluster() # Separate cache layer
async def process(self, event):
# Compute owns this logic
enriched = await self.enrich(event)
result = await self.transform(enriched)
# Storage handled by another service
await self.storage.store(result)
return result
The second version lets me scale compute workers independently. When a batch job spikes CPU, I spin up 20 more workers without touching the storage cluster. When storage needs reindexing, I take down zero compute.
State vs. Stateless
This is where most distributed systems fail. They pretend state doesn't exist until it bites them.
Let me quote directly from a paper I wish I'd written: Your Agent is a Distributed System (and fails like one). The author describes AI agents failing the exact same way distributed systems have failed for decades — partial failures, split-brain, inconsistent state.
The fix is simple, but hard: every component that holds state must be explicitly disaggregated from components that transform state.
yaml
# docker-compose for a disaggregated stateful system
version: '3.8'
services:
api-gateway:
# Stateless — can scale horizontally
image: sivaro/gateway:latest
depends_on: []
replicas: 5
compute-worker:
# Stateless — state lives in cache and store
image: sivaro/worker:latest
environment:
REDIS_CLUSTER: "true"
depends_on:
- cache-cluster
- state-store
cache-cluster:
# Stateful — needs persistence and consistency
image: redis:7.4
volumes:
- redis-data:/data
deploy:
replicas: 3
restart_policy: on-failure
state-store:
# The ground truth
image: cockroachdb/cockroach:v24.1
volumes:
- cockroach-data:/cockroach/cockroach-data
Notice the API gateway has zero dependencies. It's a dumb pipe. The compute workers depend on state stores. The state stores depend on nothing but disk.
This is the architecture. What does it mean to be disaggregated? It means your stateless layer treats state stores as black boxes accessed only through well-defined APIs.
Control Plane vs. Data Plane
This is the axis almost everyone gets wrong.
Most teams build one service that handles API calls (control) AND executes business logic (data). When they need to change routing logic, they redeploy the data processing. When the data processing crashes, the control plane is unreachable.
Disaggregated systems separate these completely.
At SIVARO, we run a control plane that manages routing, auth, rate limiting, and configuration. Zero business logic. The data plane processes data. When we deploy a new routing rule, we update the control plane — data plane keeps running. When a data plane node fails, the control plane detects and reroutes. No blast radius.
This pattern saved us during the Redis debacle of 2025 (where a memory leak in a Redis module took down half our customers' caches). Control plane stayed up. Data plane degraded gracefully. Zero customer data loss.
Multi-Agent Systems Have a Distributed Systems Problem makes this exact point for agent systems: if your control plane for agent orchestration shares a process with the agents themselves, you've built a monolith.
The Coordination Trap
Here's the thing nobody tells you about disaggregation: it doesn't eliminate coordination problems. It exposes them.
When you have two separate services that need to agree on something — an order status, a user's balance, a configuration version — you've introduced a distributed consensus problem. That's hard.
Most teams respond by adding more coordination. They reach for distributed locks, consensus protocols, distributed transactions. This is the wrong answer.
The right answer is borrowed from Every System is a Log: Avoiding coordination in distributed applications: use the log as the ground truth.
go
// Disaggregated order processing using event log
type OrderService struct {
log EventLog
orders StateStore
payment PaymentClient
}
func (s *OrderService) CreateOrder(ctx context.Context, order Order) error {
// Write to log — this is the single source of truth
event := OrderCreatedEvent{
OrderID: order.ID,
UserID: order.UserID,
Amount: order.Amount,
Items: order.Items,
Timestamp: time.Now(),
}
if err := s.log.Append(ctx, "orders", event); err != nil {
return fmt.Errorf("failed to append event: %w", err)
}
// Don't coordinate with payment — let an async processor handle it
// Just acknowledge the order was received
return nil
}
The log becomes the single source of truth. Every downstream service reads from the log and acts independently. No two-phase commits. No distributed locks. Just ordered events.
I know what you're thinking: "But what about exactly-once semantics?" You don't need them. You need idempotent processing. Process each event until it succeeds, then record the offset. If you crash and replay, the operation is idempotent.
Real-World: What We Learned at SIVARO
Let me tell you about the system we built for a logistics company in 2024. They processed 200,000 package tracking events per minute. Their legacy system was a single Postgres instance with a CRUD API. It failed every Black Friday.
We disaggregated it into four independent systems:
- Ingestion layer — stateless Go services accepting updates via gRPC
- Event log — Kafka cluster partitioning by tracking ID
- Materialized state — CockroachDB for current package status
- Analytics — ClickHouse for historical queries
The ingestion layer pushes to Kafka. A stream processor materializes state into CockroachDB. A separate batch pipeline loads data into ClickHouse hourly.
What happens when ClickHouse is down for maintenance? Nothing. The pipeline pauses, the log accumulates, and analytics catch up when ClickHouse returns. The live tracking API never notices.
What happens when CockroachDB has a failover? The stream processor pauses, reconnects, and replays from its last committed offset. Idempotent writes mean zero duplicates.
The result: 99.999% uptime for live tracking. Black Friday 2025? 1.2 billion events processed. Zero outages.
This is what being disaggregated buys you: the failure of one component doesn't cascade into every other component.
What does it mean to be disaggregated? It means your system has more failure modes, but each failure is smaller, contained, and survivable.
When Disaggregation Backfires
I'm not going to sell you on disaggregation as a universal good. It has sharp edges.
Latency
Every network hop adds latency. If your disaggregated system requires five services to serve one request, you're adding 20ms of network overhead minimum. For user-facing APIs, that's death.
I've seen teams disaggregate a search service into query planner, index reader, ranker, and result formatter. The result? 800ms search latency instead of 50ms. Users left.
Fix: batch operations that must be fast. Keep hot paths in a single process with clear internal boundaries. Disaggregate cold paths and background processing.
Operational Complexity
THE SIGNAL: What matters in distributed systems | #4 points out that distributed systems fail in ways that are fundamentally harder to debug than monolithic ones. Network partitions. Partial failures. Clock skew.
At SIVARO, we run 47 microservices in production. Debugging a single request across all of them requires distributed tracing, structured logging, and a dedicated on-call rotation. That's expensive.
Fix: don't disaggregate until you have the observability infrastructure to support it. Without tracing and metrics, you're flying blind.
Transactional Integrity
If your business requires atomic updates across multiple services, disaggregation is painful. You can use sagas, but sagas are hard to implement correctly.
I've seen teams give up and use a monolithic service for order processing precisely because they needed transactional guarantees across payment, inventory, and shipping.
Fix: keep transactions within a service boundary. Use event-driven eventual consistency across service boundaries. If you absolutely need cross-service transactions, use a saga orchestrator — I recommend Temporal or Restate.
The Architecture of a Disaggregated State Machine
Let me show you what a truly disaggregated system looks like in practice.
We built a state machine for a multi-agent AI system last year. The problem: coordinating 12 AI agents that process insurance claims. Each agent reads claim data, makes a decision, and writes its decision back. The system must guarantee that decisions are processed in order and that partial failures don't corrupt state.
Here's the architecture:
┌─────────────────────────────────────────┐
│ Control Plane │
│ (Rust, no state, just orchestration) │
└──────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Event Log (Kafka) │
│ Partitioned by claim_id │
│ Ordered per partition │
└──────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ State Machine Executor │
│ Reads from log, executes transitions │
│ Writes state to CockroachDB │
└──────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Materialized State │
│ Current claim status │
│ Accessed by REST API │
└─────────────────────────────────────────┘
The control plane writes events to Kafka. The state machine executor reads events, executes deterministic state transitions, and writes results to CockroachDB. The REST API reads from CockroachDB.
What happens when an agent crashes mid-transition? The executor catches the failure, writes a "TransitionFailed" event, and the control plane decides whether to retry or fail the claim. The state remains consistent because the log is the authoritative record of what happened.
Here's the code that makes this work:
rust
// Disaggregated state machine executor
async fn execute_claim_transition(
log: &mut EventLog,
state: &mut StateStore,
event: StateTransitionEvent,
) -> Result<(), ExecutorError> {
// Read current state from materialized store
let current_state: ClaimState = state
.get(&event.claim_id)
.await
.map_err(|_| ExecutorError::StateReadFailed)?;
// Compute next state (deterministic, pure function)
let next_state = match (¤t_state, &event.transition) {
(ClaimState::New, Transition::AssignAgent { agent_id }) => {
ClaimState::Assigned { agent_id: *agent_id }
}
(ClaimState::Assigned { agent_id }, Transition::Review { decision }) => {
ClaimState::Reviewed {
agent_id: *agent_id,
decision: decision.clone()
}
}
_ => return Err(ExecutorError::InvalidTransition),
};
// Write transition event to log (atomic append)
log.append(
&event.claim_id,
TransitionRecord {
claim_id: event.claim_id,
from: current_state,
to: next_state.clone(),
timestamp: Utc::now(),
}
).await?;
// Update materialized state (idempotent — replay is safe)
state.put(&event.claim_id, next_state).await?;
Ok(())
}
The key insight: the log write happens before the state store update. If the state store fails, the executor replays from the last committed offset. The state store updates are idempotent. Consistency is maintained.
This is what is the architecture of a distributed system? — it's a system that accepts partial failure as normal and builds resilience into every component.
Caching in a Disaggregated World
Disaggregation changes how you think about caching.
In a monolith, caching is simple — in-memory map, TTL, done. In a disaggregated system, caching becomes a distributed systems problem. Cache invalidation. Stale reads. Thundering herds.
I've learned the hard way: don't cache in your application layer. Cache in a dedicated layer.
Caching for Agentic Java Systems: Internal, Distributed, ... covers this well. For agent systems, they recommend a write-through cache that invalidates entries when the underlying state changes.
Here's the pattern we use:
python
# Bad: cache inside the service
class BadOrderService:
def __init__(self):
self.cache = {}
self.db = Database()
def get_order(self, order_id):
if order_id in self.cache:
return self.cache[order_id]
order = self.db.fetch(order_id)
self.cache[order_id] = order
return order
# Good: dedicated cache with explicit invalidation
class OrderService:
def __init__(self):
self.cache = RedisCache() # Separate service
self.db = Database()
async def get_order(self, order_id):
cached = await self.cache.get(order_id)
if cached:
return cached
order = await self.db.fetch(order_id)
await self.cache.set(order_id, order, ttl=300)
return order
async def update_order(self, order_id, updates):
await self.db.update(order_id, updates)
await self.cache.invalidate(order_id) # Explicit invalidation
The second version separates caching from business logic. You can scale the cache independently. You can swap Redis for Memcached without touching the service. Most importantly, failure of the cache doesn't crash the service — it just degrades performance.
The Faq You Actually Need
What does it mean to be disaggregated in simple terms?
It means splitting your system into parts that can fail, scale, and evolve independently. Instead of one big database, you have separate compute and storage. Instead of one big server, you have stateless workers and stateful stores. The key test: can you upgrade one component without taking down the whole system?
What is distributed system architecture and how does disaggregation relate?
What is distributed system architecture? It's the design of a system where components run on different computers and communicate via network. Disaggregation is the application of distributed systems principles to software architecture. Every disaggregated system is a distributed system, but not every distributed system is disaggregated. (A monolithic database replicated across servers is distributed but not disaggregated.)
When does disaggregation stop making sense?
When your coordination costs exceed your isolation benefits. Three signs: your system needs multi-service transactions for every request, your p95 latency is 10x your monolith's, or your team spends more time debugging network issues than building features. For small teams building simple CRUD apps, a monolith is usually the right choice.
How do you handle testing in a disaggregated system?
Contract tests between services, integration tests for critical paths, and chaos engineering for failure scenarios. We use Testcontainers to spin up real dependencies in tests. You can't mock your way to reliability — you need to test against real network conditions, real database failures, real latency.
What's the hardest part of disaggregation?
Observability. You go from one log file to twenty. From one metric to three hundred. From "I can SSH into the server" to "I need distributed tracing to find a single request." Invest in your observability tooling before you disaggregate. I recommend OpenTelemetry for tracing, Prometheus for metrics, and structured logging with correlation IDs.
Does disaggregation work for AI/agent systems?
Yes, but with caveats. Multi-Agent Systems Have a Distributed Systems Problem argues that most agent frameworks ignore distributed systems fundamentals. The result: agents get stuck in infinite loops, state diverges, and systems fail unpredictably. Disaggregated agent systems use a centralized event log for coordination, separate state stores per agent, and a control plane that manages agent lifecycle independently from agent execution.
The Bottom Line
Disaggregation isn't a technology choice. It's a trade-off.
You trade simplicity for resilience. You trade low latency for fault isolation. You trade easy debugging for operational flexibility. You trade cheap development for expensive operations.
I've built systems both ways. My current company SIVARO ships disaggregated infrastructure by default. But I still tell clients: if your traffic is predictable, your data volume is moderate, and your team is small, build a monolith first. Then disaggregate when the monolith hurts.
The pain tells you where to cut.
The pain for LendFast was blast radius. One query took down everyone. The fix was isolating tenants into separate compute environments. That's a form of disaggregation — multi-tenancy isolation.
The pain for our logistics client was scalability. One Postgres couldn't handle 200K events/minute. The fix was splitting ingestion, processing, and storage.
The pain for the insurance agent system was state consistency. Multiple agents writing to shared state without coordination caused corruption. The fix was the event log as single source of truth.
What does it mean to be disaggregated? It means your system fails gracefully. It means you can upgrade components independently. It means you pay for what you use and scale what needs scaling.
It means you've accepted that building resilient systems is harder than building fragile ones — but the cost of fragility is higher than the cost of complexity.
I learned that at LendFast's CEO's desk at 3 PM on a Tuesday. I'm still learning it today.
Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.