Chapter 20 · Materialized Views and Derived-Table Alternatives

Write Propagation, Consistency Expectations, Operational Limitations, and Failure Considerations

Trace base-to-view propagation under normal writes and replica failure, then verify recovery/repair instead of assuming synchronous relational-view freshness.

Intermediate → Advanced120–165 minutesPropagation + failure/repair labApache Cassandra 5.0.9 · experimental MV isolated cluster · Java 17 image · Java Driver 4.19.3 optionalLast reviewed: September 2026

Learning outcomes

AtlasMart's legal view builds successfully. The next risk appears during an incident: a base-table write returns success while one replica is unavailable, and the support team immediately reads the materialized view. “The base write succeeded” and “the derived view is already fresh everywhere” are not the same statement.

01

Explain base-to-view propagation as a separate distributed maintenance path with its own replicas, batchlog/streaming implications, and observability.

02

Distinguish base operation consistency level from materialized-view freshness and avoid relational synchronous-view guarantees.

03

Observe view-build, view-mutation/thread-pool, table statistics, and controlled replica-loss evidence.

04

Explain the current materialized_views_on_repair_enabled setting and why repair ownership must be tested, not assumed.

05

Recover the disposable cluster and verify base/view convergence after an explicit repair exercise.

Chapter 20 isolated lab baseline

Apache Cassandra 5.0 materialized views are experimental, disabled by default, and not recommended by the Apache Cassandra documentation for production use. Therefore this chapter does not alter the shared atlasmart-course cluster. It builds a separate disposable image atlasmart-cassandra-derived:5.0.9 from the pinned Docker Official Image cassandra:5.0.9 and enables materialized_views_enabled: true. The same isolated image enables cdc_enabled: true only so Lesson 4 can observe Change Data Capture (CDC) files locally; CDC remains off in the earlier shared course cluster. The lab cluster is atlasmart-derived-course on Docker network atlasmart-derived-net, nodes atlasmart-derived-cass-1..3, datacenter dc1, racks rack1..rack3, 16 virtual nodes per node, and named disposable data volumes. The keyspace atlasmart_derived uses NetworkTopologyStrategy with replication factor (RF) 3; ordinary lab reads/writes use LOCAL_QUORUM. New tables/views explicitly use UnifiedCompactionStrategy (UCS), no default time-to-live (TTL), and Cassandra's normal gc_grace_seconds unless stated otherwise. Authentication, client/internode Transport Layer Security (TLS), and remote Java Management Extensions (JMX) are disabled only on this private single-host lab network. Apache Cassandra Java Driver 4.19.3 is optional for projection/reconciliation examples. Exact build timing, view propagation timing, repair behavior, CDC filenames, latency, and failure outcomes are learner-captured evidence—not canned benchmark results.

Execution and safety note

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 base table is the Cassandra table the application writes directly. A materialized view (MV) is an automatically maintained Cassandra table derived from one base table with a different primary-key layout; applications cannot mutate the view directly. In Cassandra 5.0 this feature is explicitly experimental. A derived table is a broader design idea: any table whose rows are produced from authoritative data for another read pattern. An explicit projection or query table is a derived table owned by application/stream/reconciliation logic rather than Cassandra's MV mechanism. Storage-Attached Indexing (SAI) is Cassandra 5.0's storage-integrated secondary indexing framework and is a different way to support non-primary-key predicates. Change Data Capture (CDC) exposes durable portions of commit-log segments for an external consumer; Cassandra does not turn those raw records into a projection for you.

A primary key determines Cassandra partition placement and clustering order. The partition key is hashed to a token; replicas store token ranges according to the keyspace replication strategy. A request-scoped coordinator routes work to replicas and waits for the chosen consistency level (CL). A view replica stores a materialized-view partition, whose partition key can hash to a different token than the corresponding base row. Batchlog is Cassandra's durable coordination mechanism for certain multi-mutation operations and is used internally by MV maintenance; it is not a relational transaction log. Hints, repair, and streaming are separate convergence mechanisms. An immutable SSTable (Sorted String Table) stores flushed table/view data on disk and is managed by compaction.

1. A base write and a view mutation are related—but not identical operations

Each base replica receiving a mutation may need to inspect existing local row state to calculate the corresponding view change. The paired view partition can hash elsewhere, so Cassandra coordinates view mutations internally. Implementation details have evolved, but the operational lesson is stable: the base table and materialized view have separate storage/replica state, and the MV mechanism has additional write/locking/batchlog work. The official feature status is experimental precisely because operators must not infer relational indexed-view guarantees that Cassandra does not promise.

