Citus: scaling Postgres horizontally without leaving it
Updated: 2026-07-07
Citus postgres: scale your database horizontally without abandoning Postgres. Learn how it works, when to use it, and compare with CockroachDB alternatives.
Postgres is a wonderful database until you stop fitting on a single node. At that point three paths open up: move everything to a distributed database like CockroachDB or YugabyteDB, build manual sharding with external tooling, or use Citus. This post focuses on Citus because in 2025 it has finally found its place: no longer a risky bet or a commercial promise, but a solid Postgres extension solving a concrete problem with less disruption than the alternatives.
Key takeaways
-
Citus adds an extension to the coordinator and worker nodes that intercepts query planning and distributes it, while presenting as a single Postgres from the outside.
-
The distribution key is the most important decision you’ll make: it conditions the performance of every subsequent query.
-
Citus shines in three scenarios: multi-tenant with clean per-customer partitioning, real-time analytics on time-series data, and applications that grew inside Postgres to need the next jump without a rewrite.
-
Queries crossing distinct distribution keys are slow because the coordinator has to move data between workers.
-
Citus works best when adopted as an early architectural decision, not a late patch.
The short history: from commercial fork to open extension
Citus was born in 2011 as a startup offering distributed Postgres. For years it ran as a mix of open source and a commercial edition, with the advanced features locked behind the paid tier. Microsoft bought the company in January 2019[1], and that split-edition model held for the following three years. The turn came with the Citus 11.0 release in June 2022[2], when the separate enterprise edition disappeared: features that used to sit behind a paywall, like non-blocking shard rebalancing and multi-user support with role propagation, moved into the open extension. The engine remains under the same AGPLv3 license it had used since its earliest releases[3], but that license now covers the whole product.
This matters because Citus is no longer a product with features locked behind a paywall. It’s a Postgres extension you can install via apt, compile from source, or deploy on any distribution without extra licensing. The 13.x series, shipping since 2025, adds support for Postgres 17[4], which means you can combine the base engine’s recent improvements, including the rewritten memory management for VACUUM[5], with distributed partitioning without giving up either one, including the query-planning improvements covered in PostgreSQL 17: optimisations that change real queries.
How Citus works
The core idea is designed with Postgres in mind from day one. Instead of rewriting the storage engine, it adds an extension to the coordinator and worker nodes that intercepts query planning and distributes it. From outside, the cluster looks like a single Postgres; you connect to the coordinator on the usual port and run standard SQL.
Citus distinguishes three table types:
-
Distributed tables: partition by a column called the distribution key, which defines which worker holds each row.
-
Reference tables: replicate across all nodes because they’re small and join-heavy with distributed ones.
-
Local tables: stay only on the coordinator for data that doesn’t need to scale.
The ideal distribution key meets two conditions. First, it spreads data reasonably evenly across workers to avoid hot spots. Second, it’s the column that shows up in most filters and joins, because that lets the coordinator route the query to a single worker instead of scattering it across the whole cluster.
The clearest success cases are multi-tenant applications where the customer identifier is the natural key: each tenant lives on one worker, a given customer’s dashboard queries only touch one node, and the whole cluster serves many tenants in parallel.
Where it fits well and where it hurts
Citus shines in three concrete scenarios:
-
Multi-tenant: where data partitions cleanly by customer or organization. Scaling is almost linear: the cluster grows by adding workers and per-query latency barely changes.
-
Real-time analytics on time-series-like data: Citus splits aggregate queries in parallel across workers and consolidates them on the coordinator. Citus’s own documentation[6] describes dashboards with sub-second response times that hold up even as the dataset keeps growing, something a single node can’t sustain without this kind of split.
-
Applications that grew inside Postgres to a large node and need the next jump without a rewrite. Migrating to CockroachDB or Spanner means significant changes to the data model, the SQL, transactions and operations. Migrating to Citus is far more incremental.
Where it hurts is just as important:
-
Queries crossing distinct distribution keys are slow because the coordinator has to move data between workers. If your workload runs a lot of ad-hoc queries that don’t respect the distribution model, Citus performs worse than a well-tuned monolithic Postgres.
-
Multi-partition transaction support exists, but it’s slower and more fragile than local transactions on a single Postgres.
-
Operations: a Citus cluster is more complex than a single Postgres, with a coordinator that’s a single point of failure unless you set up high availability with Patroni or similar.
Citus versus the distributed alternatives
The short answer when comparing to CockroachDB or YugabyteDB: Citus wins when your workload partitions cleanly and you want to stay on Postgres; the others win when you need friction-free distributed transactions on any access pattern.
CockroachDB and YugabyteDB are distributed databases by design from day one. They implement Raft consensus on every data range and offer global ACID transactions with no topology constraints. In exchange, the minimum latency of a transaction is higher than on Postgres, SQL compatibility covers around 95 percent but not everything, and the extension ecosystem is thinner.
Citus, by contrast, is a smart shortcut. It accepts that most applications with scaling problems have a clear natural partitioning, and if you respect it, it lets you scale horizontally without leaving Postgres.
When it pays off
Citus pays off when three signals align:
-
Your app already runs on Postgres.
-
It has grown to need more than one server.
-
It has a clear natural partitioning by customer, region, project, or similar.
If all three signals are present, Citus offers the lowest-disruption path and excellent performance within its range. If the third is missing, you’re wasting your time: you’ll pick a bad distribution key and Citus will deliver worse results than a bigger monolithic node.
The second reading is that Citus works best when adopted as an early architectural decision, not a late patch. Projects that planned the data model with Citus in mind from the start scaled smoothly. Those that arrived with a dense Postgres after years of organic growth had to restructure schemas, move data and rewrite queries before seeing any benefit.
If the system already has performance problems today, it’s better to stabilize the single node first with read replicas and caches, and plan Citus as the next phase. The same reasoning applies to the SQLite in production debate: the right tool depends on the load profile, not on what’s fashionable.
Conclusion
Citus is the right answer for a specific niche: Postgres that has grown beyond what a single node can handle, with a naturally partitionable load. Outside that niche, distributed alternatives or scale-up are better options. Inside it, Citus offers the lowest-disruption path of any alternative.
This article is also available in Spanish.