Platform Services

Database CDC Service

Stream PostgreSQL row-level changes to real-time subscribers using Change Data Capture.

The Database Service streams changes from your application database to real-time subscribers using PostgreSQL Change Data Capture (CDC). Administrators define domain mappings that control which table changes produce events and how payloads are constructed.

Info

Mappings always publish to a topic — the central routing unit for all event delivery. To understand how mappings, topics, schemas, and webhooks fit together, see Core Concepts.

How It Works

PostgreSQL WAL
  → Logical Replication Slot
  → CDC Reader (worker)
  → Mapping Evaluator
  → Event Router
  → Redis Pub/Sub
  → Socket Gateway
  → Connected Clients

Warning

The platform monitors an external application database — not its own metadata database. These are two separate PostgreSQL instances.

Two Databases

ConnectionConfigured InPurpose
Platform DB.envDATABASE_URL or POSTGRES_*Realtime's own metadata (topics, schemas, mappings, keys)
Target DBApplication Settings (per environment)Your application database that the CDC reader monitors

Info

CDC database connections are configured per-application per-environment in the Admin UI under Application Settings. There is no global CDC_DATABASE_URL environment variable — each application and environment (Development, Staging, Production) can point to a different target database.

Target Database Preparation

Before the CDC reader can monitor your application database, the target database needs to be configured with the correct settings and user privileges.

Enable logical replication

On your target database, ensure wal_level is set to logical:

SHOW wal_level;
-- If not 'logical':
ALTER SYSTEM SET wal_level = 'logical';
-- Then restart PostgreSQL

Warning

Changing wal_level requires a PostgreSQL restart. Plan accordingly for production databases.

Info

On managed databases (AWS RDS, Cloud SQL, Supabase, etc.), enable logical replication through the provider's parameter group or dashboard settings.

Create a dedicated replication user

Create a database user with the privileges required for CDC. This user needs REPLICATION, LOGIN, and ownership or superuser rights to manage publications on monitored tables.

-- Create the CDC user
CREATE ROLE realtime_cdc WITH LOGIN PASSWORD 'your-secure-password' REPLICATION;

-- Grant connect access to the target database
GRANT CONNECT ON DATABASE your_app_db TO realtime_cdc;

-- Grant usage on all schemas the CDC reader will monitor
GRANT USAGE ON SCHEMA public TO realtime_cdc;
GRANT USAGE ON SCHEMA salesforce TO realtime_cdc;  -- if using Salesforce schema
-- Repeat for any additional schemas

-- Grant SELECT on all tables in those schemas
GRANT SELECT ON ALL TABLES IN SCHEMA public TO realtime_cdc;
GRANT SELECT ON ALL TABLES IN SCHEMA salesforce TO realtime_cdc;
-- Repeat for any additional schemas

-- IMPORTANT: Grant superuser so the CDC user can manage publications
-- on tables it doesn't own (e.g. tables managed by Salesforce, ORM migrations, etc.)
ALTER ROLE realtime_cdc WITH SUPERUSER;

Tip

If you prefer not to grant full SUPERUSER, PostgreSQL 15+ supports a more targeted role:

-- PostgreSQL 15+ only — grants publication management without full superuser
GRANT pg_publication_owner TO realtime_cdc;

Warning

Without SUPERUSER or pg_publication_owner, the CDC reader can only add tables to publications if the CDC user owns those tables. If you see errors like "must be owner of table", the user needs elevated privileges.

Verify replication settings

Confirm the database is ready for logical replication:

-- Should return 'logical'
SHOW wal_level;

-- Should show the CDC user with replication privilege
SELECT rolname, rolreplication, rolsuper
FROM pg_roles
WHERE rolname = 'realtime_cdc';

-- Check max_replication_slots (default is 10, increase if needed)
SHOW max_replication_slots;

-- Check max_wal_senders (must be > 0)
SHOW max_wal_senders;

Connecting in Application Settings

Open Application Settings

In the Admin UI, navigate to Applications and select your application.

Configure CDC for each environment

For each environment (Development, Staging, Production), click the edit icon on the CDC row and enter the connection details for the replication user you created above:

  • Connection String: postgresql://realtime_cdc:your-secure-password@host:5432/your_app_db
  • Or use Individual Fields mode to enter host, port, user, password, and database separately
  • Set the Replication Slot Name (default: realtime_cdc_slot)
  • Use Test Connection to verify connectivity before saving

Create CDC subscriptions

Navigate to Operational Dashboards → Database Pipeline and create subscriptions for the mappings you want to monitor. Activate them to start streaming changes.

Note

The replication slot and publication are created automatically when a CDC subscription is first activated. No manual SQL is needed beyond the user setup above.

Domain Mappings

Mappings define which table changes become realtime events. Tables do not automatically stream events — every mapping must be explicitly configured.

Mapping Structure