Question Base table Materialized view
Primary key/token base model different view key/token possible
Client mutation direct INSERT/UPDATE/DELETE cannot be directly mutated
Build/backfill normal base data initial MV build required for existing rows
Repair/streaming normal table repair rules extra MV replay/streaming setting matters
Freshness evidence read at chosen CL must measure view result/build/recovery separately

2. Normal propagation: measure instead of assuming

bash / WSL · start the isolated three-node derived-data lab
docker network inspect atlasmart-derived-net >/dev/null 2>&1 || docker network create atlasmart-derived-netdocker volume create atlasmart-derived-cass-1-datadocker volume create atlasmart-derived-cass-2-datadocker volume create atlasmart-derived-cass-3-datadocker inspect atlasmart-derived-cass-1 >/dev/null 2>&1 || docker run -d --name atlasmart-derived-cass-1 --hostname atlasmart-derived-cass-1 --network atlasmart-derived-net -e CASSANDRA_CLUSTER_NAME=atlasmart-derived-course -e CASSANDRA_DC=dc1 -e CASSANDRA_RACK=rack1 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_NUM_TOKENS=16 -v atlasmart-derived-cass-1-data:/var/lib/cassandra atlasmart-cassandra-derived:5.0.9# Wait for node 1 to become UN before starting peers.docker exec atlasmart-derived-cass-1 nodetool statusdocker inspect atlasmart-derived-cass-2 >/dev/null 2>&1 || docker run -d --name atlasmart-derived-cass-2 --hostname atlasmart-derived-cass-2 --network atlasmart-derived-net -e CASSANDRA_CLUSTER_NAME=atlasmart-derived-course -e CASSANDRA_DC=dc1 -e CASSANDRA_RACK=rack2 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_NUM_TOKENS=16 -e CASSANDRA_SEEDS=atlasmart-derived-cass-1 -v atlasmart-derived-cass-2-data:/var/lib/cassandra atlasmart-cassandra-derived:5.0.9docker inspect atlasmart-derived-cass-3 >/dev/null 2>&1 || docker run -d --name atlasmart-derived-cass-3 --hostname atlasmart-derived-cass-3 --network atlasmart-derived-net -e CASSANDRA_CLUSTER_NAME=atlasmart-derived-course -e CASSANDRA_DC=dc1 -e CASSANDRA_RACK=rack3 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_NUM_TOKENS=16 -e CASSANDRA_SEEDS=atlasmart-derived-cass-1 -v atlasmart-derived-cass-3-data:/var/lib/cassandra atlasmart-cassandra-derived:5.0.9# Continue only when all nodes are UN in dc1.docker exec atlasmart-derived-cass-1 nodetool versiondocker exec atlasmart-derived-cass-1 java -versiondocker exec atlasmart-derived-cass-1 nodetool statusdocker exec atlasmart-derived-cass-1 sh -lc "grep -E '^(materialized_views_enabled|cdc_enabled|materialized_views_on_repair_enabled):' /etc/cassandra/cassandra.yaml || true"
CQL · create the AtlasMart base/derived schema
CREATE KEYSPACE IF NOT EXISTS atlasmart_derivedWITH replication = {'class':'NetworkTopologyStrategy','dc1':3};CREATE TABLE IF NOT EXISTS atlasmart_derived.orders_by_day (    tenant_id text,    order_day date,    order_id uuid,    customer_id text,    created_at timestamp,    status text,    total decimal,    shipping_city text,    note text,    updated_at timestamp,    PRIMARY KEY ((tenant_id,order_day),order_id)) WITH compaction = {'class':'UnifiedCompactionStrategy'}  AND cdc = false;CONSISTENCY LOCAL_QUORUM;CREATE MATERIALIZED VIEW IF NOT EXISTS atlasmart_derived.orders_by_customer_mv ASSELECT tenant_id,order_day,order_id,customer_id,created_at,status,total,shipping_cityFROM atlasmart_derived.orders_by_dayWHERE tenant_id IS NOT NULL  AND order_day IS NOT NULL  AND order_id IS NOT NULL  AND customer_id IS NOT NULLPRIMARY KEY ((tenant_id,customer_id,order_day),order_id)WITH compaction = {'class':'UnifiedCompactionStrategy'};
CQL · seed deterministic AtlasMart orders
CONSISTENCY LOCAL_QUORUM;INSERT INTO atlasmart_derived.orders_by_day (tenant_id,order_day,order_id,customer_id,created_at,status,total,shipping_city,note,updated_at) VALUES ('tenant-a','2026-09-08',20000000-0000-0000-0000-000000000001,'cust-42','2026-09-08T08:00:00Z','PAID',129.90,'Baku','gift','2026-09-08T08:00:00Z');INSERT INTO atlasmart_derived.orders_by_day (tenant_id,order_day,order_id,customer_id,created_at,status,total,shipping_city,note,updated_at) VALUES ('tenant-a','2026-09-08',20000000-0000-0000-0000-000000000002,'cust-42','2026-09-08T08:10:00Z','PACKING',59.00,'Baku','fragile','2026-09-08T08:10:00Z');INSERT INTO atlasmart_derived.orders_by_day (tenant_id,order_day,order_id,customer_id,created_at,status,total,shipping_city,note,updated_at) VALUES ('tenant-a','2026-09-08',20000000-0000-0000-0000-000000000003,'cust-99','2026-09-08T08:20:00Z','PAID',210.00,'Ganja','priority','2026-09-08T08:20:00Z');INSERT INTO atlasmart_derived.orders_by_day (tenant_id,order_day,order_id,customer_id,created_at,status,total,shipping_city,note,updated_at) VALUES ('tenant-a','2026-09-07',20000000-0000-0000-0000-000000000004,'cust-42','2026-09-07T18:00:00Z','SHIPPED',90.50,'Baku','','2026-09-07T18:00:00Z');SELECT tenant_id,order_day,order_id,customer_id,status,totalFROM atlasmart_derived.orders_by_dayWHERE tenant_id='tenant-a' AND order_day='2026-09-08';
CQL · trace a base update, then read the view
CONSISTENCY LOCAL_QUORUM;TRACING ON;UPDATE atlasmart_derived.orders_by_daySET status='SHIPPED', updated_at='2026-09-08T09:00:00Z'WHERE tenant_id='tenant-a' AND order_day='2026-09-08'  AND order_id=20000000-0000-0000-0000-000000000001;TRACING OFF;SELECT order_id,status,totalFROM atlasmart_derived.orders_by_customer_mvWHERE tenant_id='tenant-a' AND customer_id='cust-42' AND order_day='2026-09-08';
bash · capture view/base operational state
docker exec atlasmart-derived-cass-1 nodetool viewbuildstatus atlasmart_derived orders_by_customer_mvdocker exec atlasmart-derived-cass-1 nodetool tablestats atlasmart_derived.orders_by_daydocker exec atlasmart-derived-cass-1 nodetool tablestats atlasmart_derived.orders_by_customer_mvdocker exec atlasmart-derived-cass-1 nodetool tpstats | grep -i -E 'view|mutation|batch' || true

