Skip to content

PATTERN Cited by 1 source

Two-monolith architecture

Problem

A product has two fundamentally different workload shapes that don't fit one monolith well:

  • Real-time / event-driven. High concurrent connections, data streaming, low-latency reactive. (e.g., location tracking + dispatch at ridesharing; presence + message delivery at chat.)
  • Request/response business logic. Standard CRUD with moderate concurrency, multiple domain owners, need for transactional semantics. (e.g., authentication, pricing, payments.)

Forcing both into one monolith picks one runtime/framework that's suboptimal for the other. Splitting straight to a full microservices fleet is premature — the team is still small, the domain boundaries aren't well-understood yet.

Solution

Split the system into exactly two monoliths, each picked for its workload shape:

  • Real-time monolith — a language/runtime with async + event-loop strengths (Node.js was the classic 2011–14 pick; Elixir/Go today).
  • Business-logic monolith — a language/framework with good data-modeling + transactional-DB ergonomics (Python/Django, Ruby/Rails, Java/Spring).

Each monolith has its own database matched to its workload:

  • Real-time monolith → Redis or MongoDB for low-latency state.
  • Business-logic monolith → PostgreSQL or MySQL for relational invariants.

Optional: a resilience shim between the two monoliths so failures in one don't propagate.

Canonical instance — Uber 2011

"The resulting architecture was two services. One built in Node.js ('dispatch') connected to MongoDB (later Redis) and the other built in Python ('API') connected to PostgreSQL. And to improve the resiliency of Uber's core dispatching flow, a layer between dispatch and API known as 'ON' or Object Node was built to withstand any disruptions within the API service."

sources/2024-03-14-highscalability-brief-history-of-scaling-uber

  • dispatch (Node.js + MongoDB→Redis) for rider-driver matching and real-time location.
  • API (Python + PostgreSQL) for business logic (auth, promotions, fare calculation).
  • Object Node / "ON" between them for failure isolation.

The pattern enabled Uber's engineering org to scale from <20 engineers to ~100 before the next architectural phase (the 2013 SOA split of the API monolith).

Why this is a stepping stone, not a destination

Two monoliths share all the monolith-scale pathologies — they just share them in two places instead of one:

  • Both eventually grow too large; one or both needs to split further (Uber's API became ~100 microservices in 2014).
  • The resilience shim (like Object Node) is itself a monolith that needs to be maintained.
  • Team ownership maps to two monoliths → hard to grow past two large engineering teams.

The value is buying time — keeping operational simplicity in the early-scale phase where the full microservices tax doesn't pay off yet.

When to use

  • Early-stage product with a clear workload bifurcation (real-time vs. request-response).
  • Engineering org size < ~50–100.
  • Strong language/runtime preferences on either side of the split.

When not to use

  • Workload isn't bifurcated — a single monolith is cheaper.
  • Already at microservices scale and headed there — skip this intermediate step.
Last updated · 517 distilled / 1,221 read