Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Cassandra CQL DDL Commands

Data Definition Language (DDL) commands manage schema objects in Apache Cassandra. This section provides comprehensive reference documentation for all DDL statements.


DDL commands create, modify, and remove schema objects: keyspaces, tables, indexes, materialized views, user-defined types, functions, and aggregates. Unlike traditional relational databases where schema changes may require table locks or data migration, Cassandra applies schema modifications as metadata operations that propagate cluster-wide through the gossip protocol.


Cassandra maintains schema information in the system_schema keyspace. Unlike user keyspaces where replication factor determines how many nodes store data, every node in the cluster stores a complete copy of the schema. This ensures all nodes can independently validate queries and understand the data model without contacting other nodes.

The system_schema keyspace uses LocalStrategy for replication, meaning each node manages its own local copy. Schema synchronization occurs through the gossip protocol rather than normal read/write replication:

DESCRIBE KEYSPACE system_schema;
CREATE KEYSPACE system_schema
WITH REPLICATION = { 'class': 'LocalStrategy' }
AND DURABLE_WRITES = true;
Client → Coordinator → Local Schema Update → Gossip Propagation → All Nodes
│ │
└── system_schema tables updated └── Every node receives
complete schema

When a DDL statement executes:

  1. The coordinator node validates the DDL statement
  2. Schema metadata is written to local system_schema tables
  3. A schema mutation is broadcast via gossip to all nodes
  4. Each node applies the schema change to its local system_schema independently
  5. Schema agreement is reached when all nodes have matching schema versions

Schema vs Data Replication

Schema replication is independent of keyspace replication settings:

  • Schema: Always stored on every node (via gossip)
  • Data: Stored on nodes determined by partition key and replication factor

A keyspace with replication_factor: 3 stores data on 3 nodes, but the schema definition for that keyspace exists on all nodes in the cluster.

Before returning success for a DDL operation, Cassandra waits for schema agreement—a state where all live nodes have the same schema version. The schema version is a UUID computed from the hash of all schema metadata.

-- Check current schema versions across the cluster
SELECT schema_version FROM system.local;
-- Cassandra 4.0+: prefer system.peers_v2
SELECT peer, schema_version FROM system.peers_v2;
-- Legacy (may be disabled in newer versions):
-- SELECT peer, schema_version FROM system.peers;
-- Using nodetool
-- nodetool describecluster | grep -A 10 "Schema versions"

Schema Disagreement

If schema agreement cannot be reached within the timeout (default 10 seconds), the DDL statement returns a warning but the change may still propagate. Operations during schema disagreement may produce unpredictable results. Monitor nodetool describecluster for schema version mismatches.

Queries May Fail or Return Inconsistent Results

When nodes have different schema versions, queries can behave unpredictably:

ScenarioPotential Outcome
Query column not yet on all nodesInvalidRequestException on some nodes
Query new table before propagationTableNotFoundException on some nodes
Query with different column typesData corruption or read failures
Concurrent DDL operationsConflicting schemas across cluster

Real-world failure scenarios:

# DDL executed but not propagated
CREATE TABLE new_table (...);
# Immediate query may fail
SELECT * FROM new_table;
-- Node A: Success
-- Node B: UnconfiguredTableException: new_table

After DDL operations, ensure schema agreement before executing DML:

-- Configuration in cassandra.yaml
max_schema_agreement_wait_seconds: 10 -- Default wait time
-- Programmatic check (driver)
-- Most drivers provide schema agreement wait methods

Best practices:

  1. Wait for agreement: Use driver's schema agreement API after DDL
  2. Avoid concurrent DDL: Execute schema changes sequentially
  3. Monitor disagreement: Alert on prolonged schema version mismatches
  4. Rolling restarts: Wait for schema agreement between node restarts
  5. Repair after issues: Run nodetool repair -pr on nodes with stale schema
Terminal window
# Check for schema disagreement
nodetool describecluster
# Output showing disagreement:
# Schema versions:
# a1b2c3d4-... : [10.0.0.1, 10.0.0.2, 10.0.0.3]
# e5f6g7h8-... : [10.0.0.4] # This node has different schema!
Timeout ScenarioClient Action
DDL returns timeout warningCheck schema versions, retry if needed
DDL fails completelyVerify schema state, rerun DDL
Prolonged disagreement (> 30s)Investigate node health, repair or restart
Permanent disagreementRestart affected nodes with correct schema

Schema metadata is stored in the system_schema keyspace:

TableContents
keyspacesKeyspace definitions and replication settings
tablesTable schemas, options, and flags
columnsColumn definitions for each table
typesUser-defined type definitions
functionsUser-defined function code and signatures
aggregatesUser-defined aggregate definitions
indexesSecondary index metadata
viewsMaterialized view definitions
triggersTrigger configurations

Cassandra supports online schema modifications without blocking reads or writes. This capability stems from the storage engine architecture:

Adding Columns

New columns are metadata-only changes. Existing SSTables are not modified. When reading rows written before the column was added, Cassandra returns null for the new column.