The trace proves what the coordinator observed for the client mutation; it may not expose every internal MV message. Thread-pool/table metrics and subsequent reads provide complementary evidence. Exact stage names/counters are implementation-dependent and should be treated as diagnostics, not a public API.

3. Failure timeline: pause one replica, write at LOCAL_QUORUM, recover

RF=3 with LOCAL_QUORUM can tolerate one unavailable local replica for the base write. The view also has RF=3, but its replicas are chosen from the view token. On this three-node RF=3 lab all nodes store each replicated partition, which makes the recovery exercise compact without pretending the production topology is equally simple.

bash · pause only the disposable third node
docker pause atlasmart-derived-cass-3docker exec atlasmart-derived-cass-1 nodetool status
CQL · write while one replica is unavailable
CONSISTENCY LOCAL_QUORUM;UPDATE atlasmart_derived.orders_by_daySET status='DELIVERED', updated_at='2026-09-08T09:20:00Z'WHERE tenant_id='tenant-a' AND order_day='2026-09-08'  AND order_id=20000000-0000-0000-0000-000000000001;SELECT order_id,statusFROM atlasmart_derived.orders_by_customer_mvWHERE tenant_id='tenant-a' AND customer_id='cust-42' AND order_day='2026-09-08';
bash · recover, inspect, then run controlled full repair
docker unpause atlasmart-derived-cass-3# Wait until node 3 returns UN.docker exec atlasmart-derived-cass-1 nodetool status# Confirm the pinned configuration governing MV replay during streaming/repair.docker exec atlasmart-derived-cass-1 sh -lc "grep -E '^materialized_views_on_repair_enabled:' /etc/cassandra/cassandra.yaml || true"# Controlled lab repair; record network/disk cost and duration on your machine.docker exec atlasmart-derived-cass-1 nodetool repair --full atlasmart_deriveddocker exec atlasmart-derived-cass-1 nodetool viewbuildstatus atlasmart_derived orders_by_customer_mv

Current Cassandra documentation says materialized_views_on_repair_enabled defaults true: materialized-view mutations associated with streamed data are replayed through the write path/commit log. Setting it false can make streaming faster but introduces an explicit risk that, in extreme loss scenarios, SSTable data may not be reflected in the view. Treat this as a recovery contract, not a throughput knob.

