Cassandra Data Modeling: From CQL to Production
Master Cassandra data modeling with CQL.
20+ years shipping high-throughput database systems. Written from production experience, not tutorials.
- ✓Basic understanding of SQL (SELECT, INSERT, CREATE TABLE)
- ✓Familiarity with Cassandra basics (nodes, replication, consistency)
- ✓Cassandra installed locally or access to a cluster
- Cassandra data modeling is query-driven: design tables based on your access patterns.
- Primary key consists of partition key (for data distribution) and clustering columns (for sorting within a partition).
- Denormalization and duplication are expected; joins are not supported.
- Use compound primary keys and clustering order to optimize queries.
- Avoid large partitions; model to keep partition size manageable.
Think of Cassandra like a filing cabinet with many drawers (nodes). Each drawer holds folders (partitions). The partition key tells you which drawer to open. Inside a folder, papers are sorted by clustering columns. To find something quickly, you need to know exactly which drawer and the order of papers. You can't search across drawers easily, so you might keep copies of the same paper in multiple drawers organized differently.
Imagine you're building a real-time analytics platform that tracks millions of user events per second. You need a database that can handle massive write throughput, scale horizontally, and remain available even if some servers fail. Traditional relational databases with their joins, transactions, and normalized schemas would buckle under this load. Enter Apache Cassandra: a distributed NoSQL database designed for high availability and linear scalability. But Cassandra's power comes with a price: you must abandon decades of relational data modeling intuition. In Cassandra, you don't model data based on entities and relationships; you model based on queries. This tutorial will take you from CQL basics to production-ready data modeling patterns. You'll learn how to design primary keys, use clustering for sorting, denormalize for performance, and avoid common pitfalls like hot partitions and tombstones. By the end, you'll be able to design schemas that scale to petabytes without breaking a sweat.
Understanding CQL and Cassandra's Data Model
Cassandra Query Language (CQL) looks like SQL, but its data model is fundamentally different. In Cassandra, a table is a logical grouping of rows, but rows are distributed across nodes based on the partition key. Each row is uniquely identified by its primary key, which consists of a partition key (one or more columns) and optional clustering columns. The partition key determines which node stores the data, while clustering columns define the sort order within a partition. Unlike SQL, you cannot join tables; you must denormalize data into a single table per query pattern. CQL supports SELECT, INSERT, UPDATE, DELETE, and batch operations, but with restrictions: WHERE clauses can only include primary key columns (unless using ALLOW FILTERING, which is discouraged). Understanding these constraints is the first step to effective data modeling.
Designing Primary Keys for Query Patterns
In Cassandra, you design tables to serve specific queries. For example, if you need to fetch recent events for a user, you'd use a table with partition key user_id and clustering column event_time sorted descending. If you need to query events by type across users, you'd need a different table with partition key event_type and clustering column event_time. This is called 'query-first design.' You may end up with multiple tables storing the same data in different orders. This duplication is intentional and necessary for performance. When designing, consider the cardinality of your partition key: too low (e.g., boolean) leads to large partitions; too high (e.g., UUID) leads to many small partitions. Aim for a partition size between 10 MB and 100 MB. Use compound partition keys to achieve this balance.
Clustering Columns and Sort Order
Clustering columns control the physical order of rows within a partition. You can specify ascending or descending order per column. This is crucial for queries that need the most recent data first. For example, CLUSTERING ORDER BY (event_time DESC) ensures that the latest event is at the top of the partition. You can also use multiple clustering columns for hierarchical sorting, like (event_time DESC, user_id ASC). However, you cannot change the clustering order after table creation; you must drop and recreate the table. Also, note that clustering columns are part of the primary key, so they enforce uniqueness. If you need multiple rows with the same clustering values, you must add a distinguishing column (e.g., a UUID) to the clustering key.
Denormalization and Data Duplication
Cassandra does not support joins. To retrieve related data in a single query, you must denormalize: store all the data you need in one table. This means duplicating data across multiple tables. For example, in an e-commerce system, you might have an orders_by_user table and an orders_by_status table, both containing the same order details but organized differently. Denormalization improves read performance but increases write complexity and storage. You must ensure consistency across duplicates, often by using batch writes or application-level logic. Also, consider using user-defined types (UDTs) to group related fields, but be cautious: UDTs cannot be indexed and can complicate schema evolution.
Secondary Indexes and Materialized Views
Cassandra offers secondary indexes and materialized views as alternatives to denormalization, but they come with caveats. Secondary indexes are best for low-cardinality columns (e.g., status) and are not recommended for high-cardinality columns (e.g., user_id). They can cause performance issues because they involve querying all nodes. Materialized views automatically maintain a denormalized copy of data based on a new primary key. However, they have limitations: you cannot use them with certain data types (e.g., collections), and they can impact write performance. In production, many teams prefer explicit denormalization over materialized views for better control and predictability.
Handling Time Series Data
Time series data is a common use case for Cassandra. The key challenge is to avoid unbounded partition growth. Use time bucketing: include a date or month in the partition key. For example, PRIMARY KEY ((sensor_id, date), hour). This limits each partition to one day's data. You can also use a sliding window of buckets (e.g., last 30 days) and delete old partitions. Another pattern is to use a separate table for each time granularity (e.g., raw events, hourly aggregates). For high-frequency data, consider using a wider row with multiple columns per time slot (e.g., one column per minute) to reduce partition count.
Common Pitfalls and Anti-Patterns
Several anti-patterns plague Cassandra data modeling. 1) Large partitions: Partitions over 100 MB cause garbage collection pauses and slow repairs. 2) Hot spots: Uneven data distribution due to poor partition key choice (e.g., using a single partition key for a popular entity). 3) Too many tombstones: Frequent deletes or updates with TTL can create tombstones that slow reads. 4) ALLOW FILTERING: Using this in production queries indicates a missing index or table design. 5) Overusing batches: Large batches (more than 10 statements) can cause coordinator overload. 6) Ignoring compaction: Default compaction strategies may not suit your workload; use SizeTieredCompactionStrategy for write-heavy, LeveledCompactionStrategy for read-heavy.
nodetool cfstats. If it exceeds 20%, investigate your delete patterns.Production Best Practices
In production, follow these best practices: 1) Test with realistic data volumes using tools like cassandra-stress. 2) Monitor partition size with nodetool tablehistograms. 3) Use consistency level LOCAL_QUORUM for most operations to balance availability and consistency. 4) Set appropriate compaction strategy: LeveledCompactionStrategy for read-heavy, SizeTieredCompactionStrategy for write-heavy. 5) Use TTL for time-series data to avoid manual deletes. 6) Avoid cross-datacenter writes if possible; use local consistency. 7) Plan for schema evolution: Adding columns is cheap, but changing primary keys requires creating new tables. 8) Use lightweight transactions (LWT) sparingly; they are expensive. 9) Backup regularly using nodetool snapshot. 10) Design for failure: Assume nodes will fail and ensure your application can handle retries.
The Hot Partition That Took Down a Social Feed
- Avoid high-cardinality partition keys that lead to hotspots.
- Use composite partition keys to distribute data evenly.
- Monitor partition size; keep partitions under 100 MB.
- Design for access patterns: if a user is popular, spread their data.
- Test with realistic data volumes before production.
nodetool tablehistograms. If partitions are large, redesign partition key to add more granularity.nodetool cfstats. If tombstone ratio > 20%, adjust compaction strategy or TTL settings.nodetool tablehistograms keyspace tableSELECT * FROM system.size_estimates WHERE keyspace_name='ks' AND table_name='tbl';| File | Command / Code | Purpose |
|---|---|---|
| create_table.cql | CREATE TABLE user_events ( | Understanding CQL and Cassandra's Data Model |
| query_patterns.cql | CREATE TABLE user_recent_events ( | Designing Primary Keys for Query Patterns |
| clustering_example.cql | CREATE TABLE sensor_data ( | Clustering Columns and Sort Order |
| denormalization.cql | CREATE TABLE orders_by_user ( | Denormalization and Data Duplication |
| indexes_views.cql | CREATE INDEX ON orders_by_user (status); | Secondary Indexes and Materialized Views |
| time_series.cql | CREATE TABLE sensor_readings ( | Handling Time Series Data |
| anti_patterns.cql | SELECT * FROM user_events WHERE event_type = 'click' ALLOW FILTERING; | Common Pitfalls and Anti-Patterns |
| best_practices.cql | CREATE TABLE events ( | Production Best Practices |
Key takeaways
Common mistakes to avoid
3 patternsUsing ALLOW FILTERING in production queries.
Using a single column as partition key for a high-traffic entity (e.g., celebrity user).
Creating large batches with many statements.
Interview Questions on This Topic
Explain the difference between partition key and clustering key in Cassandra.
Frequently Asked Questions
20+ years shipping high-throughput database systems. Written from production experience, not tutorials.
That's Cassandra. Mark it forged?
4 min read · try the examples if you haven't