Dropping Columns

Dropped columns are marked in metadata but data remains in SSTables until compaction. Reading dropped column data is prevented at the storage layer.

Altering Types

Type changes for non-primary-key columns are permitted if the new type is compatible (e.g., widening INT to BIGINT). The storage engine interprets existing bytes according to the new type.

Primary Key Immutability

Primary key columns (partition key and clustering columns) cannot be modified after table creation. The primary key structure determines data distribution and physical storage layout.

Schema changes propagate at gossip speed, typically completing cluster-wide within seconds. Factors affecting propagation:

  • Cluster size: Larger clusters require more gossip rounds
  • Network latency: Cross-datacenter propagation adds delay
  • Node health: Unresponsive nodes delay agreement
Typical propagation times:
- 3-node cluster: < 1 second
- 50-node cluster: 1-3 seconds
- 200-node multi-DC cluster: 3-10 seconds

Each schema modification increments the schema version. Cassandra tracks schema history but does not provide built-in rollback capabilities.

Schema Version Control

Maintain DDL scripts in version control. Use migration tools like cassandra-migration or application-level schema management to track and apply schema changes systematically.

Before significant schema modifications, create snapshots:

Terminal window
# Snapshot specific table before altering
nodetool snapshot -t pre_alter_backup keyspace_name table_name
# Snapshot entire keyspace
nodetool snapshot -t pre_migration keyspace_name

Cluster
└── Keyspace (namespace + replication configuration)
├── Table (column families)
│ ├── Column definitions
│ ├── Primary key structure
│ ├── Indexes
│ ├── Materialized views
│ └── Triggers
├── User-Defined Types
├── Functions
└── Aggregates

All schema object names follow these rules:

RuleUnquoted IdentifiersQuoted Identifiers
First characterLetter (a-z, A-Z)Any character
Subsequent charactersLetters, digits (0-9), underscore (_)Any character
Case sensitivityCase-insensitive (stored as lowercase)Case-sensitive (preserved exactly)
Reserved wordsNot allowedAllowed
Maximum length48 characters48 characters
-- Unquoted: stored as lowercase
CREATE TABLE UserEvents (...); -- Stored as 'userevents'
CREATE TABLE user_events (...); -- Stored as 'user_events'
-- Quoted: case and special characters preserved
CREATE TABLE "UserEvents" (...); -- Stored as 'UserEvents'
CREATE TABLE "user-events" (...); -- Stored as 'user-events'
CREATE TABLE "select" (...); -- Reserved word allowed when quoted
Object TypeMaximum LengthNotes
Keyspace name48 characters
Table name48 characters
Column name65535 charactersPractical limit ~100 for readability
Index name48 charactersAuto-generated: table_column_idx
Materialized view name48 characters
User-defined type name48 characters
UDT field name65535 characters
Function name48 characters
Aggregate name48 characters
Role name256 characters

Directory Name Limits

On disk, keyspace and table names become directory names. Some filesystems have path length limits (255 characters for most). Very long names combined with Cassandra's data directory path may exceed filesystem limits.

Unquoted identifiers (recommended for portability):

  • Letters: a-z, A-Z (ASCII only)
  • Digits: 0-9 (not as first character)
  • Underscore: _

Quoted identifiers allow additional characters:

  • Spaces and hyphens: "my table", "user-events"
  • Unicode characters: "日本語テーブル"
  • Special characters: "table.name", "column@v2"
  • Reserved words: "select", "table", "index"
-- Valid unquoted names
CREATE TABLE user_events_2024 (...);
CREATE TABLE t1 (...);
-- Invalid unquoted names (require quoting)
CREATE TABLE 2024_events (...); -- ERROR: starts with digit
CREATE TABLE user-events (...); -- ERROR: contains hyphen
CREATE TABLE select (...); -- ERROR: reserved word
-- Valid quoted equivalents
CREATE TABLE "2024_events" (...);
CREATE TABLE "user-events" (...);
CREATE TABLE "select" (...);

CQL reserves certain keywords that cannot be used as unquoted identifiers. Common reserved words include:

ADDALTERANDASASC
BATCHBEGINBYCOLUMNCREATE
DELETEDESCDROPEXISTSFROM
GRANTIFININDEXINSERT
INTOKEYSPACELIMITNOTNULL
OFONORORDERPRIMARY
REVOKEROLESELECTSETTABLE
TOTOKENTRUNCATEUPDATEUSE
USINGVALUESWHEREWITH

For a complete list, consult the CQL specification.

Best Practice

Use lowercase unquoted identifiers with underscores for maximum compatibility:

  • user_events
  • UserEvents ✓ (stored as userevents)
  • "user-events" - works but requires quoting everywhere
  • "select" - works but confusing
-- These are equivalent
CREATE TABLE users (...);
CREATE TABLE Users (...);
CREATE TABLE USERS (...);
-- This preserves case
CREATE TABLE "UserAccounts" (...);

Keyspaces define namespaces and replication configuration for tables.

