September 5, 2024·6 min read·engineeringcassandrasaistorageupgrades

Cassandra 5.0 expands indexing while keeping distribution explicit

Cassandra 5.0 combines storage-attached indexes, vector search, and new storage structures. The release also makes upgrade compatibility and the end of 3.x maintenance immediate operational concerns.

Cassandra 5.0 makes it easier to query columns outside the primary key without removing the distributed work those queries require. Storage-attached indexes are local to replicas, and global indexed queries still use range reads. That distinction is central to understanding the September 5 general-availability release. More flexible queries expand the available data models, while partitioning and consistency remain application design decisions.

The release announcement brings together SAI, vector search, trie-based storage structures, and Unified Compaction Strategy. It also announces the end of normal maintenance for the 3.x series, including 3.0 and 3.11. The release matters both to developers evaluating its capabilities and to operators deciding which maintained version their existing clusters should run.

I am drawn to Cassandra where active-active operation at scale is a real requirement. Its capabilities solve a difficult problem, but they do not make the operational model appropriate for every application. Version 5.0 is interesting because it expands what that model can express without making the distribution disappear.

SAI attaches indexes to storage

The SAI documentation shipped with 5.0.0 describes a column-based local secondary index implementation. Strings use an on-disk byte-ordered trie, while numeric values use a block-oriented balanced tree. The implementation shares related offset and token information at SSTable level instead of repeating all that information for each indexed column.

That storage organization matters when a table has several indexed columns. Each column still has an index from the user's perspective, but common row-related information can be represented once. The result is a different storage cost structure from independently duplicating the same identifying data with every column index.

Index updates occur synchronously with mutations and do not need a read before the write. Index components participate in snapshots, and incremental index building is supported. The same documentation explains that whole-SSTable streaming can transfer them with the corresponding storage files. These are operational properties of the index's attachment to storage, rather than merely details of its query syntax.

An example based on the shipped tutorial creates indexes over ordinary columns and uses them in a predicate. It assumes the application has already selected its keyspace.

CREATE TABLE person (
    id int PRIMARY KEY,
    name text,
    age int
);

CREATE INDEX ON person (name) USING 'sai';
CREATE INDEX ON person (age) USING 'sai';

SELECT * FROM person WHERE age >= 18 AND age <= 35;

The syntax expands access beyond a primary-key equality lookup. It does not establish that every query touches one partition or one replica. The SAI documentation explicitly says global queries build on Cassandra range reads. A useful data model still accounts for the scope of the queries it expects to execute.

Vector search uses the indexing system

The versioned release notes introduce a fixed-dimension vector type and approximate nearest-neighbor search through SAI. CQL expresses the ordering with ORDER BY ... ANN OF. The release also adds cosine, Euclidean, and dot-product similarity functions.

The word approximate describes an important part of the contract. A similarity query asks for nearby vectors through the search implementation, rather than converting the database into a promise of exhaustive exact ranking for every stored vector. The representation and similarity function need to match the application's retrieval model.

Keeping vector data in Cassandra can make sense when it belongs with data already using Cassandra's distribution. That is an architectural reason to consider the feature, rather than assuming a new retrieval operator justifies moving unrelated data into the database. The release adds another access method to an existing distributed storage system.

I find that integration more interesting than treating vector search as an isolated product category. Query flexibility is useful when it fits the data's ownership and update model. The application still decides what a vector represents and how a returned match becomes a meaningful result.

Trie structures change the storage implementation

The release adds trie memtables and a trie-indexed SSTable format. The notes describe reduced memory use and improved lookup behavior for the memtable implementation. Its SSTable format removes the index summary component and does not require key caching. These are changes to how the engine represents and finds keys, rather than changes to the application's primary-key definition.

The new SSTable format is named bti and requires configuration selection. The memtable implementation also has an explicit selection mechanism. Their presence in 5.0 therefore should not be interpreted as a claim that every upgraded node immediately rewrites all data into the new representation.

A storage improvement can preserve the logical data model while changing operational compatibility. That distinction becomes particularly relevant during an upgrade. An engine may read an older format while a previous engine cannot read every new artifact the upgraded node could produce.

UCS makes compaction choices configurable within one strategy

The announcement describes Unified Compaction Strategy in broad operational terms. Its shipped design document supplies a more precise mechanism. UCS combines tiered and leveled behavior through configurable scaling parameters, and uses SSTable density to organize levels.

Density is the size of an SSTable divided by the token-range share it covers. This lets the strategy split output into shards without mistaking a smaller shard for data at a lower logical level. Two tables with different physical sizes can represent the same density if they cover proportionately different portions of the token space.

Predetermined shard boundaries allow independent non-overlapping work to compact concurrently. The strategy can control individual SSTable sizes while retaining the hierarchy used to decide when compaction is needed. That addresses a limitation of organizing everything solely by physical file size.

The parameters still express a read and write cost tradeoff. Leveled behavior favors lower read amplification at greater write cost. Tiered behavior favors writes while potentially requiring more work on reads. The design allows different choices at different levels and identifies the notation used for each policy.

Calling this automatic workload tuning would overstate the shipped design. The document describes a basis for future automatic adaptation and a strategy that can be reconfigured. Its concrete present contribution is a unified parameterized model with sharding. Operators still choose a policy appropriate to the workload instead of receiving a universal performance guarantee.

Masking changes query output

Dynamic data masking can obscure sensitive column values when queries return them. The release notes distinguish masking functions used directly in queries from masks attached to columns in the schema. They also introduce permissions controlling access to clear values.

The stored value remains unchanged. That is useful for serving different query results according to access policy, but it is different from transforming the persisted data into an encrypted representation. A deployment needs to understand which identities can obtain clear data and how its permissions interact with attached masks.

This is another feature whose mechanism should guide its use. An application can reduce exposure through query output while preserving the original data for authorized operations. The security property depends on access control around those operations, not merely on the presence of a mask in one selected expression.

Upgrade compatibility is a separate state

The versioned notes introduce storage_compatibility_mode, initially set to CASSANDRA_4. In that mode, SSTables, commit logs, hints, and messaging stay compatible with Cassandra 4.x. The documented upgrade sequence separates installing the 5.0 binary from later transitions that enable the new storage compatibility state.

The distinction also affects TTL expiration limits. The notes describe the older 2038 boundary remaining while compatibility mode applies, with the later limit available after the cluster completes the documented storage transition. Running the new binary is not by itself evidence that every compatibility-dependent behavior has changed.

Java selection deserves the same precision. The public announcement promotes JDK 17 support, while the tagged NEWS.txt still labels that support experimental and states Java 11 as the minimum after Java 8's removal. That qualification is relevant to choosing the runtime for this release, rather than assuming the announcement's broad support heading answers every deployment question.

The 3.x end-of-life announcement is more immediate. The community may consider contributed security fixes case by case, but it does not guarantee them for unmaintained branches. Moving an existing cluster to a maintained line is therefore a separate decision from whether its application needs vector search or SAI. Version 5.0 expands the database's capabilities while making that support boundary explicit for operators still on 3.0 or 3.11.