4. Verify convergence from multiple coordinators

bash · compare base and view after recovery
for n in 1 2 3; do  echo "=== coordinator node $n : base ==="  docker exec atlasmart-derived-cass-$n cqlsh -e "CONSISTENCY LOCAL_ONE; SELECT order_id,status,updated_at FROM atlasmart_derived.orders_by_day WHERE tenant_id='tenant-a' AND order_day='2026-09-08' AND order_id=20000000-0000-0000-0000-000000000001;"  echo "=== coordinator node $n : view ==="  docker exec atlasmart-derived-cass-$n cqlsh -e "CONSISTENCY LOCAL_ONE; SELECT order_id,status FROM atlasmart_derived.orders_by_customer_mv WHERE tenant_id='tenant-a' AND customer_id='cust-42' AND order_day='2026-09-08';"done

Because LOCAL_ONE through a coordinator is not a forensic “read this exact SSTable” command, combine the result with tracing/status if you need to prove which replica answered. The acceptance criterion is application-visible convergence after recovery/repair—not a claim that every intermediate state was synchronous.

Wrong approach: “The base write returned LOCAL_QUORUM, therefore the MV is a synchronous read-your-write view.”

The feature does not grant relational indexed-view semantics. Base CL, MV maintenance, view build/read CL, hints/streaming, and failures are separate parts of the system. Measure the actual freshness behavior required by the application or choose an explicit projection with a defined lag contract.

5. Verification checklist

  • The base write succeeds/fails exactly as RF=3/LOCAL_QUORUM availability predicts.
  • All nodes are restored to UN.
  • View build status is complete before judging query results.
  • materialized_views_on_repair_enabled is recorded from the running image.
  • Base and view results are compared after explicit repair rather than assuming recovery from one successful read.

Check your understanding

  1. Does LOCAL_QUORUM base-write success guarantee an immediately fresh materialized-view read?
  2. Why can a view row use a different set/routing order of replicas than its base row?
  3. What does materialized_views_on_repair_enabled=true protect?
  4. What does nodetool viewbuildstatus prove?
  5. Why repair after the failure drill?
Review the answers

1. No. The base operation CL and derived-view maintenance/read state must be evaluated separately.

2. The view has a different partition key and therefore a different token calculation.

3. It replays MV mutations through the write path for streamed data such as repair, reducing the risk that streamed base data bypasses view maintenance.

4. Initial view-build progress/readiness, not every future freshness or failure guarantee.

5. Hints/normal propagation are not a substitute for explicitly validating anti-entropy/recovery behavior in an operational plan.

Production judgment

Do not choose a derived-data mechanism because it removes code from one layer. Evaluate who owns correctness, replay, backfill, deletion semantics, schema evolution, failure recovery, repair, observability, and rollback. For Cassandra materialized views, start with the documented fact that the feature is experimental and disabled by default. If an organization still evaluates it, test base/view key shape, update/delete mix, view build duration, write amplification, p95/p99 base-write latency, view-query latency/freshness, replica failures, streaming/repair behavior, disk headroom, compaction/tombstones, and upgrade behavior under the exact Cassandra patch. For explicit query tables or CDC/application projections, budget application/stream complexity, idempotency, checkpointing, retries, poison-event handling, backfill, reconciliation lag, and ownership. For SAI, measure selectivity/fanout, index build/disk cost, query limits, and failure behavior.

Replication factor and consistency level protect Cassandra replica operations but do not turn a derived-data pipeline into a global transaction. Security and tenant boundaries must be part of the query/table/index design; a local secondary access path is not authorization. Driver retry and speculative execution require idempotency analysis. Managed Cassandra services may disable, alter, or omit materialized views/CDC and may expose different repair/backup controls, so verify the actual service contract rather than assuming Apache Cassandra behavior. Keep migration reversible: dual-read comparison, backfill/reconciliation, explicit cutover criteria, and the ability to drop or stop the derived mechanism without losing the authoritative base data. Lesson 3 replaces implicit view ownership with an explicit denormalized projection and shows exactly what application-owned backfill and reconciliation buy you.

Summary and next bridge

Materialized-view maintenance is a distributed write/recovery path, not a magical synchronous local index. Base success, view freshness, build, streaming, and repair are separate evidence. Lesson 3 asks whether explicit denormalized tables provide a better operational contract even when they require more application code.

Authoritative references

Materialized-view behavior is version-sensitive and the feature remains experimental in Cassandra 5.0. Re-check these sources before promoting any example beyond a disposable lab.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.