Skip to content

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

Sidecar CDC Outbox Pattern

CEP-44 introduces official Kafka integration for Cassandra CDC through the Sidecar component. This provides an alternative to the transactional outbox pattern with built-in deduplication and Schema Registry support.

Status: Accepted

CEP-44 has been accepted for implementation. Check Cassandra Sidecar release notes for availability.


Cassandra Sidecar is a separate process that runs alongside Cassandra nodes, providing management and operational capabilities without modifying the core database. CEP-44 extends Sidecar to include CDC-to-Kafka streaming.

Sidecar CDC components between a Cassandra node and KafkaSidecar CDC components between a Cassandra node and KafkaCassandra NodeSidecar ProcessSchema RegistryCassandracdc_rawCDC ReaderDeduplicationKafka ProducerOrder v1 (id=1)Order v2 (id=2)KafkaCDC logsread segmentsmutationsdeduplicatedeventsregister/lookup
ComponentDescription
CDC ReaderReads commit log segments from cdc_raw directory
Deduplication EngineEliminates duplicate mutations across replicas
Kafka ProducerPublishes events to Kafka topics
Schema Registry ClientManages schema versioning (pluggable)
State ManagerTracks processing position for recovery

The Sidecar approach addresses key limitations of other CDC solutions:

ChallengeDebeziumSidecar CDC
DeduplicationConsumer must deduplicateBuilt-in deduplication
Consistency validationNot availableConfigurable replica acknowledgment (not equivalent to CQL consistency levels)
Schema managementSeparate configurationIntegrated Schema Registry
Official supportThird-partyApache Cassandra project
ConfigurationExternal config filesCQL syntax + REST API

With replication factor 3, each mutation appears on 3 nodes. Traditional CDC solutions (like Debezium) push this problem to downstream consumers. Sidecar CDC deduplicates at the source:

Deduplication of replicated mutations across three Sidecar instancesDeduplication of replicated mutations across three Sidecar instancesCassandra Cluster (RF=3)Sidecar ClusterCoordinationNode 1Node 2Node 3Sidecar 1Sidecar 2Sidecar 3Token ownershipMutation hashesKafka(1 event per mutation)Each mutation published onceregardless of replication factorCDCCDCCDCdeduplicated

Deduplication mechanism:

  1. Each Sidecar owns a token range portion
  2. Mutations are hashed (MD5) and cached
  3. Only mutations meeting consistency requirements are published
  4. Coordination ensures each mutation is published exactly once per token range

Sidecar CDC segment processing flowSidecar CDC segment processing flowRead CDC segment from diskDeserialize mutationsEach Sidecar owns portion of ringFilter by token rangeCheck mutation hash cacheAlready seen?yesnoSkip (duplicate)Wait for sufficient replicase.g., LOCAL_QUORUM requires2 of 3 replicasValidate consistency levelConsistency met?yesnoConvert to Avro/ProtobufPublish to KafkaCache mutation hashRetry until timeoutStore pendingUpdate CDC state

Sidecar maintains processing state in Cassandra itself:

-- Configuration storage
CREATE TABLE sidecar_internal.configs (
service TEXT,
config MAP<TEXT, TEXT>,
PRIMARY KEY (service)
);
-- CDC processing state
CREATE TABLE sidecar_internal.cdc_state (
job_id TEXT,
split SMALLINT,
start VARINT,
end VARINT,
state BLOB,
PRIMARY KEY ((job_id), split)
) WITH default_time_to_live = 2592000; -- 30-day retention

State contents:

ComponentDescription
Segment markerLast processed segment ID and byte offset
Mutation cacheMD5 hashes of recent mutations awaiting replica confirmation
Token rangesOwned ranges for this Sidecar instance

Terminal window
# Create CDC configuration
curl -X PUT http://sidecar:9043/api/v1/services/cdc/config \
-H "Content-Type: application/json" \
-d '{
"kafka.bootstrap.servers": "kafka:9092",
"schema.registry.url": "http://schema-registry:8081",
"tables": ["keyspace.orders"],
"consistency.level": "LOCAL_QUORUM"
}'
# Get current configuration
curl http://sidecar:9043/api/v1/services/cdc/config
# Disable CDC streaming
curl -X DELETE http://sidecar:9043/api/v1/services/cdc/config

CEP-44 leverages CEP-38's management syntax for declarative configuration:

-- Create a Kafka sink
CREATE DATA_SINK order_events_sink
WITH uri = 'kafka://kafka:9092/order-events'
AND options = {
'schema.registry.url': 'http://schema-registry:8081',
'value.serializer': 'avro'
};
-- Enable CDC streaming for a table
CREATE DATA_SOURCE cdc ON TABLE keyspace.orders
WITH sink = order_events_sink
AND options = {
'consistency.level': 'LOCAL_QUORUM'
};
-- Disable CDC streaming
DROP DATA_SOURCE cdc ON TABLE keyspace.orders;

Sidecar CDC integrates with Schema Registry for schema evolution:

Schema registration and event publishing by Sidecar CDCSidecarSchema RegistryKafkaSidecarSidecarSchema RegistrySchema RegistryKafkaKafkaSchema RegistrationDetect table schemaGenerate deterministic ID(MD5 of schema)Register schemaConfirm IDEvent PublishingPublish event[schema_id in header]Schema ChangeDetect ALTER TABLERegister new versionNew IDPublish with new schema_id

Unlike random assignment, Sidecar generates schema IDs deterministically from table structure:

schema_id = MD5(keyspace + table + column_definitions)

Benefits:

  • Schema can be re-registered if Registry is unavailable
  • Consistent IDs across Sidecar restarts
  • No coordination required for ID assignment
Cassandra TypeAvro TypeNotes
asciistring
bigintlong64-bit signed integer
blobbytes
booleanboolean
dateintDays since epoch
decimalbytesAvro decimal logical type
doubledouble64-bit IEEE 754
durationbytesThree signed integers (months, days, nanoseconds)
floatfloat32-bit IEEE 754
frozenbytesSerialized bytes
inetstringIP address as string
intint
list<T>arrayElement type must be supported
map<K,V>mapKey/value types must be supported
set<T>arrayConverted to array
smallintint
textstring
timelongNanoseconds since midnight
timestamplongMilliseconds since epoch
timeuuidstring
tinyintint
tuplerecordFixed-order fields as Avro record
uuidstring
varcharstring
varintbytesArbitrary precision integer
UDT (frozen)recordNested record with supported types

Unsupported Types

  • Counter columns - Require read-before-write semantics
  • Unfrozen collections - Require database reads; index information in mutations not usable by downstream consumers
  • Custom types - No automatic conversion

Sidecar CDC provides at-least-once delivery:

  • Events are published at least once
  • Failures after publish but before state update may cause redelivery
  • Consumers should be idempotent

Unlike basic CDC which captures all replica writes, Sidecar validates consistency:

Configuration: consistency.level = LOCAL_QUORUM (RF=3)
Mutation arrives on Node 1 → Wait
Mutation arrives on Node 2 → 2/3 = QUORUM met → Publish
Mutation arrives on Node 3 → Already published (deduplicated)

Failure handling:

ScenarioBehavior
Insufficient replicas within timeoutMutation dropped (logged)
Sidecar crashResume from last checkpoint
Kafka unavailableBackpressure, retry with backoff
Schema Registry unavailableContinue with cached schemas

AspectTransactional OutboxSidecar CDC
Write overheadExtra outbox table writeNone (uses existing CDC)
AtomicitySame-partition batchesEventual (consistency level)
DeduplicationConsumer responsibilityBuilt-in
Schema evolutionManualIntegrated
OrderingControllablePer-partition in Kafka
LatencyPolling intervalNear real-time
Operational complexityApplication codeSidecar deployment

Use Transactional Outbox when:

  • Strict atomicity required between entity and event
  • Custom event schema independent of table structure
  • Fine-grained control over event content
  • No Sidecar deployment available

Use Sidecar CDC when:

  • Table mutations map directly to events
  • Built-in deduplication preferred
  • Schema Registry integration needed
  • Near real-time latency required
  • Operational simplicity preferred over application complexity

MetricDescriptionAlert Threshold
cdc.publish.latencyTime from mutation to Kafka publish> 1 second
cdc.events.per.secondEvent throughputBelow baseline
cdc.consistency.failuresMutations not meeting consistency> 0
cdc.dropped.mutationsMutations lost due to timeout> 0
cdc.segment.read.lagSegments pending processing> 10
Terminal window
# Check Sidecar CDC status
curl http://sidecar:9043/api/v1/services/cdc/health
# Response
{
"status": "healthy",
"lag_segments": 2,
"last_published": "2024-01-15T14:30:00Z",
"events_per_second": 1250,
"pending_mutations": 45
}

LimitationDescription
No exactly-onceAt-least-once only; consumers must be idempotent
No SSTable importsOnly CQL writes via commit log
No linearizable orderingEvents may arrive out of order across partitions
Counter tablesNot supported
Before/after statesOnly current state, not previous values