faucet-source-oracle-cdc
Oracle Database change data capture for the faucet-stream ecosystem, built on LogMiner (DBMS_LOGMNR) over the online and archived redo logs. Emits every committed insert, update and delete on the captured tables as a change envelope, resumable by SCN with no gap and no duplicate.
Runtime requirement: Oracle Instant Client must be installed where ODPI-C can load it — see
faucet-common-oracle.
Highlights
- Transactions emitted on commit only — changes are buffered per transaction and released when its
COMMITis mined; rolled-back work and savepoint-rolled-back statements never appear. Each committed transaction is one page with its own bookmark. - SCN bookmarks — resume reaches back to the start of any transaction still open at the bookmark, so long transactions spanning log switches are captured whole.
- Exactly-once —
supports_exactly_once() = true; pair with an idempotent sink. - Snapshot handoff —
capture_resume_position()returns the current SCN forfaucet mirror. - LOBs —
CLOB/BLOBvalues LogMiner reports in pieces (LOB_WRITE) are reassembled into the row. - Never skips silently — missing redo fails the run naming the SCN range; changes LogMiner cannot render fail by default.
Database prerequisites
-
ARCHIVELOG mode, with archived logs kept until faucet has mined them.
-
Supplemental logging — minimal logging at the database (SYSDBA, in the CDB root), and all-column logging on each captured table for full before/after images:
ADD SUPPLEMENTAL LOG DATA; APP.ORDERS ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;With only primary-key logging, updates carry the key and changed columns. A table that logs no key columns is rejected at startup.
-
Privileges for the capture user:
CREATE SESSION, , LOGMINING, SELECT ANY TRANSACTION, SELECT_CATALOG_ROLE, EXECUTE_CATALOG_ROLE TO cdc_user; SELECT ON APP.ORDERS TO cdc_user;CREATE TABLEis for the flush table (below). -
Where to connect — to the PDB that owns the tables (Oracle 21c+ mines per PDB and locates logs itself), or to a non-CDB / the CDB root (faucet registers the log files).
faucet doctor checks supplemental logging, full-row images and redo-log visibility, and prints the exact ALTER statement to run.
Configuration
Connection fields are documented in faucet-common-oracle.
| Field | Default | Description |
|---|---|---|
tables |
— | OWNER.TABLE list, in dictionary case. Required. |
start_position |
{ type: current } |
Fresh-run start: current or earliest (oldest available redo). |
poll_interval |
1 |
Seconds between polls that find nothing. |
idle_timeout |
30 |
End the fetch cycle after this many quiet seconds once mining has reached the current SCN. Re-mining redo from restart_scn (held back by the oldest open transaction in the database) never counts as quiet, so a long-open transaction cannot stall capture. |
max_scn_window |
500000 |
Largest SCN range per LogMiner session. |
max_staged_records |
1000000 |
Abort when one open transaction buffers more changes (null = unbounded). In a config submitted to faucet serve it may be 1 to 1,000,000; null and 0 are refused. |
max_staged_records_total |
unset (unbounded) | Abort when all open transactions together buffer more changes, counting LOB pieces and folded updates (null = unbounded). A config submitted to faucet serve must set it, to 1 to 1,000,000. |
batch_size |
1000 |
0 = transactions are aggregated into pages of up to 100,000 records (emitted at least every idle_timeout, each with its bookmark); otherwise a page per transaction. |
max_connections |
2 |
Pooled sessions. |
statement_timeout_secs |
600 |
Per-round-trip call timeout (0 disables). |
flush_table |
FAUCET_LOGMNR_FLUSH |
Table committed to before each window to force the log writer to flush redo up to the window's end. Created if missing. |
on_unsupported |
fail |
fail or skip for changes LogMiner cannot render. |
state_key |
oracle-cdc:<host>_<port>_<service>:<table> |
Bookmark key (a digest for several tables; the connect string stands in for host, port and service in that form). A bookmark under the earlier oracle-cdc:<service>:<table> key is moved to it on the first run. |
source:
type: oracle-cdc
config:
host: db.example.com
service_name: ORCLPDB1
username: cdc_user
password: ${secret:ORACLE_PASSWORD}
tables:
Change envelope
op is i / u / d; DDL on a captured table arrives as ddl (with sql) and TRUNCATE as truncate, both dropped by the cdc_unwrap transform. Values are typed from the column's data type exactly as the query source types them. before is the logged before image (where clause) and never includes LOB columns.
Bookmark
Transactions committed below commit_scn, or at it and listed in committed_xids, were emitted. Mining resumes at restart_scn, the first SCN of the oldest transaction open at the bookmark.
Failure behaviour
- Missing redo — if the logs covering the resume range were recycled or deleted, the run fails with the missing SCN range. Re-snapshot the tables, then restart capture.
- Dictionary mismatch — mining uses the current data dictionary (
DICT_FROM_ONLINE_CATALOG). Redo written before a DDL on a captured table cannot be rendered after it; if a restart has to re-mine such redo the run fails (or skips, withon_unsupported: skip). Mining near real time avoids this; after DDL during downtime, re-snapshot the table. - Unsupported changes (
UNSUPPORTEDoperations, types LogMiner cannot render, binaryXMLTypechanges) fail by default. - Partial LOB writes — an append, a write at an offset above 1, or a trim on a LOB whose current value is not part of the same transaction cannot be reassembled (LogMiner logs only the piece). It is treated as an unsupported change rather than emitted as a padded or truncated value. CLOB positions are UTF-16 code units, as Oracle counts them; when LogMiner splits one write into several pieces, it advances each later piece by the previous piece's character count, and the source corrects for that, so characters outside the Basic Multilingual Plane (emoji and the like) land where they were written. Writes that start at offset 1 and run on without a gap, with no trim, are read as a whole new value — that is how LogMiner logs
UPDATE … SET lob_col = …. ADBMS_LOB.WRITEthat overwrites only the start of a longer existing value at offset 1, without trimming it, looks identical in the redo log, so the row then carries only the written part; if your application patches LOBs in place that way, re-read those rows from the table (a snapshot orfaucet verify) rather than relying on the change stream. - DDL — column metadata is reloaded at the DDL row, so later changes in the same mining window are typed by the new columns.
- RESETLOGS — the bookmark records the database incarnation (
resetlogs_scn); only archived logs of the current incarnation are mined, and resuming a bookmark from another incarnation (after a point-in-time recovery or flashback) fails instead of mining an abandoned branch. Connected to a pluggable database, it also records the PDB's incarnation (pdb_incarnation_scn, fromV$PDB_INCARNATION), so a PDB-level point-in-time recovery or flashback — which leaves the database-wide RESETLOGS SCN unchanged — fails the resume the same way.SELECT_CATALOG_ROLE(above) covers reading it; a user who cannot read it skips the check with a warning. A bookmark written before either field existed is bound to the current incarnation on its first resume, with a warning when it lies after that incarnation's start, because a recovery before the upgrade cannot be detected — reset such a bookmark yourself if the database or PDB was recovered since. - RAC — the redo flush reaches only the connected instance, so with several open redo threads the mining window never passes the lowest thread checkpoint SCN (
V$THREAD). Changes from every instance therefore arrive once the slowest thread checkpoints past them. A thread checkpoints on a log switch or a full checkpoint, so a quiet instance can hold capture back for as long as it goes without a log switch; a warning names the delay after a minute. SetARCHIVE_LAG_TARGET(for exampleALTER SYSTEM SET ARCHIVE_LAG_TARGET = 60 SCOPE=BOTH) to force a switch on every thread at that interval, which bounds the latency. When capture is anchored at the current SCN (start_position: current, or the resume positionfaucet mirrorcaptures), the oldest transaction still open on any instance is read fromGV$TRANSACTION, so its earlier changes are mined too; a user who cannot readGV$TRANSACTIONfalls back to the connected instance'sV$TRANSACTIONwith a warning.
Source lag
Source::lag (#733) reports seconds: the commit time of the database's
current SCN minus that of the captured commit_scn (this run's last emitted
bookmark, else the applied start bookmark), via SCN_TO_TIMESTAMP. SCN
distances are not event counts, so no events or bytes are reported. When
the position is older than the database's SCN-to-time mapping (ORA-08181) no
lag is reported. The pipeline polls it on the first page, at most every 15 s
after, and when the run ends, exporting faucet_source_lag_seconds{pipeline,row,connector};
faucet status --probe and faucet doctor ask it from the stored bookmark,
and sla.max_lag_seconds turns it into an SLA. A failing lag query is logged
once and reported as no lag — it never fails a run.
Mirroring
Pair with cdc_unwrap and an upsert-capable sink (write_mode: upsert, delete_marker: { field: __op, values: [d] }). For an initial load, faucet mirror captures capture_resume_position() before bulk-copying with the oracle query source, then streams changes from that SCN.
Capabilities
Declares exactly_once (deterministic replay from the SCN bookmark) and reports_lag (seconds behind the current SCN). faucet list --json prints the declaration.
Timeouts, cancellation and retries
The wait for each captured page, the pause between LogMiner polls and the lag and capture-position queries run through the pipeline's CallContext (#880): the run's deadline and cancel token end an idle cycle promptly with an error (the pipeline has already stopped reading and flushed what it took), and the capture thread stops at its next poll. Queries count as query round trips in faucet_source_roundtrips_total. Nothing is retried; the next run resumes from the last persisted SCN.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.