Chapter 19 · Vector Search in Cassandra 5.0
Vector CQL Type: Fixed Dimensions, Embeddings, Similarity, and Schema Constraints
Define Cassandra 5.0 vector schema contracts, embedding provenance, fixed dimensions, normalization, similarity metrics, and failure boundaries before indexing.
Learning outcomes
AtlasMart wants semantic support search, but its first design review contains a dangerous sentence: “Store whatever embeddings the AI team sends us and add vector search later.” Cassandra makes the schema contract explicit: vector dimensions are fixed, vector-search elements must be floats, every vector belongs to one embedding space, and the index metric is part of the index definition. This lesson establishes those invariants before any ANN query.
Explain the Cassandra 5.0 VECTOR type as a fixed-length, non-null-element value and distinguish general vector storage from vector-search-compatible float vectors.
Record embedding model/version/preprocessing provenance so incomparable embedding spaces are never mixed silently.
Explain cosine, dot product, Euclidean similarity, normalization, ANN, exact top-k, and recall@k before using them.
Create a bounded AtlasMart vector schema and observe dimension/type/index state with CQL and SAI virtual tables.
Diagnose dimension mismatch, model-space mixing, and “Cassandra generates embeddings” as application/schema errors.
The mandatory labs continue the disposable AtlasMart course
cluster: Apache Cassandra 5.0.9 in the pinned
cassandra:5.0.9 container, Java 17 inside the
image, cluster atlasmart-course, Docker network
atlasmart-cassandra, nodes
atlasmart-cass-1..3, datacenter dc1,
racks rack1..rack3, and 16 virtual nodes per
node. The chapter keyspace is
atlasmart_vector with
NetworkTopologyStrategy, RF=3, and normal
reads/writes at LOCAL_QUORUM. Tables explicitly
use UnifiedCompactionStrategy (UCS), no default TTL, and the
Cassandra default gc_grace_seconds unless a
lesson says otherwise. Authentication, client TLS, internode
TLS, and remote JMX are disabled only inside this isolated
local learning network; production systems must enforce
appropriate authentication, authorization, encryption, and
network boundaries. Apache Cassandra Java Driver 4.19.3 is
optional for application-level latency/vector-codec examples.
The mandatory embedding data is a deterministic
four-dimensional pedagogical fixture—free, local, and
intentionally not a production semantic model. No paid
embedding or LLM API is required.
Run commands only against the disposable Apache Cassandra course lab or another explicitly approved non-production environment. Confirm node, keyspace, table, container, volume, path, and datacenter targets before destructive, failure-injection, cleanup, repair, restore, security, or topology operations. Capture current state and expected rollback/recovery evidence first; output and timings can differ by host, operating system, Java runtime, Docker/runtime, driver, and Cassandra configuration.
Terms and mental model
A vector is an ordered fixed-length array of numbers. An embedding is a vector produced by a model or deterministic transformation to represent an object in a geometric space. Its dimension is the number of elements. Embedding provenance means the model name/version, preprocessing, normalization policy, and source revision that explain how the vector was created. A similarity function or distance rule decides what “near” means. Cosine similarity compares direction, dot product combines direction and magnitude unless vectors are normalized, and Euclidean is based on geometric distance. Normalization usually means scaling a vector to unit length. Approximate Nearest Neighbor (ANN) retrieval searches an index for likely nearest vectors without guaranteeing the exact top-k ordering; exact retrieval evaluates every candidate under the chosen metric. Recall@k is the fraction of exact top-k items recovered by ANN. It measures retrieval fidelity, not whether the documents are useful to a user.
Storage-Attached Indexing (SAI) is Cassandra
5.0's storage-integrated secondary indexing framework; vector
ANN search uses an SAI index on a
vector<float,n> column. A
coordinator is the Cassandra node handling a
particular client request. A replica stores
data for a token range according to the keyspace replication
strategy. A partition is the Cassandra
storage/routing unit selected by a partition key; the
partitioner hashes that key to a token. An
immutable SSTable is an on-disk Sorted String
Table. A consistency level (CL) states the
replica acknowledgments/responses required by an operation.
RAG (Retrieval-Augmented Generation) is an
application architecture that retrieves evidence and supplies it
to a generative model; Cassandra can be the retrieval store but
does not generate embeddings or model answers.
1. The vector column is a schema contract
Cassandra 5.0 introduced
VECTOR<type,dimension>. The value is fixed
length and flattened as one value; elements cannot be null.
Cassandra can store vectors of several element types, but vector
search requires a floating-point vector. The documented
dimension ceiling is 8192 elements. A schema such as
vector<float,4> therefore rejects a three- or
five-element value before it can poison an ANN index.
| Contract | Why it matters | AtlasMart decision |
|---|---|---|
| dimension = 4 | all vector arithmetic requires equal length | toy lab only; production dimension comes from model |
| float elements | SAI vector search operates on floating-point embeddings | reject int/bigint search vectors |
| no null element | geometry is undefined for missing coordinates | validate embedding pipeline before write |
| model/version columns | same dimension does not imply same embedding space | store provenance beside every vector |
| similarity_function | ANN ordering depends on one index metric | choose and evaluate before build |
2. Reproducible local schema and validation
# Verify the shared course lab first.docker exec atlasmart-cass-1 nodetool versiondocker exec atlasmart-cass-1 nodetool statusdocker exec atlasmart-cass-1 java -version# Recreate only if the disposable course cluster does not exist.docker network inspect atlasmart-cassandra >/dev/null 2>&1 || docker network create atlasmart-cassandradocker volume create atlasmart-cass-1-datadocker volume create atlasmart-cass-2-datadocker volume create atlasmart-cass-3-datadocker inspect atlasmart-cass-1 >/dev/null 2>&1 || docker run -d --name atlasmart-cass-1 --hostname atlasmart-cass-1 --network atlasmart-cassandra -e CASSANDRA_CLUSTER_NAME=atlasmart-course -e CASSANDRA_DC=dc1 -e CASSANDRA_RACK=rack1 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_NUM_TOKENS=16 -v atlasmart-cass-1-data:/var/lib/cassandra cassandra:5.0.9# Wait until node 1 is UN before starting peers.docker exec atlasmart-cass-1 nodetool statusdocker inspect atlasmart-cass-2 >/dev/null 2>&1 || docker run -d --name atlasmart-cass-2 --hostname atlasmart-cass-2 --network atlasmart-cassandra -e CASSANDRA_CLUSTER_NAME=atlasmart-course -e CASSANDRA_DC=dc1 -e CASSANDRA_RACK=rack2 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_NUM_TOKENS=16 -e CASSANDRA_SEEDS=atlasmart-cass-1 -v atlasmart-cass-2-data:/var/lib/cassandra cassandra:5.0.9docker inspect atlasmart-cass-3 >/dev/null 2>&1 || docker run -d --name atlasmart-cass-3 --hostname atlasmart-cass-3 --network atlasmart-cassandra -e CASSANDRA_CLUSTER_NAME=atlasmart-course -e CASSANDRA_DC=dc1 -e CASSANDRA_RACK=rack3 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_NUM_TOKENS=16 -e CASSANDRA_SEEDS=atlasmart-cass-1 -v atlasmart-cass-3-data:/var/lib/cassandra cassandra:5.0.9# Continue only after all three nodes are UN.docker exec atlasmart-cass-1 nodetool statusdocker exec -it atlasmart-cass-1 cqlsh
CREATE KEYSPACE IF NOT EXISTS atlasmart_vectorWITH replication = {'class':'NetworkTopologyStrategy','dc1':3};CREATE TABLE IF NOT EXISTS atlasmart_vector.documents ( tenant_id text, corpus_bucket tinyint, document_id uuid, title text, body text, locale text, doc_type text, embedding_model text, embedding_version text, embedding vector<float,4>, updated_at timestamp, PRIMARY KEY ((tenant_id,corpus_bucket),document_id)) WITH compaction = {'class':'UnifiedCompactionStrategy'};CONSISTENCY LOCAL_QUORUM;
INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at) VALUES ('tenant-a',0,00000000-0000-0000-0000-000000001901,'Return policy','How to return an unopened product within the return window.','en','support','atlasmart-toy','v1',[1.0,0.0,0.0,0.0],'2026-09-08T07:00:00Z');INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at) VALUES ('tenant-a',0,00000000-0000-0000-0000-000000001902,'Refund delay','Why approved refunds can take several days to appear.','en','support','atlasmart-toy','v1',[0.9,0.2,0.1,0.0],'2026-09-08T07:01:00Z');INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at) VALUES ('tenant-a',0,00000000-0000-0000-0000-000000001903,'Shipping tracker','Track a parcel after warehouse dispatch.','en','support','atlasmart-toy','v1',[0.0,1.0,0.0,0.0],'2026-09-08T07:02:00Z');INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at) VALUES ('tenant-a',0,00000000-0000-0000-0000-000000001904,'Password reset','Recover access to an AtlasMart account.','en','security','atlasmart-toy','v1',[0.0,0.0,1.0,0.0],'2026-09-08T07:03:00Z');INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at) VALUES ('tenant-a',1,00000000-0000-0000-0000-000000001905,'Invoice copy','Download a VAT invoice for an order.','en','billing','atlasmart-toy','v1',[0.0,0.0,0.0,1.0],'2026-09-08T07:04:00Z');INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at) VALUES ('tenant-a',1,00000000-0000-0000-0000-000000001906,'Exchange item','Exchange an eligible item for another size.','en','support','atlasmart-toy','v1',[0.8,0.1,0.0,0.2],'2026-09-08T07:05:00Z');INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at) VALUES ('tenant-a',1,00000000-0000-0000-0000-000000001907,'Cancel order','Cancel before warehouse fulfillment begins.','en','support','atlasmart-toy','v1',[0.7,0.0,0.2,0.1],'2026-09-08T07:06:00Z');INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at) VALUES ('tenant-a',1,00000000-0000-0000-0000-000000001908,'Delivery delay','Investigate a shipment that missed its delivery date.','en','support','atlasmart-toy','v1',[0.1,0.9,0.0,0.0],'2026-09-08T07:07:00Z');INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at) VALUES ('tenant-b',0,00000000-0000-0000-0000-000000001909,'Tenant B private return note','Private tenant B support content.','en','support','atlasmart-toy','v1',[0.99,0.01,0.0,0.0],'2026-09-08T07:08:00Z');
The four numbers here are not generated by a language model. They are an intentionally transparent teaching embedding: the first axis roughly represents returns/refunds, the second shipping, the third account/security, and the fourth billing. That makes expected neighborhoods understandable without a paid API or opaque model. Production embeddings require a real model and a documented preprocessing/version contract.
-- Valid: exactly four float-compatible values.INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at)VALUES ('tenant-a',0,00000000-0000-0000-0000-000000001910,'Boundary valid','dimension four','en','lab','atlasmart-toy','v1',[0.25,0.25,0.25,0.25],toTimestamp(now()));-- Deliberately wrong: dimension three for vector<float,4>.INSERT INTO atlasmart_vector.documents (tenant_id,corpus_bucket,document_id,title,body,locale,doc_type,embedding_model,embedding_version,embedding,updated_at)VALUES ('tenant-a',0,00000000-0000-0000-0000-000000001911,'Wrong dimension','must fail','en','lab','atlasmart-toy','v1',[0.1,0.2,0.3],toTimestamp(now()));-- Inspect the schema instead of inferring dimension from one row.DESCRIBE TABLE atlasmart_vector.documents;
The second insert should fail because the value does not match
vector<float,4>. The exact
cqlsh/native-protocol message can vary. A rejection proves
schema validation; it does not prove your embedding is
semantically meaningful.
3. Embedding provenance is part of correctness
Two models can both produce 384-dimensional vectors and still define unrelated coordinate systems. Comparing them is as invalid as measuring a temperature in Celsius against an unrelated identifier that happens to be numeric. Record at least model name, model/version/hash, preprocessing/tokenization, normalization policy, source revision, and embedding timestamp. If the embedding space changes, treat migration as a search-index/data migration—not an in-place semantic overwrite hidden behind the same version string.
SELECT tenant_id,corpus_bucket,document_id,title,embedding_model,embedding_version,embeddingFROM atlasmart_vector.documentsWHERE tenant_id='tenant-a' AND corpus_bucket=0;
Dimension compatibility is syntactic, not semantic. A safer migration writes v2 embeddings into a separate versioned table/column/index, evaluates v1 and v2 against the same ground-truth queries, cuts traffic deliberately, and retains rollback until the new space is proven.
4. Verification and reset
DELETE FROM atlasmart_vector.documentsWHERE tenant_id='tenant-a' AND corpus_bucket=0 AND document_id=00000000-0000-0000-0000-000000001910;SELECT count(*) FROM atlasmart_vector.documentsWHERE tenant_id='tenant-a' AND corpus_bucket=0;
- All three Cassandra nodes are
UN. -
The schema is exactly
vector<float,4>. - The three-element insert is rejected.
- Every retained row has explicit embedding model and version metadata.
- No external embedding API is required for the mandatory lab.
Check your understanding
- Does equal vector dimension prove two embeddings can be compared?
-
Can Cassandra vector search use vector
? - What does recall@k measure?
- Why store embedding_version in the row?
- Does Cassandra generate embeddings?
Review the answers
1. No. They must also belong to the same compatible embedding space and preprocessing/normalization contract.
2. Cassandra can store vector values of other element types, but vector search is defined for floating-point vectors.
3. How many exact top-k neighbors are recovered by ANN; it does not measure whether the retrieved documents answer the business question.
4. It makes semantic provenance observable and supports safe dual-version migration/evaluation.
5. No. Cassandra stores/indexes/searches vectors; the application or external/local model pipeline generates them.
Production judgment
Decide whether Cassandra vector search fits by measuring the whole retrieval system: corpus size and growth; vector dimension and bytes; embedding generation/update rate; embedding provenance; tenant and authorization model; query filters and bucket fanout; RF/CL and node/DC failures; ANN recall@k against a fixed exact baseline; application relevance metrics; p50/p95/p99 retrieval latency; result payload size; write amplification; SAI disk and memory footprint; SSTable count/compaction; vector overwrite/delete rate; index build/rebuild/streaming time; repair/backup/restore behavior; JVM/off-heap/chunk-cache pressure; driver timeouts/retries/idempotency; guardrails; observability; and operator skill. Do not report ANN latency without recall, and do not call recall “answer quality.”
Security is not a similarity metric. Cassandra role permissions are table/keyspace oriented, not row-level authorization; an application must derive allowed tenant/corpus scope before retrieval and encode that scope in the data model/query. Post-filtering unauthorized ANN results can leak information or reduce useful top-k results. Managed services and Cassandra-compatible APIs may differ in vector syntax, limits, index lifecycle, metrics, encryption, billing, and topology. Lesson 2 adds the SAI vector index and traces actual ANN queries, build state, limits, and failure behavior.
Summary and next bridge
Vector search begins with a stable embedding-space contract, not
with CREATE INDEX. Cassandra enforces
dimension/type structure; your application owns semantic
provenance and preprocessing. Next, build the SAI ANN index and
observe how Cassandra serves approximate neighbors.
Authoritative references
Vector search and SAI are version-sensitive. Re-check these sources when regenerating the lesson rather than freezing a 2026 assumption.