Cassandra consultants have an obvious incentive to tell you Cassandra fits your problem. So let us spend a post on the opposite: the workload shapes where Cassandra is the wrong tool, where saying yes anyway costs a team a year of fighting the architecture. We would rather lose those engagements at the first call.
What Cassandra is actually for
The architecture makes specific promises: masterless writes at very high sustained rates, linear capacity scaling, always-on availability across datacenters, and predictable low-latency reads for queries you designed tables around. Workloads that match — IoT telemetry, messaging, activity feeds, personalization state, time-series at scale — get all of it, and there are few better tools in those lanes.
Everything below is the price of those promises. If your workload doesn't need them, you're paying without collecting.
The mismatches
Ad-hoc queries and analytics. Cassandra has no query planner rescuing arbitrary WHERE clauses; efficient access requires the partition key. If analysts need to slice data along dimensions nobody predicted, that is the defining anti-fit. Analytics belongs in a columnar warehouse or lake (and if operational data lives in Cassandra legitimately, CDC out to one — don't try to make the operational store also be the warehouse).
Relational shape: joins, constraints, transactions. No joins, no foreign keys, no multi-row ACID transactions (lightweight transactions are narrow, per-partition, and costly; Accord-based general transactions are arriving in the ecosystem but are not yet the thing to bet an order-processing system on). If your domain is orders–inventory–payments with cross-entity invariants, Postgres holds those invariants for you; Cassandra makes your application code hold them.
Small data. Below, say, a few hundred gigabytes and a few thousand writes per second, a single well-tuned Postgres instance with replicas typically outperforms a minimum-viable Cassandra deployment — while costing one database's worth of operational attention instead of a distributed system's. Cassandra's economics start at scales where partitioning is forced; adopting it early to "be ready" purchases years of operational overhead ahead of need.
Queues and worker coordination. Consume-and-delete patterns generate tombstone storms (we wrote a whole post on it), and Cassandra offers no queue semantics — no visibility timeouts, no consumer groups. Kafka, SQS, or Postgres SKIP LOCKED all do this properly.
Strong global consistency as a default. Cassandra's consistency is tunable per query, and quorum arithmetic gives you a lot — but if every operation in the system requires serializable global consistency, you are describing a different database (Spanner-class SQL, CockroachDB, or plain single-primary Postgres).
Frequently-mutated large collections and "update this one field on an entity read a hundred ways" — access patterns that resist being named by a partition key at design time will fight you forever.
The honest scorecard
Six questions we run with prospects:
- Can you list your queries now, before the data arrives? (No → warehouse or relational.)
- Is sustained write rate in the tens of thousands per second or higher, or headed there on a real curve? (No → Postgres is simpler.)
- Do you need active-active multi-datacenter or five-nines availability contractually? (Yes → strong Cassandra signal.)
- Are cross-entity transactions core to correctness? (Yes → relational.)
- Is the data naturally partitioned — by device, user, conversation, sensor? (Yes → good sign.)
- Do you have, or will you fund, real operational ownership — repairs, compaction, upgrades? (No → managed Cassandra like Astra DB, or don't.)
A workload scoring well on 2, 3, and 5 with a yes on 1 is what Cassandra was built for. A workload failing 1 and 4 will be unhappy no matter how well it's tuned — and tuning is not the cure for architectural mismatch.
The failure mode of adopting anyway
When a mismatched workload lands on Cassandra, the failure is rarely dramatic — it is a slow accretion of workarounds. Missing joins become application-side fan-out reads, so a page render costs twelve round trips. Missing transactions become compensating-write logic that mostly works, with a reconciliation job to catch the rest. Unpredicted query shapes become secondary indexes, then materialized views, then a nightly export to somewhere queryable. Each patch is individually reasonable; together they are a distributed database being operated as an expensive key-value store with a bespoke query layer bolted on top.
The tell, when we walk into these engagements, is that nobody can answer "which query does this table serve?" — because the tables were derived from entities rather than access patterns, and the access patterns kept arriving after the schema froze. At that point the honest options are a real remodel (query-first, with a dual-write migration) or a move to a store whose shape matches the domain. Tuning will not close the gap; we say so, and it is usually the most valuable sentence in the report.
Sometimes the answer is both
Mature systems are frequently polyglot in exactly this shape: Postgres for the relational core (accounts, orders, billing), Cassandra for the high-volume flows (events, telemetry, feeds), a warehouse fed by CDC for analytics. That is not indecision; it is putting each promise where it's collected.
If you're weighing this decision, we're glad to look at your workload and give you the same scorecard treatment — including, when it's true, the recommendation that you don't need us or Cassandra at all.