TL;DR
| |
| |
Every COMMIT that changes the result of that query fires on_change with the affected rowids. No polling, no triggers, no GoldenGate.
Why
When another system must react to changes in Oracle (invalidate a cache, replicate to another database, kick off a process), the usual approach is asking every N seconds with WHERE updated_at > :last. It works until it does not: rows without a date column, deletes that leave no trace, useless load on the database.
Continuous Query Notification flips the flow: you register a query and Oracle calls you when it changes. In query mode (SUBSCR_QOS_QUERY) it notifies only if the result of that query changes; in object mode (without that flag) it notifies on any change to the tables involved. SUBSCR_QOS_ROWIDS includes the rowids in the notification, so the callback can read exactly those rows.
The case: replicating Oracle into PostgreSQL
oracle_fdw lets you query Oracle tables from PostgreSQL as if they were local, and it is tempting to think a trigger on the foreign table will fire when data changes in Oracle. It does not: triggers on a foreign table only fire for writes made from PostgreSQL through it. The oracle_fdw author says it plainly: there is no automatic way to keep a PostgreSQL table in sync with an Oracle table from the PostgreSQL side; Oracle has to trigger it.
With CQN, the small Python service is the missing piece:
| |
Deletes arrive with row.operation & oracledb.OPCODE_DELETE and a rowid that no longer exists: delete in PostgreSQL by the key you stored against that rowid, or flag the row.
Requirements
- Permission:
GRANT CHANGE NOTIFICATION TO app_user, besidesSELECTon the tables. events=Trueon the connection that subscribes; without itsubscribe()fails.- Network back-channel. By default the database opens a connection towards your application to deliver notifications: the client machine needs an IP reachable from the Oracle server and an open port (
subscribe(..., ip_address=…, port=…)pins which). Behind NAT, inside a container, or with a firewall in between, notifications simply never arrive and nobody tells you. client_initiated=Trueremoves that problem: your app opens the connection. It requires Thick mode (oracledb.init_oracle_client()with Instant Client 19.4 or later) and a 19.4+ database. In Thin mode, python-oracledb’s default, CQN works but only in the classic mode.job_queue_processesgreater than zero on the database: notifications are dispatched through the job queue.
Traps
ORA-29977: Unsupported column type for query registration in guaranteed mode: in query mode some column types are not allowed (LOBs, among others), andSELECT *drags them in. List the columns you need, or dropSUBSCR_QOS_QUERYand work in object mode.- Notifications are not durable: if your service is down during a change, it does not get it. On startup, reconcile first (by
updated_at, or in full) and subscribe afterwards.timeout=0onsubscribekeeps the subscription until the connection dies. - One subscription per live connection: the
events=Trueconnection must stay open; give it keepalives and reconnect with a re-subscribe if it drops. - Heavy volume (thousands of commits per second) is not CQN territory: that is where redo-log CDC comes in (GoldenGate, or Debezium with its Oracle connector over LogMiner).
SELECTwith non-deterministic functions or over complex views may not register in query mode; simplify the query or fall back to object mode.
Reference: Continuous Query Notification in the python-oracledb documentation and the oracle_fdw discussion.