# If You Last Used Cassandra 3.11, You Might Not Recognize It Today

> Source: <https://softwaremill.com/apache-cassandra-5-6-what-changed-since-3-11/>
> Published: 2026-09-16 12:25:05+00:00

# If You Last Used Cassandra 3.11, You Might Not Recognize It Today

If you learned Cassandra around version 3.11, the advice was fairly consistent: design tables around queries, be careful with secondary indexes, choose a compaction strategy for each workload, and arrange repair scheduling yourself. For conditional updates, use lightweight transactions. For transactions spanning multiple partitions, expect to do more work outside the database.

In consulting work, I still encounter 3.11 clusters operated according to that model. Much of it remains useful, but several limitations behind those recommendations have changed. Cassandra now has storage-integrated indexing, vector search, a more flexible compaction strategy, and built-in repair scheduling. The 6.0 work goes further into cluster metadata and transactions.

As of September, 2026, [Apache Cassandra 5.0.9 is the latest GA release](https://cassandra.apache.org/_/download.html). Cassandra 6.0 remains pre-GA, with alpha builds available. That distinction matters: SAI is something to evaluate on the stable release; Accord is a reason to follow and test the next one.

The interesting part is not that Cassandra gained more configuration knobs. It is that several old assumptions about how Cassandra must be queried and operated have started to loosen. A rough comparison looks like this:

| Area | What changed | Release | 
|---|---|---|
| Streaming | An eligible whole SSTable can use a zero-copy transfer path | 4.0 | 
| Operational limits | Guardrails provide configurable warnings and failures | 4.1 | 
| Secondary indexing | Storage-Attached Indexing, or SAI | 5.0 | 
| Compaction | Unified Compaction Strategy, or UCS | 5.0 | 
| Similarity queries | Native vectors and approximate nearest-neighbor search | 5.0 | 
| Repair scheduling | Auto Repair, with explicit opt-in in the 5.0 backport | 5.0.8+ | 
| Cluster metadata | Transactional Cluster Metadata | 6.0 work | 
| Cross-partition transactions | Accord integration | 6.0 work | 

I wrote about some of the older pitfalls in [7 mistakes when using Apache Cassandra](https://blog.softwaremill.com/7-mistakes-when-using-apache-cassandra-51d2cf6df519). The useful question now is which advice still follows from Cassandra's architecture and which reflected limitations of an older implementation.

## Cassandra 4.0 improved the cost of moving data

Streaming transfers data between nodes during bootstrap, replacement, rebuild, repair, and topology changes. In a large cluster, it determines how long a node takes to become useful and how quickly lost replica data can be restored.

Cassandra 4.0 introduced a zero-copy streaming path for eligible whole SSTables. Instead of deserializing their contents into Java objects and serializing them again, Cassandra can transfer the files more directly. Partial transfers and other cases still need the appropriate streaming path; the optimization does not apply to every transfer.

The [4.0 streaming documentation](https://cassandra.apache.org/doc/4.0/cassandra/new/streaming.html) reports roughly a fivefold improvement in the benchmark used to introduce the feature. That is not a capacity estimate for every cluster, but it explains why the change mattered: less allocation and CPU work can leave more of the transfer limited by storage and network throughput.

For a node holding terabytes of data, faster streaming can shorten replacement and reduce the time spent with missing replicas. It is an operational improvement even if the application's CQL stays exactly the same.

## Guardrails move some limits into the database

Older Cassandra deployments often depended on conventions enforced in code review or runbooks. Avoid creating hundreds of tables. Keep collections bounded. Do not let an application issue queries over an uncontrolled number of partitions. Use `ALLOW FILTERING` carefully.

The [Guardrails Framework introduced in 4.1](https://cassandra.apache.org/doc/4.1/cassandra/new/index.html) lets operators express some of these rules in Cassandra itself. Depending on the guardrail, a configuration can warn, reject an operation after a threshold, or disable a feature. Examples include table and index counts, collection sizes, and the number of partition keys selected by a query. `ALLOW FILTERING` can be disabled.

Conceptually:

A warning gives an application team a chance to fix growing usage before it reaches the failure threshold. A rejection prevents an operation that exceeds the configured limit. Neither replaces workload design, but both are more reliable than assuming every developer has read the same operational guide.

## Secondary-index advice needs to distinguish SAI from older indexes

If you learned Cassandra years ago, there is a good chance somebody told you:

Don't use secondary indexes.

That advice was often justified. Traditional Cassandra secondary indexes could behave poorly at scale, especially when queries had to scatter across many partitions or index cardinality did not match the workload. Experienced teams often preferred another Cassandra pattern: create another table containing exactly the shape required by the query.

Suppose we have users:

```
CREATE TABLE users (
    id uuid PRIMARY KEY,
    country text,
    age int,
    status text,
    name text
);
```

and we need queries by `country` and `age`. The traditional Cassandra answer might be another table:

```
users_by_country_and_age
```

with an appropriate partition and clustering key. Then another query appears, and another table appears. That is not necessarily wrong - query-driven denormalization remains one of Cassandra's strengths.

Cassandra 5.0 adds Storage-Attached Indexing. We can index the columns used for filtering:

```
CREATE INDEX users_country_idx
ON users(country)
USING 'sai';

CREATE INDEX users_age_idx
ON users(age)
USING 'sai';

CREATE INDEX users_status_idx
ON users(status)
USING 'sai';
```

These examples assume an existing keyspace selected for the session. The [CQL index documentation](https://cassandra.apache.org/doc/latest/cassandra/developing/cql/create-custom-index.html) covers the syntax and supported column types.

SAI integrates indexes with memtables and SSTables. Reads combine results from those structures, and the index lifecycle follows the storage engine's flushes and compactions. It supports numeric ranges, combinations of indexed predicates, collection predicates, and text equality. For some access patterns, that can remove the need for an additional query-specific table.

The operational benefit is also relevant. If the required filtering can stay in Cassandra, there may be no need to maintain another copy in an external indexing system, with its own replication, recovery, and monitoring. That only holds when SAI provides the query behavior the application needs.

### An index does not eliminate query fan-out

SAI changes how Cassandra finds matching rows. It does not make a query over many partitions equivalent in cost to a lookup using one partition key.

With the indexes above, we can filter by both country and age without `ALLOW FILTERING`:

```
SELECT id, name, age
FROM users
WHERE country = 'PL'
  AND age >= 30
  AND age < 40;
```

SAI supports combining indexed predicates with `AND`, as shown in the [query documentation](https://cassandra.apache.org/doc/stable/cassandra/developing/cql/indexing/sai/sai-query.html). This gives us an additional access pattern without maintaining another table.

The physical layout remains different. A table partitioned by country, with age as a clustering column, can serve this query from the relevant country's replica set. In the `users` table, rows remain partitioned by ID, so an indexed query can require work across multiple token ranges. Supporting the same filter does not imply the same routing, ordering, or read cost.

The `users` example is deliberately simple. Filtering by a common country or status can match a large part of the dataset. An index makes that query expressible and can reduce local scanning, but the distribution of values and the work across nodes still matter.

For a frequent query with strict latency requirements, I would compare SAI against a table designed for that query using representative data. Measure the read latency and cluster work, then account for the write and storage costs of maintaining either indexes or denormalized tables.

The old blanket advice to avoid secondary indexes is too broad. The replacement is to evaluate SAI for the access pattern, with partitioning and selectivity still part of the decision.

## UCS makes compaction behavior easier to adjust

Cassandra operators have traditionally chosen among several compaction strategies:

| Strategy | Typical reason to choose it | Cost to consider | 
|---|---|---|
| STCS | Write-heavy workloads | More SSTables may need checking on reads | 
| LCS | Lower read amplification | More data rewritten during compaction | 
| TWCS | Time-series data with suitable TTL patterns | Effectiveness depends on timestamp and expiration behavior | 

Cassandra 5.0 adds Unified Compaction Strategy. The [UCS documentation](https://cassandra.apache.org/doc/latest/cassandra/managing/operating/compaction/ucs.html) recommends it for most workloads and describes how to configure behavior resembling the older strategies.

UCS exposes the tiered-versus-leveled trade-off through scaling parameters. Positive values favor tiered behavior, negative values favor leveled behavior, and different levels can use different settings. Sharding allows compaction work to run in parallel and helps control SSTable sizes on nodes holding large datasets.

The parameters can be changed while the system is running without requiring a full recompaction solely to switch between those behaviors. This makes tuning less disruptive than treating the initial strategy as a permanent choice.

It still requires measurement. A change that reduces read amplification can increase background write work. Existing SSTables also take time to move through compaction; changing a setting does not instantly produce a new layout. UCS gives operators a common mechanism for adjusting that balance, rather than removing the balance itself.

## Vector search can stay next to operational data

Cassandra 5.0 introduces a native `vector` type and approximate nearest-neighbor search through SAI. A product can store both its operational fields and an embedding:

```
CREATE TABLE products (
    id uuid PRIMARY KEY,
    name text,
    description text,
    embedding vector<float, 768>
);

CREATE INDEX products_embedding_idx
ON products(embedding)
USING 'sai';
```

A client can prepare a similarity query:

```
SELECT id, name
FROM products
ORDER BY embedding ANN OF ?
LIMIT 10;
```

Here `?` is a bind marker for a 768-element query vector supplied by the client. The model generating the embedding remains outside Cassandra.

Supported similarity functions include cosine similarity, dot product, and Euclidean distance. The [5.0 announcement](https://cassandra.apache.org/_/blog/Apache-Cassandra-5.0-Announcement.html) includes vector search among the release's major additions.

The most relevant case is an application that already stores a large operational dataset in Cassandra and wants similarity queries over it. Keeping the record and embedding together can avoid copying that data into a second database purely for vector retrieval.

That does not settle the choice of retrieval system. Approximate search quality, filtering, latency, update behavior, and operational cost still need evaluation. For a RAG application, for example, the useful result is good retrieval over its actual corpus, not simply the ability to run an ANN query.

## Auto Repair adds built-in scheduling

Cassandra replicas can temporarily diverge. Anti-entropy repair compares replica data and synchronizes differences that other mechanisms have not resolved. The repair machinery has long been part of Cassandra; scheduling it across the cluster has often depended on external tooling such as Cassandra Reaper.

The scheduler must cover the relevant token ranges, retry failures, and spread work without overwhelming foreground traffic. [Auto Repair](https://cassandra.apache.org/doc/latest/cassandra/managing/operating/auto_repair.html), developed through CEP-37 for 6.0 and backported to 5.0.8, moves that orchestration into Cassandra. It supports full, incremental, and preview repair scheduling, token-range splitting, repair history, and table priorities. Preview repair checks consistency rather than repairing differences.

There are two separate steps in the 5.0 backport. The JVM property `cassandra.autorepair.enable` enables the required schema support, and scheduling is then enabled through configuration or JMX. The documentation marks the property as non-reversible; disabling a schedule is a different operation. This deserves an explicit migration decision rather than being bundled into routine configuration cleanup.

A built-in scheduler reduces the need to operate a separate orchestration service. Operators still need to verify coverage, successful completion, and resource usage. A configured schedule is not evidence that every range is being repaired often enough.

## Cassandra 6.0 orders cluster metadata changes

The remaining changes belong to the 6.0 development line, not the current stable 5.0 release.

Cassandra has historically relied heavily on gossip and eventual propagation for cluster information. That works well for exchanging observations, but topology and schema changes need a consistent interpretation. During a node join or departure, coordinators must know which replicas own the affected ranges, including while requests are in flight.

Transactional Cluster Metadata, defined in [CEP-21](https://cwiki.apache.org/confluence/display/CASSANDRA/CEP-21:+Transactional+Cluster+Metadata), introduces a Cluster Metadata Service and an ordered log of transformations. Nodes apply committed metadata changes in that order. This provides a common history for changes such as schema updates and token ownership transitions.

The distinction is between agreeing on cluster structure and observing node liveness. Gossip still has a role; it is no longer expected to establish the authoritative order of critical metadata changes. Nor does every user-data write become an operation through a permanent primary node.

For operators, this addresses ambiguity around concurrent or partially observed topology changes. The objective is to make ownership transitions explicit and consistently interpreted, rather than relying on each node eventually reconstructing the same view.

## Accord extends the transaction model across partitions

Cassandra already has atomic operations within a partition and lightweight transactions using Paxos for conditional updates. Reserving a username is a familiar example:

```
INSERT INTO usernames (username, user_id)
VALUES ('alice', ?)
IF NOT EXISTS;
```

This assumes a table keyed by `username` and a client binding `user_id`. It expresses a conditional operation within that partition. It does not provide a general transaction for arbitrary reads and writes across unrelated partitions.

The difference matters when an invariant spans multiple accounts, inventory items, or other independently partitioned entities. Packing everything into one partition can simplify coordination, but it can also create a hot or unbounded partition. A logged batch across partitions is not a substitute for an isolated, conditional multi-partition transaction.

[CEP-15](https://cwiki.apache.org/confluence/display/CASSANDRA/CEP-15:+General+Purpose+Transactions) introduces Accord for general-purpose transactions with strict serializability. It targets a leaderless protocol with a one-WAN-round-trip fast path under normal conditions. That is a protocol property under its stated conditions, not a blanket latency promise for every application transaction.

The [6.0-alpha2 integration documentation](https://cassandra.apache.org/doc/6.0-alpha2/cassandra/architecture/accord-architecture.html) describes how Cassandra connects CQL execution, persistence, and recovery to Accord. For a transfer between independently partitioned accounts, the intended benefit is that the database can coordinate the conditional debit and credit as one transaction.

Strict serializability means committed transactions behave as if executed in a serial order that also respects real-time ordering. It is a substantially broader guarantee than separate successful writes to each account.

### Coordination and migration still need attention

Cross-partition transactions involve the participating replicas, transaction state, conflict handling, and recovery. Data locality still affects cost, and contention can affect latency. Accord can support an invariant that previously required application coordination without making every distributed update cheap.

The adoption design also allows existing Paxos-based LWT and Accord to coexist during a transition. CEP-15 calls for live migration, with any eventual Paxos deprecation left to a later decision. [AxonOps' write-up of Ariel Weisberg's operator talk](https://axonops.com/blog/cassandra-in-2025-a-year-in-review/) reports that Accord is designed to be off by default, adopted per table, with the option to migrate a table back to Paxos-based LWT. Exact configuration and migration behavior should be checked against the 6.0 release being evaluated; an alpha's behavior is not a final production contract.

For an existing application, I would start with an invariant that is awkward to enforce today and test that operation explicitly. Measure both normal execution and recovery under failure. The new guarantee is useful when it simplifies a real requirement, rather than serving as a reason to scatter naturally related data across partitions.

## What still needs the same care

Partition keys still determine data placement. Uneven traffic can produce hot partitions, and large partitions remain expensive to read, compact, and move. SAI and Accord do not remove those effects.

Denormalization remains useful for predictable, frequent queries. The decision now has another option: compare the cost of a purpose-built table with indexed access, including the work required to maintain each representation.

Tombstones and TTLs still affect reads, compaction, and repair. Deleting or expiring data does not immediately remove every stored version. A new compaction strategy changes how that work is organized, not whether it exists.

Cassandra also retains its focus on distributed, partitioned workloads. Flexible indexing and broader transactions do not add arbitrary relational joins. Its suitability still depends on the application's query patterns, distribution requirements, and consistency choices.

## Returning from 3.11

For someone running 3.11, Cassandra 5.0 is the practical release to evaluate first. SAI, UCS, and vector search are already available, alongside lower-level changes such as trie-based memtables and the BTI SSTable format. Auto Repair is available in the later 5.0 maintenance releases, with the opt-in requirements described above.

There is also a maintenance reason to move: Apache [announced the end of life of the 3.x series with 5.0](https://cassandra.apache.org/_/blog/Apache-Cassandra-5.0-Announcement.html). Evaluating new features should be separate from deciding how to leave an unmaintained branch.

Plan the move through a supported 4.x release before 5.0. Apache describes [3.11 to 4.x](https://cassandra.apache.org/_/blog/Apache-Cassandra-3.0.x-and-3.11.x-End-of-Life-Announcement.html) and [4.x to 5.0](https://cassandra.apache.org/_/blog/Apache-Cassandra-5.0-Announcement.html) as paths designed and tested for online upgrades. The exact patch versions, runtime requirements, driver compatibility, and SSTable steps still need to follow the target release's upgrade notes.

I would keep the version upgrade separate from schema and indexing changes, then compare representative queries and operational tasks on the upgraded cluster. That makes it easier to identify which change affected behavior.

If 3.11 shaped your view of Cassandra, the useful update is specific: some queries may no longer need another table, compaction is easier to tune within one strategy, and repair scheduling can live in the database. The 6.0 work may remove another application burden with cross-partition transactions. Partitioning and workload design remain the basis for deciding whether those capabilities help.

*Reviewed by: Krzysztof Ciesielski, Grzegorz Kocur*