CommandDescription
CREATE KEYSPACECreate a new keyspace with replication settings
ALTER KEYSPACEModify keyspace replication or options
DROP KEYSPACERemove a keyspace and all contents
USESet the current keyspace for the session

Tables store data as rows organized by primary key.

CommandDescription
CREATE TABLEDefine a new table with columns and primary key
ALTER TABLEAdd/drop columns or modify table options
DROP TABLERemove a table and all its data
TRUNCATERemove all rows from a table

Indexes enable queries on non-primary-key columns.

CommandDescription
CREATE INDEXCreate a secondary index or SAI index
DROP INDEXRemove an index

Cassandra supports indexing collection types (SET, LIST, MAP) to enable CONTAINS and element-specific queries:

Collection TypeIndex TargetQuery Enabled
SET<T>Column nameWHERE set_col CONTAINS value
LIST<T>Column nameWHERE list_col CONTAINS value
MAP<K,V>KEYS(column)WHERE map_col CONTAINS KEY key
MAP<K,V>VALUES(column)WHERE map_col CONTAINS value
MAP<K,V>ENTRIES(column)WHERE map_col[key] = value
FROZEN<collection>FULL(column)WHERE frozen_col = entire_value
-- Index SET elements
CREATE INDEX ON users (tags);
SELECT * FROM users WHERE tags CONTAINS 'premium';
-- Index MAP entries for key-value lookups
CREATE INDEX ON users (ENTRIES(attributes));
SELECT * FROM users WHERE attributes['role'] = 'admin';

UDT indexing capabilities depend on whether the type is frozen and the index implementation:

UDT StateIndex TypeCapability
FROZEN<udt>2i, SAIIndex entire frozen value for equality matching
Non-frozenSAI (5.0+)Index individual UDT fields

Frozen UDT indexing:

CREATE TYPE address (
street TEXT,
city TEXT,
zip TEXT
);
CREATE TABLE customers (
id UUID PRIMARY KEY,
home_address FROZEN<address>
);
-- Index entire frozen UDT
CREATE INDEX ON customers (home_address);
-- Query requires exact match of all fields
SELECT * FROM customers
WHERE home_address = {street: '123 Main St', city: 'NYC', zip: '10001'};

Non-frozen UDT field indexing (SAI, Cassandra 5.0+):

CREATE TABLE customers (
id UUID PRIMARY KEY,
home_address address -- non-frozen
);
-- Index specific UDT field
CREATE CUSTOM INDEX ON customers (home_address.city)
USING 'StorageAttachedIndex';
-- Query individual field
SELECT * FROM customers WHERE home_address.city = 'NYC';

Frozen vs Non-Frozen UDT Indexing

  • Frozen UDTs: Serialized as single value; only equality matching on complete UDT supported
  • Non-frozen UDTs: Individual fields addressable; SAI enables field-level indexing and queries
  • Legacy secondary indexes (2i) do not support non-frozen UDT field indexing

Materialized views maintain denormalized copies of base table data.

CommandDescription
CREATE MATERIALIZED VIEWCreate an auto-maintained view
ALTER MATERIALIZED VIEWModify view options
DROP MATERIALIZED VIEWRemove a materialized view

Triggers execute custom server-side Java code on data mutations.

CommandDescription
CREATE TRIGGERAttach a trigger to a table
DROP TRIGGERRemove a trigger from a table

UDTs define composite types with named fields.

CommandDescription
CREATE TYPEDefine a new user-defined type
ALTER TYPEAdd fields or rename existing fields
DROP TYPERemove a user-defined type

User-defined functions extend CQL with custom scalar operations.

CommandDescription
CREATE FUNCTIONCreate a user-defined scalar function
DROP FUNCTIONRemove a user-defined function

User-defined aggregates process multiple rows into a single value.

CommandDescription
CREATE AGGREGATECreate a user-defined aggregate
DROP AGGREGATERemove a user-defined aggregate

Design Principles

  • Design tables for specific query patterns (query-first modeling)
  • Denormalize data to avoid joins
  • Keep partition sizes under 100MB
  • Limit clustering columns to support required query patterns

Production Deployments

  1. Test schema changes in a staging environment first
  2. Create snapshots before applying changes
  3. Apply changes during low-traffic periods when possible
  4. Monitor schema agreement after changes
  5. Run repairs after replication changes
IssueCausePrevention
Schema disagreementNetwork issues, slow nodesMonitor cluster health, increase timeout if needed
Orphaned dataDropping columns without compactionRun nodetool compact after dropping columns
Replication lagChanging RF without repairAlways run repair after replication changes
Type mismatchesIncompatible column type changesOnly widen types (INT→BIGINT), never narrow

FeatureMinimum CQL VersionCassandra Version
Basic DDL3.02.0+
User-Defined Types3.02.1+
User-Defined Functions3.02.2+
Materialized Views3.43.0+
SASI Indexes3.43.4+
Storage-Attached Indexes3.4.75.0+
Vector Types3.4.75.0+