Cassandra Migration Best Practices

Migrating to Cassandra — whether from a relational database, another NoSQL system, or a legacy Cassandra version — requires careful planning. The data model, write patterns, and query design all need to align with Cassandra's architecture. Rushing migration without this alignment leads to poor performance, hot partitions, and expensive rework later.

Migration Scenarios

Scenario                               Key Challenge
──────────────────────────────────────────────────────────────
Relational DB → Cassandra              Schema redesign (query-first)
MySQL / PostgreSQL → Cassandra         Remove joins; denormalize
MongoDB → Cassandra                    Rethink document structure
Old Cassandra → New Cassandra          Schema changes; version upgrade
Adding Cassandra alongside SQL DB      Dual-write transition pattern

Phase 1: Model Before You Migrate

Never start a Cassandra migration by converting existing tables one-for-one. Instead, follow the query-first modeling approach: list all application queries, then design Cassandra tables to answer each query with a single partition read.

Relational model (DO NOT copy directly):
  users(id, name, email)
  orders(id, user_id, date, total)
  products(id, name, price)
  order_items(order_id, product_id, qty, unit_price)

Cassandra model (designed from queries):
  Q: Get all orders for user X (newest first)
    → orders_by_user PRIMARY KEY (user_id, order_date DESC)

  Q: Get order detail by order_id
    → orders_by_id PRIMARY KEY (order_id)

  Q: Get all items in an order
    → order_items PRIMARY KEY (order_id, product_id)

Phase 2: Build and Validate the Schema

-- Validate that every query is served by a table:
-- For each query in the application:
--   Can it be answered by partition key + clustering columns?
--   Does the table avoid ALLOW FILTERING on production queries?
--   Is the partition key high-cardinality enough?
--   Are partition sizes bounded (< 100 MB)?

Phase 3: Load Historical Data

Use bulk loading tools rather than INSERT statements for large data sets. Bulk loaders write SSTables directly to disk, bypassing the Cassandra write path and achieving 10–100× higher throughput than CQL INSERTs.

Option A: sstableloader

# Generate SSTables from a CSV using CQL:
cqlsh -f load_products.cql

# Stream pre-built SSTables into the cluster:
sstableloader -d 10.0.0.1 /path/to/sstables/ecommerce/products/

Option B: DSBulk (DataStax Bulk Loader)

# Load CSV into a Cassandra table at high speed:
dsbulk load \
  --contact-point 10.0.0.1 \
  --keyspace ecommerce \
  --table products \
  --url /data/products.csv \
  --header true \
  --schema.mapping "product_id, name, price, category"

Option C: Spark for Large-Scale ETL

# Read from source database, transform, write to Cassandra:
source_df = spark.read.jdbc(
  url="jdbc:postgresql://old-db:5432/ecommerce",
  table="orders",
  properties={"user": "admin", "password": "secret"}
)

transformed_df = source_df.select(
  col("user_id").alias("customer_id"),
  col("created_at").alias("order_date"),
  col("id").alias("order_id"),
  col("status"),
  col("total")
)

transformed_df.write \
  .format("org.apache.spark.sql.cassandra") \
  .options(table="orders_by_customer", keyspace="ecommerce") \
  .mode("append") \
  .save()

Phase 4: Dual-Write Transition Pattern

Running the old system and Cassandra in parallel lets you validate correctness before cutting over completely. The application writes to both systems and reads from the old system until confidence is high, then gradually shifts reads to Cassandra.

Stage 1: Dual-write
  Application → Old DB (primary reads + writes)
              → Cassandra (writes only)

Stage 2: Shadow reads
  Application → Old DB (primary reads + writes)
              → Cassandra (writes + shadow reads for comparison)
  Compare results; fix discrepancies.

Stage 3: Cassandra primary reads
  Application → Old DB (writes only for rollback safety)
              → Cassandra (primary reads + writes)

Stage 4: Full cutover
  Application → Cassandra only
  Old DB → archived or decommissioned

Phase 5: Schema Version Control

Track all CQL DDL changes in version-controlled migration scripts, just as you would with SQL migrations. Tools like Liquibase (with a Cassandra extension) or custom shell scripts can apply migrations in order.

# Migration script naming convention:
V001__create_keyspace.cql
V002__create_products_table.cql
V003__add_products_by_category_table.cql
V004__alter_orders_add_shipped_at.cql

# Apply with cqlsh:
cqlsh -f V004__alter_orders_add_shipped_at.cql

Phase 6: Schema Changes on a Live Cluster

Cassandra handles many schema changes without downtime, but some require care.

Schema Change              Safe?   Notes
──────────────────────────────────────────────────────────────
Add a column               Yes     Existing rows return null for it
Drop a column              Yes     Data removed at next compaction
Add a table                Yes     No impact on existing tables
Drop a table               Yes     Permanent; cannot undo
Change PK column type      No      Not supported in Cassandra
Rename PK column           No      Not supported
Change non-PK column type  Limited Only compatible type widening
Add a secondary index      Yes     Builds in background
Drop an index              Yes     No downtime
Change replication factor  Yes     Follow with nodetool repair

Common Migration Mistakes

Mistake                               Fix
──────────────────────────────────────────────────────────────
Copying relational tables 1-to-1      Redesign using query-first method
Using sequential IDs as PKs           Use UUID to avoid hotspots
Running analytics queries directly    Add a dedicated analytics DC
  on the production cluster           or use Spark
Using ALLOW FILTERING in production   Redesign the table for the query
Loading data with CQL INSERT loops    Use DSBulk, sstableloader, or Spark
Not testing restore from backups      Always verify backup recoverability
Skipping performance testing          Load test with realistic data volumes
  with real data volumes              before going to production

Migration Validation Checklist

✓ All production queries answered without ALLOW FILTERING
✓ No partition exceeds 100 MB or 100,000 rows
✓ Partition keys have high cardinality
✓ Row counts match between source and Cassandra
✓ p99 read and write latency meets SLAs under load
✓ Repair runs successfully across the cluster
✓ Backup and restore tested end-to-end
✓ Monitoring dashboards show green on all key metrics
✓ Runbook documented for rollback if cutover fails

Summary

A successful Cassandra migration starts with query-first data modeling before any data moves. Load historical data with bulk tools like DSBulk or Spark ETL rather than CQL INSERTs. Use the dual-write transition pattern to validate correctness before cutting over. Version-control all schema changes as migration scripts. Test partition sizes, query latency, and restore procedures before the production cutover. The most common failure in Cassandra migrations is applying a relational data model to a non-relational database — always redesign the schema around your access patterns first.

Leave a Comment

Your email address will not be published. Required fields are marked *