{
  "id": "map_session_status_v1",
  "table": "sessions",
  "events": ["insert", "update"],
  "topic": "session.status",
  "triggerColumns": ["status", "ended_at"],
  "when": {
    "or": [
      { "changed": "status" },
      { "changed": "ended_at" }
    ]
  },
  "payload": {
    "sessionId": "$row.id",
    "status": "$row.status",
    "endedAt": "$row.ended_at",
    "campusId": "$row.campus_id"
  }
}

Configuration Fields

FieldRequiredDescription
tableYesSource table to monitor (use schema.table for non-public schemas, e.g. salesforce.sp_exam_session__c)
topicYesTarget domain topic for events
eventsYesOperations to capture: insert, update, delete
triggerColumnsNoColumns that trigger the event (update only fires if these change)
whenNoConditional rules (see below)
payloadYesPayload template with $row.* column references
environmentNoEnvironment scope (development, staging, production)

Creating Mappings

  1. Navigate to Registry → Mappings in the Admin UI
  2. Click Create Mapping
  3. Select the source table, trigger columns, and target topic
  4. Define the payload field mapping
  5. Optionally add conditions
  6. Save

Mapping Conditions

Mappings support conditional rules to control when events fire:

Changed Column

Fire only when a specific column changes:

{ "changed": "status" }

Equality Check

Fire only when a column equals a specific value:

{ "eq": ["$row.status", "active"] }

Logical Operators

Combine conditions with and / or:

{
  "and": [
    { "changed": "status" },
    { "eq": ["$row.status", "active"] }
  ]
}
{
  "or": [
    { "changed": "status" },
    { "changed": "ended_at" }
  ]
}

Payload Templates

Payload fields use $row.* references to extract values from the changed row:

{
  "sessionId": "$row.id",
  "status": "$row.status",
  "endedAt": "$row.ended_at",
  "campusId": "$row.campus_id"
}

The mapping evaluator resolves these references at runtime using the actual row data from the CDC event.

Mapping Validation

Mappings are validated against the target topic's schema:

  • Required fields must be present in the payload template
  • Field types must be compatible
  • Schema violations are reported inline in the Admin UI

Environment Promotion

Mappings support a three-stage promotion workflow:

Development → Staging → Production

Create in Development

Create and test mappings in the Development environment. Each mapping starts with a v1 snapshot.

Promote to Staging

Use the Environment Grid or API to promote tested mappings to Staging.

Promote to Production

After validation in Staging, promote to Production. The promote button is hidden when already in Production.

Environment Grid

The Environment Grid page (/environments) shows all mappings across all environments with color-coded sync indicators:

  • Green — in sync across environments
  • Yellow — newer version available for promotion
  • Red — missing in target environment

Outbox Pattern

In addition to CDC, the platform supports the transactional outbox pattern:

Application write
  → event_outbox table insert (in same transaction)
  → Outbox Worker polls for unprocessed rows
  → Event Router
  → Redis Pub/Sub
  → Clients

The event_outbox table uses a partial index on unprocessed rows for efficient polling.

Verifying CDC

  1. Check the Database Pipeline dashboard in the Admin UI — the reader state should show Streaming
  2. Make a change in a monitored table
  3. Look for CDC event routed log entries in the backend logs
  4. Check the Event History tab in the Database Pipeline page to see captured events
  5. Use the Event Debugger in the Admin UI to inspect routed events

Troubleshooting

ErrorCauseFix
No CDC database configuredApplication doesn't have CDC connection set for this environmentGo to Application Settings and configure the CDC database URL
must be owner of tableCDC user doesn't have permission to manage publications on this tableGrant SUPERUSER or pg_publication_owner to the CDC user
relation does not existWrong schema — table has a schema prefix (e.g. salesforce.my_table)Ensure the table name in your mapping includes the schema prefix
wal_level is not logicalTarget database hasn't been configured for logical replicationSet wal_level = 'logical' and restart PostgreSQL
replication slot already existsSlot from a previous session wasn't cleaned upDrop it with SELECT pg_drop_replication_slot('realtime_cdc_slot');

API Reference

MethodPathDescription
GET/api/database/mappingsList all mappings
GET/api/database/mappings/statusEnvironment status for all mappings
GET/api/database/mappings/:idGet mapping by ID
POST/api/database/mappingsCreate mapping
PATCH/api/database/mappings/:idUpdate mapping
DELETE/api/database/mappings/:idDelete mapping
GET/api/database/mappings/topic/:topicGet mappings by topic
GET/api/database/mappings/:id/versionsList versions
POST/api/database/mappings/:id/versionsCreate version snapshot
POST/api/database/mappings/:id/promotePromote to next environment
GET/api/database/subscriptionsList subscriptions
POST/api/database/subscriptionsCreate subscription
POST/api/database/subscriptions/:id/activateActivate
POST/api/database/subscriptions/:id/deactivateDeactivate