Moving petabytes is not trivial, but copying bytes is still easier than preserving ordering, deletes, conditional writes, TTL semantics, and query behavior while two databases disagree.

Situation

Legacy Cassandra estates carry repair, compaction, JVM, disk, backup, and upgrade work. Managed alternatives can transfer some of that burden to a provider, but they also impose different data models and failure contracts. Cassandra 5.0 offers Unified Compaction Strategy, Storage-Attached Indexing, and BTI-format SSTables, but an older estate must follow the supported rolling-upgrade path and intermediate-version requirements. Migration should be compared with a tested modernization plan, not with an intentionally neglected source cluster.

The Problem

A Cassandra partition is not one DynamoDB item. It is a set of clustering rows under one partition key and usually maps to multiple DynamoDB items under a shared partition key and sort keys. Bigtable orders keys lexicographically rather than hashing them. MongoDB maintains document indexes and routes through its shard key. Those differences affect pagination, ordering, atomicity, hotspots, TTLs, and cost.

The decision is therefore not “managed or self-managed.” It is whether the target can reproduce each required access pattern and correctness invariant at lower lifecycle risk.

Decide Before Moving Data

EvidenceModernize Cassandra whenMigrate when
Access patternsExisting CQL model fits and rewrite value is lowTarget model materially simplifies required queries
Operational burdenUpgrade, automation, and staffing are tractableRepair, upgrades, and capacity work remain a strategic burden
PortabilityMulti-cloud or infrastructure control is requiredProvider coupling is accepted explicitly
Correctness gapCassandra semantics are deeply embeddedSemantics can be mapped and tested per access pattern
EconomicsMeasured upgraded cluster wins at required headroomMeasured target configuration wins including migration and egress

No write-rate threshold answers this matrix.

A Correct Migration Architecture

flowchart TD
    A["Application mutation with immutable operation identity"] --> S["Cassandra system of record"]
    A --> L["Durable ordered change log"]
    L --> C["Bounded idempotent consumers"]
    C --> T["Target database"]
    S --> B["Checkpointed historical backfill"]
    B --> T
    S --> V["Version-aware parity verifier"]
    T --> V
    V --> G{"Cutover gates satisfied"}
    G -->|No| R["Repair divergence and continue"]
    G -->|Yes| P["Canary reads then reversible traffic shift"]

The application commits the source mutation and a durable change record using a design that cannot silently lose the second step—for example, an existing transactional outbox boundary or Cassandra CDC with a validated delivery pipeline. Each record carries operation_id, entity key, monotonic domain version, operation type, event time, and schema version. Consumers are bounded, retryable, and idempotent; the target stores the last applied version and rejects stale reordering.

Backfill reads immutable checkpoints and emits the same canonical mutation format. It must not overwrite a newer streamed mutation: apply only when the incoming version is newer. Deletes and TTL expiry require explicit representation; absence is not proof of deletion.

Safe comparator pattern

import json
from dataclasses import dataclass
from decimal import Decimal
from hashlib import sha256
from typing import Mapping

@dataclass(frozen=True)
class CanonicalRecord:
    version: int
    status: str
    balance: Decimal

def canonicalize(row: Mapping[str, object]) -> CanonicalRecord:
    return CanonicalRecord(
        version=int(row["version"]),
        status=str(row["status"]),
        balance=Decimal(str(row["balance"])),
    )

def fingerprint(record: CanonicalRecord) -> str:
    payload = json.dumps(
        {
            "balance": format(record.balance.normalize(), "f"),
            "status": record.status,
            "version": record.version,
        },
        ensure_ascii=False,
        separators=(",", ":"),
        sort_keys=True,
    )
    return sha256(payload.encode("utf-8")).hexdigest()

def equal_at_version(source: Mapping[str, object], target: Mapping[str, object]) -> bool:
    left = canonicalize(source)
    right = canonicalize(target)
    return left.version == right.version and fingerprint(left) == fingerprint(right)

Production comparison workers should use bounded executors or genuinely asynchronous database clients—never unbounded create_task calls around blocking drivers. Emit keys as keyed hashes, not raw customer identifiers or field values. Use exact decimal or canonical integer units for money, not floats. Track mismatch class and age without logging PII.

Cutover gates are workload-derived: zero unexplained mismatches for all sampled mutation classes, bounded replication age, completed delete and TTL tests, successful restore, target capacity under a failure domain, and rollback proven in rehearsal. An arbitrary “99.999% parity” is not acceptable when the missing fraction may contain deletes or financial records.

Target-Specific Translation

  • DynamoDB: decompose clustering rows into items, preserve ordering in the sort key, model the 400 KB item limit, base and GSI capacity, conditional writes, and warm throughput. DynamoDB constraints.
  • Bigtable: preserve query locality without timestamp-first hotspots. Google recommends query-driven row keys and warns against sequential prefixes. Bigtable schema design.
  • MongoDB: choose a shard key from routing and distribution requirements; model index maintenance, document growth, transaction scope, oplog window, and balancer behavior. MongoDB sharding.
  • Cassandra 5.0: upgrade separately from application redesign where possible. Apache’s binary releases are built with Java 11 and can run on Java 11 or 17; builds produced with Java 17 cannot run on Java 11. Pin the exact distribution and JDK combination, follow the supported upgrade path, and validate snapshots before rewriting SSTables. Apache Cassandra Java support.

In Practice

Use public cases only for what they document. Discord published its migration from Cassandra to ScyllaDB and the application-side work required; it is evidence for a compatibility-oriented modernization path. Discord engineering.

Intuit’s documented AWS case is a migration of a 120 TB, 66-node Cassandra workload to Amazon Keyspaces, not DynamoDB. It supports the value of a Cassandra-compatible managed target; it does not prove DynamoDB schema translation or Global Tables behavior. Intuit and Amazon Keyspaces.

Spotify’s published engineering material documents Cassandra use and broader infrastructure modernization, but the sources reviewed do not establish the specific Cassandra-to-Bigtable migration narrative previously attributed to Spotify. That claim should remain excluded unless a primary source appears.

Where It Breaks

Failure modeMechanismControl
Lost dual writeProcess fails between independent writesDurable log or outbox boundary; lag alerting
Reordered applyRetry or backfill arrives after newer stateMonotonic version and conditional target update
Delete resurrectionBackfill treats absence as stateExplicit tombstone or delete event with version
Comparator leakLogs include raw customer dataKeyed hashes, redaction, restricted mismatch store
Unbounded shadow workBlocking clients exhaust threads or tasksBounded pools, timeouts, cancellation, sampling
Premature cutoverAggregate counts hide semantic mismatchesPer-class gates, canary reads, reversible routing
Early decommissionRollback data and evidence disappearRetain source through the audited rollback window

What to Do Next

  • Problem: Migration estimates count bytes but omit semantic and correctness work.
  • Solution: Compare in-place modernization with target-specific translation, then migrate through an ordered durable log.
  • Proof: Demonstrate idempotency, reordering safety, delete and TTL handling, restoration, failed-zone capacity, and rollback.
  • Action: Inventory every CQL access pattern and invariant; reject any target design that cannot map each one explicitly.