The problem it solves
Most architectures scale the application tier and quietly assume the database will keep up. For workloads with extreme, unpredictable concurrency — a concert on-sale, a betting market during a goal, a flash sale — it will not. You hit connection limits, lock contention on the hot rows, and replication lag, and no amount of application replicas helps.
Space-based architecture (also called cloud or tuple-space architecture) removes the synchronous database from the request path entirely. Working state lives in a replicated in-memory data grid; durability happens asynchronously behind it.
The four components
| Component | Role |
|---|---|
| Processing unit | The deployable: application logic plus an in-memory copy of the data it needs. Scaled by adding identical units. |
| Virtualised middleware | The fabric: messaging grid (routes requests), data grid (replicates state), processing grid (coordinates multi-unit work), deployment manager (scales units on load). |
| Data pump | Asynchronously ships changes from memory to durable storage, usually via a durable queue. |
| Data writer / reader | Persists what the pump sends; reads seed a starting or restarting unit's in-memory state. |
How a request flows
client
-> messaging grid (route by partition key, e.g. eventId)
-> processing unit N
read : local in-memory grid (microseconds)
write : update memory + replicate to peers
emit : change record -> durable queue
<- response
durable queue
|
[ data pump ]
v
database / warehouse (seconds behind, and that's fine)Latency drops by orders of magnitude because the slowest step is now memory replication rather than a disk-backed transaction. Throughput scales by adding processing units, since each holds its data locally.
Replication and cache coherence
The hard part. Every processing unit holding a copy of the same data means updates must propagate, and how you do it determines your consistency guarantees:
- Full replication: every unit holds everything. Simple reads, but memory cost and replication traffic grow with unit count. Only workable for small datasets.
- Partitioned (sharded) grid: each unit owns a key range; requests are routed by partition key. This is the standard approach and the reason the messaging grid exists.
- Partitioned with backups: each partition has one primary and one or more backups on other units, so a unit failure does not lose the partition. This is what production grids (Hazelcast, Ignite, Coherence, GigaSpaces) do by default.
For contended counters — seats remaining, stock left — route all writes for a key to its owning primary. Single-writer-per-partition converts a distributed consistency problem into a local one, which is the whole trick.
Durability and the data pump
- A write updates memory and its backups synchronously (fast, in-datacentre), and emits a change record to a durable queue.
- The data pump consumes the queue and writes to the database at whatever rate the database can manage. Backlog during a spike is expected and drains afterwards.
- The window between memory commit and disk commit is your real durability risk. Bound it: replicate to at least two units in different failure domains, use a durable queue rather than in-process buffering, and measure pump lag as a first-class SLO.
- On restart, a unit rehydrates its partition from the database plus the unconsumed queue tail.
Sizing and partitioning
- Only the working set goes in memory. Today's events, open positions, active carts — not five years of history.
- Pick a partition key with natural isolation (eventId, matchId, accountId) so cross-partition operations are rare. Anything requiring a distributed transaction across partitions undoes the performance win.
- Watch for hot partitions. One blockbuster event can saturate a single primary; sub-partition it (seat blocks, price bands) if the domain allows.
- Scale by traffic prediction and by load. The deployment manager adds units as concurrency rises, but grid rebalancing itself costs time — pre-warm before a known on-sale.
Trade-offs and failure modes
| Property | Result |
|---|---|
| Latency | Excellent — memory-speed reads and writes |
| Elasticity | Excellent — add units, no database bottleneck |
| Durability | Weaker — a bounded window of unpersisted state |
| Consistency | Strong within a partition, eventual across the grid and the database |
| Operational complexity | High — grid tuning, split-brain, rebalancing, memory pressure |
| Cost | High — RAM is expensive relative to disk |
| Query flexibility | Poor — ad-hoc reporting must run off the persisted store |
The nastiest failure is a network partition splitting the grid: two halves each believe they own a partition and both accept writes. Production grids use quorum-based membership and will refuse to serve a minority partition. Configure this deliberately — the default is not always the safe one.
Interview framing
Strong answer
"Ticket on-sale is a 60-second spike with severe contention on a few thousand rows, so a database in the write path is the ceiling. I'd use a space-based design: processing units partitioned by eventId, each owning the seat inventory for its events in memory with a synchronous backup replica, all writes for an event routed to its primary so seat allocation is a local operation, and a data pump writing to Postgres asynchronously via a durable queue. We pre-warm units before the on-sale. The trade is a durability window of a second or two and the operational cost of running a grid — acceptable, because the alternative is a hard cap on concurrent buyers."
Follow-ups
- What happens if a processing unit dies with unpersisted writes?
- How do you prevent two units selling the same seat?
- How does this compare with just using Redis plus a queue?