Change Data Capture (CDC)
Stream database changes in real-time with Debezium, AWS DMS, and event sourcing patterns
The Missing Link: OLTP to OLAP in Real-Time
Traditional data pipelines extract data from production databases with periodic batch jobs (every hour, every night), creating latency measured in hours or days. Change Data Capture (CDC) eliminates this delay by streaming database changes, inserts, updates, deletes, as they happen, with millisecond to second latency. Instead of polling "SELECT * WHERE updated_at >?", CDC taps into database transaction logs (MySQL binlog, PostgreSQL WAL, Oracle redo logs) to capture every change without impacting production performance. This enables real-time analytics, microservices data synchronization, event-driven architectures, and efficient data lake ingestion. This lesson covers Debezium (the open-source CDC leader), AWS DMS, event sourcing patterns, and how CDC bridges the gap between operational databases and analytical systems.
What is Change Data Capture?
CDC is a technique that identifies and captures changes made to data in a database, then delivers those changes to downstream systems in near real-time.
Traditional Batch ETL
BATCH ETL (Old Approach):
┌────────────────────────────────────────────────┐
│ Production Database (PostgreSQL) │
│ • INSERT, UPDATE, DELETE happen continuously │
└─────────────┬──────────────────────────────────┘
│
▼ Every 1 hour
┌────────────────────────────────────────────────┐
│ ETL Job: SELECT * WHERE updated_at > ? │
│ • Full table scan (slow) │
│ • Impacts production (locks, CPU) │
│ • Misses deletes (no timestamp) │
└─────────────┬──────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────┐
│ Data Warehouse / Lake │
│ • Data 1-24 hours old │
└────────────────────────────────────────────────┘Problems with Batch ETL
Key Issues: • High latency (hours) • Production database impact • Can't track deletes • Inefficient (scans entire table)
CDC (Modern Approach)
CDC Pipeline:
┌────────────────────────────────────────────────┐
│ Production Database (PostgreSQL) │
│ • INSERT → WAL log │
│ • UPDATE → WAL log │
│ • DELETE → WAL log │
└─────────────┬──────────────────────────────────┘
│ Read transaction log
▼ Continuous streaming
┌────────────────────────────────────────────────┐
│ CDC Engine (Debezium/DMS) │
│ • Reads transaction log │
│ • Zero impact on production │
│ • Captures all changes (INSERT/UPDATE/DELETE) │
└─────────────┬──────────────────────────────────┘
│ Stream events (Kafka, Kinesis)
▼
┌────────────────────────────────────────────────┐
│ Downstream Systems │
│ • Data Lake (S3) │
│ • Search Index (Elasticsearch) │
│ • Cache (Redis) │
│ • Analytics (Snowflake) │
│ • Microservices │
│ Data freshness: < 1 second │
└────────────────────────────────────────────────┘CDC Benefits
CDC Advantages: • Real-time (milliseconds to seconds) • Zero production impact (reads logs only) • Captures all change types (INSERT, UPDATE, DELETE) • Efficient (no full table scans)
CDC Use Cases
📊 Real-Time Analytics
Stream database changes to data lake/warehouse for dashboards that show data within seconds
🔍 Search Index Sync
Keep Elasticsearch/Solr in sync with database without polling or API calls
⚡ Cache Invalidation
Automatically invalidate Redis cache when database rows change
🔄 Microservices Sync
Propagate data changes across microservices without tight coupling
💾 Database Migration
Migrate databases with zero downtime by replicating changes continuously
📝 Audit Trail
Capture complete history of all changes for compliance and debugging
Debezium: Open-Source CDC Platform
Debezium is an open-source distributed platform for CDC built on Apache Kafka. It provides connectors for MySQL, PostgreSQL, MongoDB, SQL Server, Oracle, and more, capturing every change and streaming it to Kafka topics.
Debezium Architecture
Debezium CDC Pipeline:
┌────────────────────────────────────────────┐
│ Source Database (PostgreSQL) │
│ │
│ Transaction: │
│ INSERT INTO users (id, name, email) │
│ VALUES (123, 'Alice', 'alice@ex.com') │
│ │
│ Written to WAL (Write-Ahead Log) │
└─────────────┬──────────────────────────────┘
│
▼ Debezium connector reads WAL
┌─────────────────────────────────────────────┐
│ Debezium PostgreSQL Connector │
│ • Reads WAL continuously │
│ • Deserializes log entries │
│ • Converts to change events │
│ • No impact on database │
└─────────────┬───────────────────────────────┘
│
▼ Publishes to Kafka
┌─────────────────────────────────────────────┐
│ Apache Kafka │
│ Topic: db.public.users │
│ │
│ Event: │
│ { │
│ "op": "c", (create/insert) │
│ "after": { │
│ "id": 123, │
│ "name": "Alice", │
│ "email": "alice@ex.com" │
│ }, │
│ "source": { │
│ "db": "mydb", │
│ "table": "users", │
│ "lsn": "0/12345" │
│ } │
│ } │
└─────────────┬───────────────────────────────┘
│
┌─────────┴──────────┬─────────────┐
▼ ▼ ▼
┌──────────┐ ┌─────────────┐ ┌──────┐
│ Spark │ │Elasticsearch│ │ S3 │
│ (ETL) │ │ (Search) │ │(Lake)│
└──────────┘ └─────────────┘ └──────┘Setting Up Debezium for PostgreSQL
Step 1: Configure PostgreSQL for CDC
# postgresql.conf changes: wal_level = logical max_replication_slots = 10 max_wal_senders = 10 # Restart PostgreSQL sudo systemctl restart postgresql # Create replication user CREATE USER debezium_user WITH REPLICATION LOGIN; GRANT SELECT ON ALL TABLES IN SCHEMA public TO debezium_user;
Step 2: Deploy Debezium Connector
# Debezium runs as Kafka Connect connector # docker-compose.yml: version: '3' services: zookeeper: image: confluentinc/cp-zookeeper:latest environment: ZOOKEEPER_CLIENT_PORT: 2181 kafka: image: confluentinc/cp-kafka:latest depends_on: [zookeeper] environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 debezium: image: debezium/connect:latest depends_on: [kafka] ports: - 8083:8083 environment: BOOTSTRAP_SERVERS: kafka:9092 GROUP_ID: 1 CONFIG_STORAGE_TOPIC: connect_configs OFFSET_STORAGE_TOPIC: connect_offsets # Start: docker-compose up -d
Step 3: Register PostgreSQL Connector
import requests
import json
connector_config = {
"name": "postgres-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres.example.com",
"database.port": "5432",
"database.user": "debezium_user",
"database.password": "secret",
"database.dbname": "mydb",
"database.server.name": "mydb",
"table.include.list": "public.users,public.orders",
"plugin.name": "pgoutput", # PostgreSQL 10+ logical decoding plugin
"publication.name": "dbz_publication",
"slot.name": "debezium_slot"
}
}
# Register connector via REST API
response = requests.post(
"http://localhost:8083/connectors",
headers={"Content-Type": "application/json"},
data=json.dumps(connector_config)
)
print(f"Connector registered: {response.status_code}")
# Check connector status
status = requests.get("http://localhost:8083/connectors/postgres-connector/status")
print(status.json())
"""
Response:
{
"name": "postgres-connector",
"connector": {"state": "RUNNING"},
"tasks": [{"id": 0, "state": "RUNNING"}]
}
"""
# Connector is now streaming all changes from users and orders tables to Kafka!PostgreSQL configured for logical replication
Debezium connector registered and running
All changes to users and orders tables streaming to Kafka
Consuming CDC Events with Python
from kafka import KafkaConsumer
import json
# Connect to Kafka
consumer = KafkaConsumer(
'mydb.public.users', # Topic: <server>.<schema>.<table>
bootstrap_servers=['localhost:9092'],
value_deserializer=lambda m: json.loads(m.decode('utf-8')),
auto_offset_reset='earliest'
)
# Process change events
for message in consumer:
event = message.value
# Event structure from Debezium
operation = event['payload']['op'] # c (create), u (update), d (delete), r (read/snapshot)
after = event['payload'].get('after') # New row state
before = event['payload'].get('before') # Old row state (for updates/deletes)
source = event['payload']['source']
print(f"\n=== Change Event ===")
print(f"Operation: {operation}")
print(f"Table: {source['table']}")
print(f"LSN: {source['lsn']}")
if operation == 'c': # INSERT
print(f"New user created:")
print(f" ID: {after['id']}")
print(f" Name: {after['name']}")
print(f" Email: {after['email']}")
# Sync to downstream system
sync_to_elasticsearch(after)
elif operation == 'u': # UPDATE
print(f"User updated:")
print(f" Before: {before}")
print(f" After: {after}")
# Update cache
invalidate_cache(after['id'])
elif operation == 'd': # DELETE
print(f"User deleted:")
print(f" ID: {before['id']}")
# Remove from search index
remove_from_elasticsearch(before['id'])
elif operation == 'r': # READ (initial snapshot)
print(f"Snapshot row: {after}")
# Example output when user inserted in PostgreSQL:
"""
=== Change Event ===
Operation: c
Table: users
LSN: 0/167B4F8
New user created:
ID: 456
Name: Bob Smith
Email: bob@example.com
[Synced to Elasticsearch in 150ms]
"""
# Example output when user updated:
"""
=== Change Event ===
Operation: u
Table: users
LSN: 0/167B600
User updated:
Before: {'id': 456, 'name': 'Bob Smith', 'email': 'bob@example.com'}
After: {'id': 456, 'name': 'Bob Smith', 'email': 'bob.smith@example.com'}
[Cache invalidated for user 456]
"""
# Example output when user deleted:
"""
=== Change Event ===
Operation: d
Table: users
LSN: 0/167B710
User deleted:
ID: 456
[Removed from Elasticsearch]
"""All database changes captured in real-time
INSERT, UPDATE, DELETE operations streamed to consumers
Downstream systems (Elasticsearch, cache) updated within milliseconds
AWS Database Migration Service (DMS)
AWS DMS is a fully managed service for database migration and continuous data replication using CDC. It supports homogeneous (Oracle → Oracle) and heterogeneous (MySQL → PostgreSQL) migrations with minimal downtime.
AWS DMS Architecture
AWS DMS Replication:
┌──────────────────────────────────────┐
│ Source Database │
│ • MySQL, PostgreSQL, Oracle, etc. │
│ • On-premises or AWS RDS │
└─────────────┬────────────────────────┘
│ CDC captures changes
▼
┌──────────────────────────────────────┐
│ AWS DMS Replication Instance │
│ • Reads transaction logs │
│ • Minimal source impact │
│ • Transforms data (optional) │
│ • Filters tables/columns │
└─────────────┬────────────────────────┘
│
▼ Continuous replication
┌──────────────────────────────────────┐
│ Target (choose any) │
│ • S3 (Parquet, JSON, CSV) │
│ • Redshift │
│ • DynamoDB │
│ • Kinesis Data Streams │
│ • Elasticsearch │
│ • Another database │
└──────────────────────────────────────┘
MIGRATION PHASES:
1. Full Load: Copy all existing data
2. CDC: Stream ongoing changes
3. Cut-over: Switch to target with minimal downtimeSetting Up DMS with Python (Boto3)
Step 1: Create Replication Instance
import boto3
dms = boto3.client('dms', region_name='us-east-1')
replication_instance = dms.create_replication_instance(
ReplicationInstanceIdentifier='my-replication-instance',
ReplicationInstanceClass='dms.c5.large',
AllocatedStorage=100,
VpcSecurityGroupIds=['sg-12345'],
AvailabilityZone='us-east-1a',
PubliclyAccessible=False
)
print(f"Replication instance created: {replication_instance['ReplicationInstance']['ReplicationInstanceArn']}")
# Wait for instance to be available
waiter = dms.get_waiter('replication_instance_available')
waiter.wait(Filters=[{'Name': 'replication-instance-id', 'Values': ['my-replication-instance']}])Step 2: Create Source Endpoint (PostgreSQL)
source_endpoint = dms.create_endpoint(
EndpointIdentifier='source-postgres',
EndpointType='source',
EngineName='postgres',
ServerName='prod-db.example.com',
Port=5432,
DatabaseName='mydb',
Username='dms_user',
Password='secret'
)
# Test connection
dms.test_connection(
ReplicationInstanceArn=replication_instance['ReplicationInstance']['ReplicationInstanceArn'],
EndpointArn=source_endpoint['Endpoint']['EndpointArn']
)Step 3: Create Target Endpoint (S3)
target_endpoint = dms.create_endpoint(
EndpointIdentifier='target-s3',
EndpointType='target',
EngineName='s3',
S3Settings={
'ServiceAccessRoleArn': 'arn:aws:iam::123456789:role/dms-s3-role',
'BucketName': 'my-data-lake',
'BucketFolder': 'cdc-data',
'DataFormat': 'parquet',
'CompressionType': 'gzip',
'TimestampColumnName': 'cdc_timestamp',
'ParquetTimestampInMillisecond': True
}
)Step 4: Create Replication Task
import json
# Table mappings: which tables to replicate
table_mappings = {
"rules": [
{
"rule-type": "selection",
"rule-id": "1",
"rule-name": "include-users-orders",
"object-locator": {
"schema-name": "public",
"table-name": "%" # All tables
},
"rule-action": "include"
},
{
"rule-type": "transformation",
"rule-id": "2",
"rule-name": "add-column",
"rule-target": "column",
"object-locator": {
"schema-name": "public",
"table-name": "%"
},
"rule-action": "add-column",
"value": "cdc_timestamp",
"expression": "$AR_H_TIMESTAMP",
"data-type": {
"type": "datetime"
}
}
]
}
replication_task = dms.create_replication_task(
ReplicationTaskIdentifier='postgres-to-s3-cdc',
SourceEndpointArn=source_endpoint['Endpoint']['EndpointArn'],
TargetEndpointArn=target_endpoint['Endpoint']['EndpointArn'],
ReplicationInstanceArn=replication_instance['ReplicationInstance']['ReplicationInstanceArn'],
MigrationType='full-load-and-cdc', # Full load then CDC
TableMappings=json.dumps(table_mappings),
ReplicationTaskSettings=json.dumps({
"TargetMetadata": {
"SupportLobs": True,
"LobMaxSize": 32 # MB
},
"FullLoadSettings": {
"TargetTablePrepMode": "DROP_AND_CREATE"
},
"Logging": {
"EnableLogging": True
}
})
)
print(f"Replication task created: {replication_task['ReplicationTask']['ReplicationTaskArn']}")Step 5: Start Replication
dms.start_replication_task(
ReplicationTaskArn=replication_task['ReplicationTask']['ReplicationTaskArn'],
StartReplicationTaskType='start-replication'
)
print("Replication started!")Step 6: Monitor Progress
import time
while True:
response = dms.describe_replication_tasks(
Filters=[{'Name': 'replication-task-arn',
'Values': [replication_task['ReplicationTask']['ReplicationTaskArn']]}]
)
task = response['ReplicationTasks'][0]
status = task['Status']
stats = task.get('ReplicationTaskStats', {})
print(f"\nStatus: {status}")
print(f"Full Load Progress: {stats.get('FullLoadProgressPercent', 0)}%")
print(f"Tables Loaded: {stats.get('TablesLoaded', 0)}")
print(f"Rows Loaded: {stats.get('FullLoadRowsLoaded', 0)}")
if status == 'running' and stats.get('FullLoadProgressPercent') == 100:
print("\n✓ Full load complete, CDC replication active!")
break
time.sleep(10)
"""
Output:
Status: running
Full Load Progress: 45%
Tables Loaded: 5
Rows Loaded: 1,245,890
...
Status: running
Full Load Progress: 100%
Tables Loaded: 12
Rows Loaded: 5,000,000
✓ Full load complete, CDC replication active!
Result:
• All existing data copied to S3 as Parquet
• Ongoing changes streaming continuously
• Latency: < 10 seconds
• Zero downtime for source database
"""Full load of 5M rows completed
CDC replication active, streaming changes to S3
Data available as Parquet files in data lake
Latency: < 10 seconds
DMS to Kinesis for Real-Time Processing
# Stream database changes to Kinesis for real-time processing
target_kinesis = dms.create_endpoint(
EndpointIdentifier='target-kinesis',
EndpointType='target',
EngineName='kinesis',
KinesisSettings={
'StreamArn': 'arn:aws:kinesis:us-east-1:123456789:stream/db-changes',
'MessageFormat': 'json',
'ServiceAccessRoleArn': 'arn:aws:iam::123456789:role/dms-kinesis-role',
'IncludeTransactionDetails': True,
'IncludeTableAlterOperations': True
}
)
# Now every database change flows to Kinesis in real-time
# Consume with Lambda, Flink, or any Kinesis consumer
# Example: Process with AWS Lambda
import boto3
import json
def lambda_handler(event, context):
"""Process CDC events from Kinesis"""
for record in event['Records']:
# Decode Kinesis record
payload = json.loads(record['kinesis']['data'])
# DMS change event
operation = payload['metadata']['operation'] # insert, update, delete
table = payload['metadata']['table-name']
data = payload['data']
print(f"Change detected: {operation} on {table}")
if operation == 'insert':
# Send to Elasticsearch
index_to_elasticsearch(table, data)
elif operation == 'update':
# Invalidate cache
invalidate_cache(table, data['id'])
elif operation == 'delete':
# Remove from search
remove_from_elasticsearch(table, data['id'])
return {'statusCode': 200}
# Result: Sub-second latency from database to downstream systemsEvent Sourcing Patterns
Event sourcing is an architectural pattern where application state changes are stored as a sequence of events rather than just the current state. CDC is a form of event sourcing for databases.
Traditional State Storage vs Event Sourcing:
TRADITIONAL (Current State Only):
┌────────────────────────────────┐
│ Database │
│ users table: │
│ ┌──────┬────────┬───────────┐ │
│ │ id │ name │ balance │ │
│ ├──────┼────────┼───────────┤ │
│ │ 123 │ Alice │ $1,000 │ │ ← Current state only
│ └──────┴────────┴───────────┘ │
└────────────────────────────────┘
Lost information:
• How did balance reach $1,000?
• What were previous balances?
• What operations occurred?
EVENT SOURCING (Event Log):
┌─────────────────────────────────────────────┐
│ Event Store │
│ ┌──────────┬─────────────────────────────┐ │
│ │Timestamp │ Event │ │
│ ├──────────┼─────────────────────────────┤ │
│ │10:00 │AccountCreated(id=123) │ │
│ │10:05 │Deposited(amount=500) │ │
│ │10:15 │Deposited(amount=300) │ │
│ │10:30 │Withdrew(amount=100) │ │
│ │10:45 │Deposited(amount=300) │ │
│ └──────────┴─────────────────────────────┘ │
└─────────────────────────────────────────────┘
Current state: Sum all events = $1,000
Historic state: Replay events to any point in time
Benefits:
• Complete audit trail
• Time travel (replay to any point)
• Event-driven architecture
• Easy debugging ("what changed?")
• Source of truth for downstream systemsImplementing Event Sourcing with CDC
# Use CDC as event stream for event sourcing
from kafka import KafkaConsumer
import json
from datetime import datetime, timezone
class EventStore:
"""Store all CDC events as immutable event log"""
def __init__(self):
self.events = [] # In production: use database or S3
def append(self, event):
"""Append event to immutable log"""
event['event_id'] = len(self.events)
event['stored_at'] = datetime.now(timezone.utc).isoformat()
self.events.append(event)
def get_events(self, entity_id, entity_type):
"""Get all events for an entity"""
return [e for e in self.events
if e.get('entity_id') == entity_id
and e.get('entity_type') == entity_type]
def replay_to_point(self, entity_id, entity_type, timestamp):
"""Reconstruct state at specific point in time"""
relevant_events = [
e for e in self.get_events(entity_id, entity_type)
if e['timestamp'] <= timestamp
]
# Replay events to build state
state = {}
for event in relevant_events:
state = apply_event(state, event)
return state
# Consume CDC events and store in event log
event_store = EventStore()
consumer = KafkaConsumer(
'mydb.public.accounts',
bootstrap_servers=['localhost:9092'],
value_deserializer=lambda m: json.loads(m.decode('utf-8'))
)
for message in consumer:
cdc_event = message.value['payload']
# Convert CDC event to domain event
if cdc_event['op'] == 'c': # Insert
domain_event = {
'event_type': 'AccountCreated',
'entity_id': cdc_event['after']['account_id'],
'entity_type': 'Account',
'timestamp': cdc_event['ts_ms'],
'data': cdc_event['after']
}
elif cdc_event['op'] == 'u': # Update
before = cdc_event['before']
after = cdc_event['after']
# Detect what changed
if before['balance'] != after['balance']:
amount = after['balance'] - before['balance']
event_type = 'Deposited' if amount > 0 else 'Withdrew'
domain_event = {
'event_type': event_type,
'entity_id': after['account_id'],
'entity_type': 'Account',
'timestamp': cdc_event['ts_ms'],
'data': {
'amount': abs(amount),
'balance_before': before['balance'],
'balance_after': after['balance']
}
}
# Store event in immutable log
event_store.append(domain_event)
print(f"Event stored: {domain_event['event_type']} for account {domain_event['entity_id']}")
# Query event history
account_events = event_store.get_events(entity_id=123, entity_type='Account')
print(f"\nAccount 123 event history ({len(account_events)} events):")
for event in account_events:
print(f" {event['timestamp']}: {event['event_type']} - {event['data']}")
"""
Output:
Account 123 event history (5 events):
1704034800000: AccountCreated - {'account_id': 123, 'balance': 0}
1704034860000: Deposited - {'amount': 500, 'balance_before': 0, 'balance_after': 500}
1704035460000: Deposited - {'amount': 300, 'balance_before': 500, 'balance_after': 800}
1704036060000: Withdrew - {'amount': 100, 'balance_before': 800, 'balance_after': 700}
1704036660000: Deposited - {'amount': 300, 'balance_before': 700, 'balance_after': 1000}
"""
# Time travel: What was balance at 10:15 AM?
state_at_1015 = event_store.replay_to_point(
entity_id=123,
entity_type='Account',
timestamp=1704035460000 # 10:15 AM
)
print(f"\nBalance at 10:15 AM: ${state_at_1015['balance']}") # $800Complete event history stored
Time travel: reconstruct state at any point
Full audit trail for compliance
Real-Time Data Synchronization Patterns
CDC enables various real-time synchronization patterns for keeping downstream systems in sync with source databases.
Pattern 1: Database to Search Index Sync
# Keep Elasticsearch in sync with PostgreSQL using CDC
from kafka import KafkaConsumer
from elasticsearch import Elasticsearch
import json
es = Elasticsearch(['http://localhost:9200'])
consumer = KafkaConsumer(
'mydb.public.products',
bootstrap_servers=['localhost:9092'],
value_deserializer=lambda m: json.loads(m.decode('utf-8'))
)
for message in consumer:
event = message.value['payload']
if event['op'] == 'c' or event['op'] == 'r': # INSERT or Snapshot
# Index new document
doc = event['after']
es.index(
index='products',
id=doc['product_id'],
document={
'name': doc['name'],
'description': doc['description'],
'price': doc['price'],
'category': doc['category']
}
)
print(f"Indexed product {doc['product_id']}")
elif event['op'] == 'u': # UPDATE
# Update document
doc = event['after']
es.update(
index='products',
id=doc['product_id'],
doc={
'name': doc['name'],
'description': doc['description'],
'price': doc['price'],
'category': doc['category']
}
)
print(f"Updated product {doc['product_id']}")
elif event['op'] == 'd': # DELETE
# Delete document
doc = event['before']
es.delete(index='products', id=doc['product_id'])
print(f"Deleted product {doc['product_id']}")
# Result: Elasticsearch always in sync with PostgreSQL (< 1 second latency)Pattern 2: Cache Invalidation
# Automatically invalidate Redis cache on database changes
import redis
redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)
consumer = KafkaConsumer(
'mydb.public.users',
bootstrap_servers=['localhost:9092'],
value_deserializer=lambda m: json.loads(m.decode('utf-8'))
)
for message in consumer:
event = message.value['payload']
if event['op'] in ['u', 'd']: # UPDATE or DELETE
# Invalidate cache
user_id = event['before']['user_id']
cache_keys = [
f"user:{user_id}",
f"user:{user_id}:profile",
f"user:{user_id}:preferences"
]
for key in cache_keys:
redis_client.delete(key)
print(f"Invalidated cache: {key}")
# Update with new value if UPDATE
if event['op'] == 'u':
new_data = event['after']
redis_client.setex(
f"user:{user_id}",
3600, # 1 hour TTL
json.dumps(new_data)
)
print(f"Updated cache: user:{user_id}")
# Result: Cache always fresh, no stale dataPattern 3: Microservices Data Sync
# Sync data across microservices without direct coupling
# Order Service database changes → Inventory Service
consumer = KafkaConsumer(
'orders_db.public.orders',
bootstrap_servers=['localhost:9092'],
value_deserializer=lambda m: json.loads(m.decode('utf-8'))
)
for message in consumer:
event = message.value['payload']
if event['op'] == 'c': # New order created
order = event['after']
# Update inventory in Inventory Service
update_inventory(
product_id=order['product_id'],
quantity_change=-order['quantity']
)
# Notify Shipping Service
send_to_shipping_queue({
'order_id': order['order_id'],
'customer_address': order['shipping_address']
})
print(f"Order {order['order_id']} processed across services")
# Result: Services stay in sync without tight coupling or API callsKey Takeaways
- CDC captures database changes in real-time by reading transaction logs
- Debezium: Open-source CDC platform with Kafka integration
- AWS DMS: Managed service for database migration and CDC
- Zero production impact: Reads logs, not tables
- Event sourcing: Store all changes as immutable event log
- Real-time sync: Keep search, cache, microservices updated
- Latency: Milliseconds to seconds (vs hours for batch ETL)
- Use cases: Analytics, search sync, cache invalidation, auditing