GuidesSchema Sync

Schema Sync

Schema sync automatically detects and applies schema differences between source and destination tables. Use this when source schemas evolve and you need to propagate changes downstream.

What is Schema Sync?

Schema sync compares the schema of each table in your mirror’s table mappings and applies DDL changes to align the destination with the source. This includes:

  • Column additions and removals
  • Data type changes
  • Index additions/removals (if replicate_indexes: true)
  • Constraint modifications
⚠️

Schema sync is a destructive operation. It drops the replication slot and restarts CDC from a fresh position. Always test in a staging environment first.

When to Use Schema Sync

Common scenarios:

  • Column added on source: New column needs to be replicated
  • Column removed from source: Destination should drop the column to match
  • Data type changed: Source column type was altered (e.g., VARCHAR(50)VARCHAR(255))
  • Index added: New index created on source should be replicated
  • After manual schema changes: Destination schema manually modified and you want to resync with source

What Schema Sync Does

Schema sync performs the following operations:

Supported Changes

Change TypeActionExample
Column additionALTER TABLE ... ADD COLUMNAdd last_login TIMESTAMP
Column removalALTER TABLE ... DROP COLUMNDrop deprecated_field
Type changeALTER TABLE ... ALTER COLUMN TYPEChange age INT to age BIGINT
Index additionCREATE INDEXCreate idx_email on users(email)
Index removalDROP INDEXDrop idx_old_field

Not Supported

Schema sync does not handle:

  • Table drops: Dropping tables from source doesn’t drop them on destination
  • Table renames: Renaming source tables requires manual table mapping updates
  • Primary key changes: Modifying PKs requires manual intervention
  • Enum type changes: Enum modifications need custom migrations

For unsupported changes, pause the mirror, manually apply DDL on both source and destination, update table mappings if needed, then resume.

Running Schema Sync

Trigger schema sync for a mirror:

curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/sync-schema \
  -H "Authorization: Bearer YOUR_TOKEN"

Response

{
  "message": "Schema sync started for mirror 'prod_to_staging'"
}

Schema Sync Process

Drop Replication Slot

BunnyDB drops the existing replication slot on the source database:

SELECT pg_drop_replication_slot('bunny_prod_to_staging');
🚫

This is a breaking point. Any in-flight CDC changes are discarded. Ensure the mirror is caught up before running schema sync.

Compare Schemas

For each table in the mirror’s table mappings, BunnyDB queries source and destination schemas:

-- Source schema
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'users';
 
-- Destination schema
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'users';

Apply DDL Changes

BunnyDB generates and executes DDL statements to align destination with source:

Example: Column added on source

ALTER TABLE public.users ADD COLUMN last_login TIMESTAMP;

Example: Column removed from source

ALTER TABLE public.users DROP COLUMN deprecated_field;

Example: Type changed

ALTER TABLE public.users ALTER COLUMN age TYPE BIGINT;

Example: Index added (if replicate_indexes: true)

CREATE INDEX idx_users_email ON public.users(email);

Recreate Replication Slot

A new replication slot is created at the current LSN:

SELECT pg_create_logical_replication_slot('bunny_prod_to_staging', 'pgoutput');

Restart CDC

CDC replication resumes from the new slot’s starting LSN. This means:

  • No historical data is lost (tables already contain data from previous sync)
  • Future changes are captured from the new LSN onward
  • Changes during schema sync may be missed if sync takes a long time
⚠️

If schema sync takes several minutes and high write activity occurs on source, some changes might not be captured. For critical workloads, pause the mirror first.

Best Practice: Pause Before Schema Sync

To ensure no data loss:

# 1. Pause the mirror
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/pause \
  -H "Authorization: Bearer YOUR_TOKEN"
 
# 2. Run schema sync
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/sync-schema \
  -H "Authorization: Bearer YOUR_TOKEN"
 
# Wait for schema sync to complete (check mirror status)
 
# 3. Resume the mirror
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/resume \
  -H "Authorization: Bearer YOUR_TOKEN"

This workflow guarantees:

  • No CDC changes are missed
  • Replication slot doesn’t accumulate WAL during schema sync
  • Clean restart from known state

Monitoring Schema Sync

Check mirror status during schema sync:

curl -X GET http://localhost:8112/v1/mirrors/prod_to_staging \
  -H "Authorization: Bearer YOUR_TOKEN"

During schema sync:

{
  "name": "prod_to_staging",
  "status": "SYNCING_SCHEMA",
  "current_phase": "SCHEMA_SYNC",
  "source_peer": "source_db",
  "destination_peer": "dest_db"
}

After completion:

