Change Data Capture
Stream row-level changes from a database. A CDC source captures inserts, updates and deletes, optionally hands each subscriber a consistent snapshot of existing rows first, and delivers everything as driver-neutral change events. Sources are addressable by their entry ID and consumed from Lua via the cdc module.
Entry Kinds
| Kind | Description |
|---|---|
db.cdc.postgres |
Postgres logical replication (pgoutput plugin) |
db.cdc.sqlite |
SQLite writes observed through a db.sql.sqlite resource |
Both kinds expose the same Lua API, the same source info record and the same change event shape. What differs is the guarantee set, published per source as capabilities.
Postgres Configuration
- name: pg_cdc
kind: db.cdc.postgres
host: ${env:DB_HOST}
port: 5432
database: app
username: ${env:DB_USER}
password: ${env:app.secrets:db_password}
slot_name: wippy_slot
publication: wippy_pub
tables:
- public.users
- public.orders
snapshot: true
streaming: true
standby_interval: "10s"
status_interval: "10s"
lifecycle:
auto_start: true
| Field | Type | Default | Description |
|---|---|---|---|
host |
string | required | Postgres host |
port |
int | required | Postgres port (must be > 0) |
database |
string | required | Database name |
username |
string | required | Replication user (must have REPLICATION privilege) |
password |
string | required | Password (inline or ${env:NAME}) |
slot_name |
string | required | Logical replication slot name |
publication |
string | - | Postgres publication; required when tables is empty |
tables |
[]string | - | Tables to capture (schema.table); omit to use the publication's tables |
snapshot |
bool | false | Entry default for the per-subscriber snapshot handoff |
streaming |
bool | false | Use the streaming pgoutput protocol version |
temporary |
bool | false | Use a temporary replication slot (removed on disconnect) |
failover |
bool | false | Enable failover slot mode (mutually exclusive with temporary) |
standby_interval |
duration | 10s |
Standby status message interval (e.g. 10s) |
status_interval |
duration | 30s |
Status update interval to the server |
snapshot_fetch_size |
int | 1000 | Rows fetched per snapshot batch (must be >= 0) |
max_transaction_changes |
int | 1000000 | Maximum changes buffered while decoding one transaction |
max_transaction_bytes |
int | 268435456 | Maximum logical bytes buffered while decoding one transaction (256 MiB) |
max_inflight_changes |
int | 1000000 | Maximum changes held across all in-flight transactions |
max_inflight_bytes |
int | 268435456 | Maximum logical bytes held across all in-flight transactions (256 MiB) |
subscriptions |
object | - | Subscription admission limits, see Subscription Limits |
options |
map | - | Extra connection options |
lifecycle |
object | - | Lifecycle configuration |
Zero on any max_* field selects the default; the decoder is never unbounded. Negative values are rejected.
Credentials resolve ${env:NAME} placeholders through the environment registry at decode time.
SQLite Configuration
A SQLite source does not open its own database. It borrows an existing db.sql.sqlite resource and subscribes to that resource's committed-mutation observer, so it captures exactly the writes made through that Wippy SQL resource — writes by another process, another connection or an external tool are not observed.
- name: cdcdb
kind: db.sql.sqlite
file: /var/data/app.db
lifecycle:
auto_start: true
- name: changes
kind: db.cdc.sqlite
db_resource: app:cdcdb
tables:
- users
- orders
snapshot: true
status_interval: "30s"
lifecycle:
auto_start: true
| Field | Type | Default | Description |
|---|---|---|---|
db_resource |
string | required | Entry ID of the db.sql.sqlite resource to observe |
name |
string | - | Accepted; the source name is always the entry ID |
tables |
[]string | - | Tables to capture; omit for all tables |
snapshot |
bool | false | Entry default for the per-subscriber snapshot handoff |
status_interval |
duration | 30s |
Status update interval |
subscriptions |
object | - | Subscription admission limits, see Subscription Limits |
lifecycle |
object | - | Lifecycle configuration |
The source declares the SQL resource as a lifecycle requirement, so the supervisor starts the database first and restarts the source when the database generation is replaced.
sqlite_preupdate_hook build tag. Official builds include it. Without the tag the driver fails closed: creating a db.cdc.sqlite entry returns sqlite cdc requires the sqlite_preupdate_hook build tag rather than starting a source that captures nothing.
Subscription Limits
Each source admits a bounded number of subscribers and reserves their worst-case backlog up front. A snapshot slot stays reserved until the snapshot-enabled stream closes.
subscriptions:
max_subscriptions: 1024
max_snapshot_subscriptions: 4
max_bytes: 268435456
| Field | Type | Default | Description |
|---|---|---|---|
max_subscriptions |
int | 1024 | Concurrent subscriptions admitted by the source |
max_snapshot_subscriptions |
int | 4 | Concurrent snapshot-enabled subscriptions |
max_bytes |
int | 268435456 | Total reserved subscriber backlog bytes (256 MiB) |
Zero selects the default; negative values are rejected. Exhausting a limit fails the subscription with a retryable errors.UNAVAILABLE.
How It Works
- A Postgres source connects as a replication user and creates (or resumes) the slot named by
slot_name. A SQLite source borrows itsdb_resourceand subscribes to that resource's committed-mutation observer. - Row changes are decoded into driver-neutral change events with
opofinsert,update,deleteortruncate. - A subscriber whose stream has
snapshotenabled — from the entry'ssnapshotfield or fromopts.snapshoton the stream — first receives the existing rows as events withop = "snapshot", then continues into live changes with no gap between the two. - A Postgres source periodically acknowledges the LSN so the server can release WAL segments (
standby_interval). - The source registers under its entry ID; Lua code subscribes with
cdc.stream.
Capabilities
Every source publishes what it guarantees, so consumers branch on capabilities rather than on the entry kind.
| Capability | db.cdc.postgres |
db.cdc.sqlite |
Meaning |
|---|---|---|---|
snapshot |
yes | yes | Supports the atomic snapshot/live handoff |
capture_resume |
yes, unless temporary |
no | Source progress survives a reconnect |
replayable |
no | no | Individual subscribers can replay past events |
captures_external_writes |
yes | no | Captures writes made outside this runtime |
before_images |
no | yes | Guarantees a full pre-change row image on update and delete |
coalesced |
no | yes | Repeated writes to a row within a transaction may arrive coalesced |
Capability flags describe source progress, not durable delivery: no driver replays events for an individual subscriber that fell behind or disconnected.
Source Info
Each source is described by an info record, returned by cdc.source and cdc.list_sources.
| Field | Type | Description |
|---|---|---|
id |
string | Entry ID |
kind |
string | db.cdc.postgres or db.cdc.sqlite |
name |
string | Source name (the entry ID) |
state |
string | unknown, starting, running, faulted or stopped |
generation |
string | Current source generation; changes when the source is replaced |
epoch |
string | Same value as generation |
engine |
string | Engine name (sqlite) |
db_resource |
string | Observed SQL resource entry ID (db.cdc.sqlite) |
slot |
string | Replication slot name (db.cdc.postgres) |
publication |
string | Postgres publication, when configured |
tables |
[]string | Captured tables, when configured |
streaming |
bool | Whether the source is currently running |
failover |
bool | Failover slot mode (db.cdc.postgres) |
temporary |
bool | Temporary slot (db.cdc.postgres) |
snapshot |
bool | Entry-level snapshot default |
faulted |
bool | Whether the source is in the faulted state |
error |
string | Last source error, when one is recorded |
admission |
object | active, snapshots, reserved_bytes, rejected |
capabilities |
object | See Capabilities |
admission counts reservations, not queue fill: active is the admitted subscription count, snapshots the snapshot-enabled subset, reserved_bytes the reserved backlog budget, and rejected the cumulative number of subscriptions refused by the limits.
Permissions
| Action | Resource | Description |
|---|---|---|
cdc.source |
Source entry ID | Read source info; also filters cdc.list_sources |
cdc.subscribe |
Source entry ID | Open a change stream |
CDC authority is separate from database access: a source can expose every captured row, including before images. Stream filters narrow delivery only; they never grant access to a source.
- name: cdc_reader
kind: security.policy
policy:
effect: allow
actions: [cdc.source, cdc.subscribe]
resources: [app:changes]
See Also
- CDC Module - Lua streaming API
- Database - SQL database services
- Environment - Resolving credentials via
${env:NAME} - Security - Policies and actions