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 ClientsWarning
The platform monitors an external application database — not its own metadata database. These are two separate PostgreSQL instances.
Two Databases
| Connection | Configured In | Purpose |
|---|---|---|
| Platform DB | .env — DATABASE_URL or POSTGRES_* | Realtime's own metadata (topics, schemas, mappings, keys) |
| Target DB | Application 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 PostgreSQLWarning
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
| Field | Required | Description |
|---|---|---|
table | Yes | Source table to monitor (use schema.table for non-public schemas, e.g. salesforce.sp_exam_session__c) |
topic | Yes | Target domain topic for events |
events | Yes | Operations to capture: insert, update, delete |
triggerColumns | No | Columns that trigger the event (update only fires if these change) |
when | No | Conditional rules (see below) |
payload | Yes | Payload template with $row.* column references |
environment | No | Environment scope (development, staging, production) |
Creating Mappings
- Navigate to Registry → Mappings in the Admin UI
- Click Create Mapping
- Select the source table, trigger columns, and target topic
- Define the payload field mapping
- Optionally add conditions
- 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 → ProductionCreate 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
→ ClientsThe event_outbox table uses a partial index on unprocessed rows for efficient polling.
Verifying CDC
- Check the Database Pipeline dashboard in the Admin UI — the reader state should show Streaming
- Make a change in a monitored table
- Look for
CDC event routedlog entries in the backend logs - Check the Event History tab in the Database Pipeline page to see captured events
- Use the Event Debugger in the Admin UI to inspect routed events
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| No CDC database configured | Application doesn't have CDC connection set for this environment | Go to Application Settings and configure the CDC database URL |
| must be owner of table | CDC user doesn't have permission to manage publications on this table | Grant SUPERUSER or pg_publication_owner to the CDC user |
| relation does not exist | Wrong 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 logical | Target database hasn't been configured for logical replication | Set wal_level = 'logical' and restart PostgreSQL |
| replication slot already exists | Slot from a previous session wasn't cleaned up | Drop it with SELECT pg_drop_replication_slot('realtime_cdc_slot'); |
API Reference
| Method | Path | Description |
|---|---|---|
GET | /api/database/mappings | List all mappings |
GET | /api/database/mappings/status | Environment status for all mappings |
GET | /api/database/mappings/:id | Get mapping by ID |
POST | /api/database/mappings | Create mapping |
PATCH | /api/database/mappings/:id | Update mapping |
DELETE | /api/database/mappings/:id | Delete mapping |
GET | /api/database/mappings/topic/:topic | Get mappings by topic |
GET | /api/database/mappings/:id/versions | List versions |
POST | /api/database/mappings/:id/versions | Create version snapshot |
POST | /api/database/mappings/:id/promote | Promote to next environment |
GET | /api/database/subscriptions | List subscriptions |
POST | /api/database/subscriptions | Create subscription |
POST | /api/database/subscriptions/:id/activate | Activate |
POST | /api/database/subscriptions/:id/deactivate | Deactivate |