Change Data Capture (CDC) and Tenant Migration Tutorial
Change Data Capture (CDC) and Tenant Migration Tutorial
Version: 3.2.0 Last Updated: December 12, 2025 Difficulty: Advanced
Table of Contents
- Introduction
- Part 1: Change Data Capture (CDC)
- Part 2: Tenant Migration
- Advanced Topics
- Troubleshooting
- Best Practices
Introduction
This tutorial covers two advanced multi-tenancy features in HeliosDB Nano:
-
Change Data Capture (CDC): Automatically tracks all data modifications (INSERT, UPDATE, DELETE) for audit trails, replication, and compliance.
-
Tenant Migration: Safely moves tenant data between isolation levels or instances with zero downtime.
What You’ll Learn
- How to view and export change events
- Understanding CDC event structure
- Planning and executing tenant migrations
- Monitoring migration progress
- Handling migration failures
Prerequisites
- HeliosDB Nano v3.2.0 or later
- Completed RLS Policy Management Tutorial
- Understanding of tenant contexts
- Familiarity with SQL transactions
Part 1: Change Data Capture (CDC)
Understanding CDC
Change Data Capture automatically logs every data modification in a tenant’s tables. Each change is recorded with:
- Event ID: Unique identifier
- Change Type: INSERT, UPDATE, or DELETE
- Table Name: Which table was modified
- Row Key: Primary key of affected row
- Old Values: Data before change (UPDATE/DELETE)
- New Values: Data after change (INSERT/UPDATE)
- Tenant ID: Which tenant made the change
- Timestamp: When the change occurred
- Transaction ID: Groups related changes
Why Use CDC?
| Use Case | Description |
|---|---|
| Audit Compliance | Track who changed what and when |
| Data Replication | Sync changes to analytics databases |
| Event Sourcing | Reconstruct state at any point in time |
| Migration | Replay changes during tenant migration |
| Debugging | Understand data evolution |
| Rollback | Undo unintended changes |
Viewing CDC Events
Basic Usage
\tenant cdc-showDefault: Shows last 10 events for current tenant.
Output:
CDC Events for tenant 'acme-corp':────────────────────────────────────────────────────────────Timestamp Type Table Row Key────────────────────────────────────────────────────────────2025-12-12 10:30:15 INSERT customers 12025-12-12 10:31:22 UPDATE customers 12025-12-12 10:32:45 INSERT orders 1012025-12-12 10:33:10 UPDATE orders 1012025-12-12 10:35:00 DELETE orders 101────────────────────────────────────────────────────────────Show More Events
\tenant cdc-show 50Shows last 50 events.
Show All Events
\tenant cdc-show 1000Use a large number to see all recorded events (limited by CDC log retention).
Step-by-Step: Tracking Changes
Step 1: Setup Test Environment
-- Create tenant\tenant create demo-company free
-- Create test tableCREATE TABLE products ( id INTEGER PRIMARY KEY, tenant_id TEXT NOT NULL, name TEXT, price DECIMAL, stock INTEGER);
-- Switch to tenant context\tenant use demo-companyStep 2: Make Some Changes
-- Insert a productINSERT INTO products (id, tenant_id, name, price, stock)VALUES (1, 'demo-company', 'Widget Pro', 99.99, 100);
-- Update the productUPDATE products SET price = 89.99 WHERE id = 1;
-- Update stockUPDATE products SET stock = 95 WHERE id = 1;
-- Delete (if needed)-- DELETE FROM products WHERE id = 1;Step 3: View CDC Log
\tenant cdc-show 10Expected Output:
CDC Events for tenant 'demo-company':────────────────────────────────────────────────────────────Timestamp Type Table Row Key────────────────────────────────────────────────────────────2025-12-12 14:20:15 INSERT products 12025-12-12 14:21:30 UPDATE products 12025-12-12 14:22:10 UPDATE products 1────────────────────────────────────────────────────────────Analysis:
- First event: Product creation (INSERT)
- Second event: Price change (UPDATE)
- Third event: Stock adjustment (UPDATE)
Exporting CDC Data
Export to JSON
\tenant cdc-export changes.jsonOutput File: changes.json in current directory
File Structure:
[ { "event_id": 1, "change_type": "Insert", "table_name": "products", "row_key": "1", "old_values": null, "new_values": "{\"id\":1,\"tenant_id\":\"demo-company\",\"name\":\"Widget Pro\",\"price\":99.99,\"stock\":100}", "tenant_id": "550e8400-e29b-41d4-a716-446655440001", "timestamp": "2025-12-12 14:20:15", "transaction_id": 42 }, { "event_id": 2, "change_type": "Update", "table_name": "products", "row_key": "1", "old_values": "{\"id\":1,\"tenant_id\":\"demo-company\",\"name\":\"Widget Pro\",\"price\":99.99,\"stock\":100}", "new_values": "{\"id\":1,\"tenant_id\":\"demo-company\",\"name\":\"Widget Pro\",\"price\":89.99,\"stock\":100}", "tenant_id": "550e8400-e29b-41d4-a716-446655440001", "timestamp": "2025-12-12 14:21:30", "transaction_id": 43 }, { "event_id": 3, "change_type": "Update", "table_name": "products", "row_key": "1", "old_values": "{\"id\":1,\"tenant_id\":\"demo-company\",\"name\":\"Widget Pro\",\"price\":89.99,\"stock\":100}", "new_values": "{\"id\":1,\"tenant_id\":\"demo-company\",\"name\":\"Widget Pro\",\"price\":89.99,\"stock\":95}", "tenant_id": "550e8400-e29b-41d4-a716-446655440001", "timestamp": "2025-12-12 14:22:10", "transaction_id": 44 }]CDC Use Cases
Use Case 1: Audit Trail for Compliance
Scenario: Financial application needs to track all monetary transactions.
Workflow:
-- Perform operations\tenant use finance-corp
INSERT INTO transactions (id, tenant_id, amount, type) VALUES (1, 'finance-corp', 10000, 'deposit');UPDATE transactions SET amount = 15000 WHERE id = 1;
-- Export audit trail\tenant cdc-export audit_2025_12.json
-- Review changes\tenant cdc-show 100Compliance Report:
- What: Transaction #1 modified
- When: 2025-12-12 14:20:15
- Old Value: $10,000
- New Value: $15,000
- Who: tenant ‘finance-corp’
Use Case 2: Data Synchronization
Scenario: Sync production database changes to analytics warehouse.
Workflow:
-- Export changes hourly\tenant use analytics-tenant\tenant cdc-export /exports/changes_$(date +%Y%m%d_%H).json
-- Process in analytics pipeline# External script:# 1. Read changes.json# 2. Transform to warehouse schema# 3. Apply to analytics DBUse Case 3: Debugging Data Issues
Scenario: Customer reports incorrect data. Need to trace how it happened.
Investigation:
\tenant use customer-corp
-- Show recent changes to problematic table\tenant cdc-show 50
-- Look for pattern:-- - What changed?-- - When did it change?-- - Was it bulk update or individual changes?Example Finding:
2025-12-12 10:00:00 UPDATE customers 1 (price corrected)2025-12-12 10:00:01 UPDATE customers 2 (price corrected)2025-12-12 10:00:02 UPDATE customers 3 (price corrected)...2025-12-12 10:00:50 UPDATE customers 50 (price corrected)
Conclusion: Bulk update script ran at 10:00 AMUse Case 4: Rollback Capability
Scenario: Accidentally deleted critical data. Need to restore.
Recovery Process:
-- 1. Export CDC to find deleted records\tenant cdc-export deleted_data.json
-- 2. Filter for DELETE events in JSON# Look for: "change_type": "Delete"
-- 3. Extract old_values from DELETE events
-- 4. Reconstruct INSERT statementsINSERT INTO customers (id, tenant_id, name, email)VALUES (extracted from old_values);Use Case 5: Change Tracking Dashboard
Scenario: Real-time dashboard showing tenant activity.
Implementation:
-- Export recent events\tenant cdc-export recent_activity.json
-- Parse in application# Read JSON# Count by change_type: {"inserts": 50, "updates": 120, "deletes": 5}# Count by table: {"customers": 75, "orders": 100}# Display in UI dashboardPart 2: Tenant Migration
Understanding Migration
Tenant Migration is the process of moving a tenant’s data from one location to another while maintaining:
- Data integrity
- Zero downtime
- Consistent state
- Rollback capability
Migration States
| State | Description |
|---|---|
| Pending | Migration queued, not started |
| Snapshotting | Taking initial data snapshot |
| Replicating | Applying ongoing changes via CDC |
| Verifying | Validating data consistency |
| Completed | Migration successful |
| Failed | Migration encountered error |
| Paused | Migration temporarily halted |
Migration Workflow
Phase 1: Pre-Migration Planning
1. Assess Current State
\tenant info source-tenant
-- Check data volume\tenant use source-tenantSELECT COUNT(*) FROM customers;SELECT COUNT(*) FROM orders;
-- Check quota usage\tenant quota source-tenant2. Create Target Tenant
\tenant create target-tenant pro3. Verify RLS Policies
-- List policies on source\tenant rls list customers\tenant rls list orders
-- Recreate on target (if needed)\tenant use target-tenant\tenant rls create customers tenant_isolation tenant_id=current_tenant() ALL\tenant rls create orders tenant_isolation tenant_id=current_tenant() ALLPhase 2: Initiate Migration
Start Migration
\tenant use source-tenant\tenant migrate-to target-tenantExpected Output:
Migration initiated successfully────────────────────────────────────Source: source-tenant (550e8400-e29b-41d4-a716-446655440001)Target: target-tenant (650e8400-e29b-41d4-a716-446655440002)State: PendingProgress: 0 / 0 changes replicated────────────────────────────────────Phase 3: Monitor Progress
Check Migration Status
\tenant migrate-status source-tenantOutput During Snapshotting:
Migration Status: source-tenant → target-tenant────────────────────────────────────────────────State: SnapshottingStarted: 2025-12-12 15:00:00Elapsed: 00:02:30Progress: 0 / 0 changes replicatedSnapshot: In progress...────────────────────────────────────────────────Output During Replication:
Migration Status: source-tenant → target-tenant────────────────────────────────────────────────State: ReplicatingStarted: 2025-12-12 15:00:00Elapsed: 00:15:45Progress: 1,250 / 1,250 changes replicated (100%)Lag: < 1 second────────────────────────────────────────────────Output After Completion:
Migration Status: source-tenant → target-tenant────────────────────────────────────────────────State: CompletedStarted: 2025-12-12 15:00:00Completed: 2025-12-12 15:20:30Duration: 00:20:30Total Changes: 1,250 replicatedVerification: ✓ Passed────────────────────────────────────────────────Phase 4: Verification
1. Compare Row Counts
-- Source tenant\tenant use source-tenantSELECT COUNT(*) as source_count FROM customers;SELECT COUNT(*) as source_count FROM orders;
-- Target tenant\tenant use target-tenantSELECT COUNT(*) as target_count FROM customers;SELECT COUNT(*) as target_count FROM orders;
-- Should be equal2. Spot Check Data
-- Compare sample records\tenant use source-tenantSELECT * FROM customers WHERE id IN (1, 10, 100);
\tenant use target-tenantSELECT * FROM customers WHERE id IN (1, 10, 100);
-- Should match exactly3. Verify Latest Changes
-- Check CDC on target\tenant use target-tenant\tenant cdc-show 10
-- Should show recent replicated eventsPhase 5: Cutover
1. Pause Application Writes
# In your application:# 1. Enable read-only mode for source tenant# 2. Wait for active transactions to complete# 3. Verify no pending writes2. Final Synchronization
-- Check status one last time\tenant migrate-status source-tenant
-- Should show: State: Completed-- And: Lag: 0 seconds3. Switch Application
# In your application:# 1. Update tenant_id reference: source-tenant → target-tenant# 2. Enable writes on target# 3. Monitor for errors4. Cleanup
-- After confirming success, optionally delete source\tenant delete source-tenant
-- Or keep for rollback capability (recommended for 7-30 days)Migration Strategies
Strategy 1: Live Migration (Zero Downtime)
Best For: Production tenants that can’t afford downtime
Process:
- Start migration while source is active
- Snapshot initial state
- Continuously replicate changes via CDC
- Cutover when lag < 1 second
- No application downtime
Timeline:
Source Active ████████████████████████████████████████Migration ───────▶ Snapshot ▶ Replicate ▶ CutoverTarget Ready ─────────────────────────────────▶ █████Strategy 2: Scheduled Migration (Maintenance Window)
Best For: Non-critical tenants or planned maintenance
Process:
- Schedule maintenance window
- Pause writes to source
- Perform migration
- Verify data
- Resume on target
Timeline:
Maintenance: 02:00 - 04:00 AM
01:59 Announce maintenance02:00 Pause writes02:01 Start migration02:30 Migration complete02:35 Verification02:40 Cutover to target02:45 Resume writesStrategy 3: Test-and-Cutover
Best For: High-risk migrations, large tenants
Process:
- Migrate to target
- Keep source active
- Test target extensively
- If issues: rollback to source
- If success: scheduled cutover
Timeline:
Week 1: MigrationWeek 2: Parallel testingWeek 3: Scheduled cutoverAdvanced Migration Scenarios
Scenario 1: Upgrading Tenant Plan
Objective: Move tenant from Free to Enterprise plan
-- Check current plan\tenant info budget-tenant-- Output: Plan: Free
-- Create enterprise target\tenant create budget-enterprise enterprise
-- Migrate data\tenant use budget-tenant\tenant migrate-to budget-enterprise
-- Monitor\tenant migrate-status budget-tenant
-- After completion, cutover application-- Then delete old tenant\tenant delete budget-tenantScenario 2: Multi-Region Migration
Objective: Move tenant from US region to EU region
-- US Region Database\tenant use us-customer\tenant cdc-export /sync/us-customer-snapshot.json
-- Transfer file to EU region# scp /sync/us-customer-snapshot.json eu-server:/sync/
-- EU Region Database\tenant create eu-customer enterprise
-- Import from snapshot (conceptual - would need import tool)# heliosdb-import --tenant eu-customer --file /sync/us-customer-snapshot.json
-- Continue live replication\tenant use us-customer\tenant migrate-to eu-customer@eu-regionScenario 3: Splitting Tenant
Objective: Split large tenant into multiple smaller tenants
-- Original tenant has mixed departments\tenant use megacorp
SELECT COUNT(*) FROM employees WHERE department = 'Sales'; -- 500SELECT COUNT(*) FROM employees WHERE department = 'Engineering'; -- 800
-- Create department-specific tenants\tenant create megacorp-sales pro\tenant create megacorp-eng pro
-- Selective migration (conceptual - would need filtering)-- Option 1: Export + filter + import\tenant cdc-export megacorp-full.json# Filter JSON: sales_data.json (department = 'Sales')# Import to megacorp-sales
-- Option 2: Manual data copy\tenant clear -- Admin modeINSERT INTO employees SELECT * FROM employees WHERE department = 'Sales' AND tenant_id = 'megacorp' -- Update tenant_id to 'megacorp-sales'Scenario 4: Consolidating Tenants
Objective: Merge multiple small tenants into one
-- Small tenants\tenant list-- Output: small-1, small-2, small-3
-- Create consolidated tenant\tenant create consolidated-tenant pro
-- Migrate each\tenant use small-1\tenant migrate-to consolidated-tenant
\tenant use small-2\tenant migrate-to consolidated-tenant
\tenant use small-3\tenant migrate-to consolidated-tenant
-- Verify total data\tenant use consolidated-tenantSELECT COUNT(*) FROM customers;-- Should equal: small-1 + small-2 + small-3Advanced Topics
CDC Event Filtering
Problem: Too many events, want specific changes.
Solution: Export and filter JSON programmatically.
import json
# Load CDC exportwith open('changes.json') as f: events = json.load(f)
# Filter: Only DELETE eventsdeletes = [e for e in events if e['change_type'] == 'Delete']
# Filter: Only specific tablecustomer_changes = [e for e in events if e['table_name'] == 'customers']
# Filter: Time rangefrom datetime import datetimestart = datetime(2025, 12, 1)end = datetime(2025, 12, 31)
monthly_events = [ e for e in events if start <= datetime.fromisoformat(e['timestamp']) <= end]Migration Performance Tuning
Slow Migration? Try these optimizations:
1. Check Network Latency
# Ping target serverping target-server
# Should be < 50ms for good performance2. Increase Batch Size (Conceptual - configuration)
[migration]batch_size = 1000 # Default: 100parallel_workers = 4 # Default: 13. Index Optimization
-- Ensure indexes exist on target\tenant use target-tenantCREATE INDEX idx_customers_tenant ON customers(tenant_id);CREATE INDEX idx_orders_tenant ON orders(tenant_id);4. Disable RLS During Migration (Faster, but risky)
-- Remove RLS temporarily\tenant rls delete target-table policy
-- Run migration\tenant migrate-to target
-- Re-enable RLS\tenant rls create target-table policy tenant_id=current_tenant() ALLRollback Procedures
If Migration Fails:
1. Check Status
\tenant migrate-status source-tenant-- Output: State: Failed - "Reason: Connection timeout"2. Verify Source Intact
\tenant use source-tenantSELECT COUNT(*) FROM customers; -- Should show original count3. Retry Migration
-- Fix underlying issue (network, permissions, etc.)-- Retry\tenant migrate-to target-tenant4. Manual Rollback
-- If target was partially populated\tenant use target-tenantDELETE FROM customers WHERE tenant_id = 'target-tenant';DELETE FROM orders WHERE tenant_id = 'target-tenant';
-- Or delete entire target\tenant delete target-tenantTroubleshooting
CDC Issues
Problem: No Events Showing
\tenant cdc-show-- Output: No CDC events foundCauses:
- No changes made yet
- CDC not enabled
- Wrong tenant context
Solution:
-- Verify tenant context\tenant current
-- Make a test changeINSERT INTO customers (id, tenant_id, name) VALUES (999, current_tenant(), 'Test');
-- Check again\tenant cdc-showProblem: Export Fails
\tenant cdc-export changes.json-- Error: Permission deniedCauses:
- No write permission in directory
- File already exists and is read-only
Solution:
# Check permissionsls -la changes.json
# Remove old filerm changes.json
# Or export to different location\tenant cdc-export /tmp/changes.jsonMigration Issues
Problem: Migration Stuck in “Pending”
\tenant migrate-status source-- State: Pending (for 10 minutes)Causes:
- Resource contention
- Target tenant doesn’t exist
- Migration queue backed up
Solution:
-- Verify target exists\tenant list | grep target-tenant
-- Check system resources\stats
-- Cancel and retry# (Conceptual - would need cancel command)Problem: Migration State “Failed”
\tenant migrate-status source-- State: Failed - "Table schema mismatch"Causes:
- Target missing tables
- Schema differences
- RLS policy conflicts
Solution:
-- Ensure target has same schema\tenant use target-tenant
CREATE TABLE customers ( id INTEGER PRIMARY KEY, tenant_id TEXT NOT NULL, name TEXT, email TEXT);
-- Retry migration\tenant use source-tenant\tenant migrate-to target-tenantProblem: Data Mismatch After Migration
-- Source: 1000 rows-- Target: 995 rowsCauses:
- Migration incomplete
- RLS filtering on target
- Ongoing writes during migration
Solution:
-- Check migration state\tenant migrate-status source-- Should be "Completed", not "Replicating"
-- Verify RLS not filtering\tenant use target-tenant\tenant clear -- Admin modeSELECT COUNT(*) FROM customers WHERE tenant_id = 'target-tenant';
-- If mismatch persists, re-run migrationBest Practices
CDC Best Practices
1. Regular Exports
# Daily CDC backup script#!/bin/bashDATE=$(date +%Y%m%d)TENANT="acme-corp"
heliosdb-repl <<EOF\tenant use $TENANT\tenant cdc-export /backups/cdc_${TENANT}_${DATE}.json\qEOF2. Monitor CDC Growth
-- Check CDC log size\tenant use my-tenant\tenant cdc-show 99999-- Count events in output
-- If too large, consider archiving old events\tenant cdc-export archive_2025_q1.json-- (Then purge old events - conceptual, would need purge command)3. Include CDC in Disaster Recovery
Recovery Plan:
- Restore last database backup
- Import CDC exports to replay changes
- Verify data integrity
Migration Best Practices
1. Pre-Migration Checklist
- Backup source tenant data
- Export CDC log:
\tenant cdc-export pre-migration.json - Document current schema:
\d <tables> - Record row counts per table
- List all RLS policies
- Identify peak usage times (avoid these)
- Notify users of scheduled migration
- Prepare rollback plan
2. During Migration
- Monitor status every 5 minutes
- Watch for error messages
- Check system resources (\stats)
- Avoid making changes to source
- Keep application in read-only mode
3. Post-Migration
- Verify row counts match
- Spot-check sample records
- Run application smoke tests
- Monitor performance for 24 hours
- Keep source tenant for 7-30 days (rollback capability)
- Update documentation
- Notify users of completion
4. Migration Schedule Template
T-7 days: Announce migration to usersT-3 days: Backup source tenantT-1 day: Final preparation, verify checklistT-0 hours: Begin migrationT+1 hour: First verificationT+6 hours: Monitor application performanceT+24 hours: Full verification, smoke testsT+7 days: Delete source tenant (if successful)Quick Reference
CDC Commands
| Command | Purpose |
|---|---|
\tenant cdc-show [limit] | View recent changes |
\tenant cdc-export <file> | Export to JSON |
Migration Commands
| Command | Purpose |
|---|---|
\tenant migrate-to <target> | Start migration |
\tenant migrate-status [tenant] | Check progress |
Migration States
Pending → Snapshotting → Replicating → Verifying → Completed ↓ Failed / PausedExample Workflows
Complete Migration Workflow
-- 1. Preparation\tenant create new-tenant pro\tenant use old-tenant\tenant cdc-export pre-migration-backup.json
-- 2. Initiate\tenant migrate-to new-tenant
-- 3. Monitor (run every 5 minutes)\tenant migrate-status old-tenant
-- 4. Verify\tenant use old-tenantSELECT COUNT(*) FROM customers; -- Note count
\tenant use new-tenantSELECT COUNT(*) FROM customers; -- Should match
-- 5. Cutover-- (Update application to use new-tenant)
-- 6. Cleanup (after 7-30 days)\tenant delete old-tenantCDC Audit Workflow
-- Daily audit export\tenant use finance-tenant\tenant cdc-export /audit/finance_$(date +%Y%m%d).json
-- Review specific events\tenant cdc-show 100
-- Generate report# External script processes JSON:# - Count changes by type# - Identify high-activity periods# - Flag suspicious patternsNext Steps
- Practice: Try the SQL test scripts with CDC enabled
- Learn More: Read the Multi-Tenancy Implementation Report
- RLS Integration: Review RLS Policy Management Tutorial
Additional Resources
- Multi-Tenancy Test Coverage
- Quick Test Guide
- REPL Help:
\h tenants
Need Help?
- Check migration status:
\tenant migrate-status - View recent changes:
\tenant cdc-show 50 - Export for analysis:
\tenant cdc-export debug.json
Report Issues: [GitHub Issues](https://github.com/dimensigon/HeliosDB Nano/issues)