Chapter 08 · CQL Data Types, Collections, Tuples, UDTs, Static Columns, and Frozen Values
Static Columns for Partition-Wide Metadata and Modeling Shared Attributes
Use static columns for metadata genuinely shared by one partition, prove their visibility across clustering rows, and avoid treating them as global table settings.
Learning outcomes
AtlasMart stores many cart-line rows for one customer/day partition, but the customer's display name and cart policy are identical for every clustering row in that partition. Duplicating those values on every line wastes storage and multiplies update fan-out. A static column stores one partition-wide value while preserving the clustered row model—but it is not global table metadata.
Explain static-column scope as one value per partition, repeated logically across clustering rows.
Create and update static values using the partition key without rewriting every clustering row.
Prove that two partitions can have different static values even in the same table.
Identify schema restrictions: static columns require clustering columns and cannot be primary-key columns.
Use static columns only when the shared attribute truly has the same lifecycle/consistency boundary as the partition.
The mandatory labs use the pinned
cassandra:5.0.9 image. Java 17,
cqlsh, and nodetool are the versions
bundled by that image. The course topology is three disposable
nodes (atlasmart-cass-1..3) in cluster
atlasmart-course, datacenter dc1,
racks rack1..rack3, 16 vnodes per node,
replication factor (RF) 3, and LOCAL_QUORUM for
consistency-sensitive examples. Authentication, client TLS,
internode TLS, and remote JMX are not enabled in this isolated
learning network; production must secure those boundaries
separately. Chapter 08 uses keyspace
atlasmart_types; new tables explicitly use
UnifiedCompactionStrategy (UCS), default table TTL is zero
unless stated, and gc_grace_seconds is not
changed. Storage-Attached Indexing (SAI) and vector search are
not required. Apache Cassandra Java Driver 4.19.3 is used only
in optional decoding snippets; the mandatory path remains
free/local with cqlsh.
The commands and CQL below are documentation- and syntax-reviewed but were not executed in this generation environment. Treat output as an expected shape, then capture exact UUIDs, time values, SSTable paths, tombstone counters, and driver-decoded values on your machine. If three nodes are too heavy, use one disposable node and RF=1 to learn type/mutation semantics, but do not treat that reduced topology as evidence about RF=3 availability or repair behavior.
1. Static is partition-wide, not table-wide
For a table with partition key
(customer_id, day) and clustering columns
cart_version, sku, a static column has one stored
value for the whole customer/day partition. A
SELECT * displays that static value beside each
clustering row because it is visible to every row in the
partition. Writing a new static value changes what all rows in
that partition observe. Another partition can store a completely
different static value.
2. Lab: prove static visibility across clustering rows
# Verify the shared course lab if it already exists.docker exec atlasmart-cass-1 nodetool versiondocker exec atlasmart-cass-1 nodetool status# Standalone local recreation path. Skip resources that already 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 for node 1 to answer 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 nodes show UN.docker exec atlasmart-cass-1 nodetool statusdocker exec atlasmart-cass-1 cqlsh -e "CREATE KEYSPACE IF NOT EXISTS atlasmart_types WITH replication = {'class':'NetworkTopologyStrategy','dc1':3};"docker exec atlasmart-cass-1 cqlsh -e "DESCRIBE KEYSPACE atlasmart_types"
CREATE TABLE atlasmart_types.cart_lines_by_customer_day ( customer_id uuid, day date, cart_version int, sku text, customer_name text static, pricing_policy text static, quantity int, unit_price decimal, PRIMARY KEY ((customer_id,day),cart_version,sku)) WITH CLUSTERING ORDER BY (cart_version DESC,sku ASC) AND compaction = {'class':'UnifiedCompactionStrategy'};INSERT INTO atlasmart_types.cart_lines_by_customer_day(customer_id,day,cart_version,sku,customer_name,pricing_policy,quantity,unit_price)VALUES(11111111-1111-1111-1111-111111111111,'2026-09-07',1,'SKU-1','A. Customer','standard',2,10.00);INSERT INTO atlasmart_types.cart_lines_by_customer_day(customer_id,day,cart_version,sku,quantity,unit_price)VALUES(11111111-1111-1111-1111-111111111111,'2026-09-07',1,'SKU-2',1,25.00);SELECT * FROM atlasmart_types.cart_lines_by_customer_dayWHERE customer_id=11111111-1111-1111-1111-111111111111 AND day='2026-09-07';
Both line rows should display the same
customer_name and pricing_policy even
though only the first insert supplied them. That is the defining
static-column behavior.
UPDATE atlasmart_types.cart_lines_by_customer_daySET pricing_policy='holiday-2026'WHERE customer_id=11111111-1111-1111-1111-111111111111 AND day='2026-09-07';INSERT INTO atlasmart_types.cart_lines_by_customer_day(customer_id,day,cart_version,sku,customer_name,pricing_policy,quantity,unit_price)VALUES(11111111-1111-1111-1111-111111111111,'2026-09-08',1,'SKU-1','A. Customer','standard',1,10.00);SELECT customer_id,day,cart_version,sku,customer_name,pricing_policyFROM atlasmart_types.cart_lines_by_customer_dayWHERE customer_id=11111111-1111-1111-1111-111111111111 AND day='2026-09-07';SELECT customer_id,day,cart_version,sku,customer_name,pricing_policyFROM atlasmart_types.cart_lines_by_customer_dayWHERE customer_id=11111111-1111-1111-1111-111111111111 AND day='2026-09-08';
The first partition should now expose holiday-2026;
the second remains standard. This disproves the
common misconception that a static column is a table-level
setting.
3. Boundary cases that should change the design
-- Invalid: no clustering column, so every partition already has one row.CREATE TABLE atlasmart_types.bad_static_no_clustering ( id uuid PRIMARY KEY, global_note text static);-- Invalid: primary-key columns cannot be static.CREATE TABLE atlasmart_types.bad_static_key ( tenant_id uuid static, seq int, value text, PRIMARY KEY (tenant_id,seq));
Static scope is the partition. If the application needs one global configuration object, model and govern that separately. If the value changes on a different lifecycle from the partition rows, coupling it into the same partition can create surprising write paths and stale application assumptions.
Another edge case is deletion: removing a static value creates a deletion marker for that partition-wide cell. Frequent static-value churn can therefore contribute tombstones even though the visible table shows the value repeated on many rows.
4. Production judgment and chapter synthesis
Static columns are a storage optimization and modeling signal for attributes truly shared by one partition. They can reduce duplicate cells and update fan-out, but they also couple the shared value's lifecycle and consistency semantics to that partition. Large static blobs still cost memory/network on reads. Security/tenant isolation must follow the partition key: a static value is visible to every row in its partition, so a mixed-tenant partition would be a modeling/security error before it is a static-column problem.
Chapter 08's broader rule is now complete: choose scalar types for value semantics; collections only for bounded multi-values; tuples/UDTs for controlled structure; frozen when whole-value replacement is the intended mutation unit; static when one value is genuinely partition-wide. Each choice changes cell layout, tombstone/mutation cost, schema evolution, driver codecs, and failure/retry behavior while leaving the partition-key replication contract intact.
Verification checklist
- Rows in the same partition expose the same static metadata.
- Updating a static column with only the complete partition key changes the value visible beside all clustering rows.
- A second partition can hold a different static value.
- Static-without-clustering and static-primary-key schemas are rejected.
- You can state why global configuration belongs outside a partition-scoped static column.
Check your understanding
- What is the scope of a static column?
- Does a static update rewrite every clustering row?
- Can two partitions have different static values?
- Why does Cassandra reject static columns on a table with no clustering columns?
- When should a shared attribute stay in a separate table instead?
Review the answers
1. One partition: all clustering rows sharing the partition key observe the same static value.
2. No. The static cell is stored once for the partition even though SELECT output displays it with each row.
3. Yes. Static does not mean table-global.
4. Such a partition already represents one row, so every regular column is effectively partition-wide.
5. When its scope/lifecycle, security boundary, or update consistency does not match the partition containing the clustered rows.
# Destructive only to this disposable chapter keyspace.docker exec atlasmart-cass-1 cqlsh -e "DROP KEYSPACE IF EXISTS atlasmart_types;"# Keep shared course containers for Chapter 09, or remove them for a full reset:# docker rm -f atlasmart-cass-1 atlasmart-cass-2 atlasmart-cass-3# docker volume rm atlasmart-cass-1-data atlasmart-cass-2-data atlasmart-cass-3-data# docker network rm atlasmart-cassandra
Summary and next bridge
CQL types determine more than validation: they define storage cells, mutation granularity, tombstones, schema evolution, and driver codecs. Chapter 09 follows one of those typed writes through the coordinator, replica selection, commit log, memtable, acknowledgment path, and consistency-level decision.
Authoritative references
- CQL data types — scalar, collection, tuple, user-defined type, frozen, and literal semantics.
- Creating collections — bounded collection guidance and current collection guardrails.
- CQL data definition — static-column behavior, table/type definitions, and schema restrictions.
- CREATE TABLE reference — frozen/non-frozen UDT and static-column examples.
- Tombstones — deletion markers, grace, reads, and compaction implications.
- Apache Cassandra downloads — current server and Java-driver release baselines.