{
  "name": "prod_to_staging",
  "status": "RUNNING",
  "current_phase": "CDC"
}

Example Workflow

Suppose your source schema evolves:

-- Source database changes
ALTER TABLE public.users ADD COLUMN verified BOOLEAN DEFAULT false;
ALTER TABLE public.users DROP COLUMN old_status;
ALTER TABLE public.orders ALTER COLUMN amount TYPE NUMERIC(12,2);
CREATE INDEX idx_users_verified ON public.users(verified);

Sync these changes to destination:

# Pause for safety
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/pause \
  -H "Authorization: Bearer YOUR_TOKEN"
 
# Sync schema
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/sync-schema \
  -H "Authorization: Bearer YOUR_TOKEN"
 
# Wait for completion (poll status)
while true; do
  STATUS=$(curl -s http://localhost:8112/v1/mirrors/prod_to_staging \
    -H "Authorization: Bearer YOUR_TOKEN" | jq -r '.status')
 
  if [ "$STATUS" == "PAUSED" ]; then
    echo "Schema sync complete"
    break
  fi
 
  echo "Status: $STATUS, waiting..."
  sleep 5
done
 
# Resume CDC
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/resume \
  -H "Authorization: Bearer YOUR_TOKEN"

After this, the destination schema matches the source, and CDC continues replicating new changes.

Verifying Schema Sync

After schema sync, validate that schemas match:

# Check column list on source
psql -h source_db -d production -c "
  SELECT column_name, data_type, is_nullable
  FROM information_schema.columns
  WHERE table_name = 'users'
  ORDER BY ordinal_position;
"
 
# Check column list on destination
psql -h dest_db -d production -c "
  SELECT column_name, data_type, is_nullable
  FROM information_schema.columns
  WHERE table_name = 'users'
  ORDER BY ordinal_position;
"
 
# Compare outputs

Handling Complex Schema Changes

Enum Type Changes

Enums are not automatically synced. Handle manually:

-- Source: Add new enum value
ALTER TYPE order_status ADD VALUE 'cancelled';
 
-- Destination: Replicate manually
ALTER TYPE order_status ADD VALUE 'cancelled';

Then run schema sync to catch any other changes.

Primary Key Changes

Changing PKs requires recreation. Pause the mirror, manually alter both databases, then resync:

# Pause
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/pause \
  -H "Authorization: Bearer YOUR_TOKEN"
 
# Manually apply PK change on both source and destination
psql -h source_db -d production -c "
  ALTER TABLE users DROP CONSTRAINT users_pkey;
  ALTER TABLE users ADD PRIMARY KEY (id, tenant_id);
"
 
psql -h dest_db -d production -c "
  ALTER TABLE users DROP CONSTRAINT users_pkey;
  ALTER TABLE users ADD PRIMARY KEY (id, tenant_id);
"
 
# Resync table to ensure data consistency
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/resync/public.users \
  -H "Authorization: Bearer YOUR_TOKEN"
 
# Resume
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/resume \
  -H "Authorization: Bearer YOUR_TOKEN"

Table Renames

Table renames require updating table mappings:

# Source: Rename table
psql -h source_db -d production -c "ALTER TABLE users RENAME TO customers;"
 
# Update mirror table mappings
curl -X PUT http://localhost:8112/v1/mirrors/prod_to_staging/tables \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "table_mappings": [
      {
        "source_table": "public.customers",
        "destination_table": "public.users"
      }
    ]
  }'
 
# Resync to align data
curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/resync \
  -H "Authorization: Bearer YOUR_TOKEN"

Troubleshooting

Schema Sync Fails with Type Incompatibility

Cause: Type change requires table rewrite (e.g., TEXTINT).

Solution: Manually handle the migration:

-- Destination: Add new column, migrate data, drop old
ALTER TABLE users ADD COLUMN age_new INT;
UPDATE users SET age_new = age::INT WHERE age IS NOT NULL;
ALTER TABLE users DROP COLUMN age;
ALTER TABLE users RENAME COLUMN age_new TO age;

Then run schema sync to catch other changes.

Replication Slot Already Exists After Schema Sync

Cause: Slot drop failed but sync continued.

Solution: Manually drop the slot:

-- On source database
SELECT pg_drop_replication_slot('bunny_prod_to_staging');

Then retry schema sync.

Schema Sync Completes but Data Missing

Cause: Changes occurred during schema sync that weren’t captured.

Solution: Run a table-level resync for affected tables:

curl -X POST http://localhost:8112/v1/mirrors/prod_to_staging/resync/public.users \
  -H "Authorization: Bearer YOUR_TOKEN"

Next Steps