oracle icon
Batch icon
Queries icon
SlackIncomingWebhook icon
Trigger icon

Incremental Oracle order sync with a polling trigger

Poll Oracle for new orders, enrich them, claim the exact id window race-safely, and MERGE into a mart so replays never duplicate.

Categories
Data

Run a change-data-capture style incremental sync from an Oracle Database with Kestra. A polling trigger captures a bounded batch of new orders, enriches them with customer attributes, claims exactly that id window race-safely, and upserts the result into a reporting mart with a MERGE so a replay never duplicates. This is the idempotent, at-least-once pattern teams reach for when an Oracle scheduler job and a plain INSERT keep producing duplicate rows or skipping records under concurrency.

How it works

  1. The poll_new_orders trigger (io.kestra.plugin.jdbc.oracle.Trigger) polls every PT5M for orders rows where processed_flag IS NULL, ordered by order_id, fetching only the first batch_size rows (default 500). Its sql LEFT JOINs customers so each captured row already carries customer_name and region, and fetchType: STORE writes the batch to internal storage as trigger.uri.
  2. The trigger afterSQL claims that exact window, marking only rows whose order_id is at or below the highest id in the batch. Because order_id is monotonically increasing, rows inserted concurrently get a higher id and are simply picked up next poll, avoiding the race a blanket UPDATE ... WHERE processed_flag IS NULL would create.
  3. The stage_batch task (io.kestra.plugin.jdbc.oracle.Batch) bulk loads the enriched batch from trigger.uri into the transient orders_enriched_stg table in chunks of batch_size.
  4. The merge_into_mart task (io.kestra.plugin.jdbc.oracle.Queries) runs a MERGE into orders_enriched keyed on order_id and clears the staging table in one transaction (transaction: true). The MERGE makes replays idempotent.
  5. The notify_batch task (io.kestra.plugin.slack.notifications.SlackIncomingWebhook) posts the synced row count to Slack.

What you get

  • Idempotent mart loads: the MERGE on order_id absorbs replays instead of inserting duplicates.
  • Race-safe claiming: only the exact emitted id window is marked processed.
  • Built-in resilience: stage_batch, merge_into_mart, and notify_batch each retry (constant, 3 attempts).
  • Operator visibility: a per-batch Slack summary plus an errors alert naming the execution id and trigger.uri to replay.
  • Back-pressure, not corruption: concurrency.limit is 1, so a slow mart load just delays the next poll.

Who it's for

  • Data engineers running incremental syncs out of Oracle into a reporting mart.
  • Analytics teams who need exactly-once-at-the-mart semantics without a full CDC stack.
  • Platform teams replacing brittle Oracle scheduler jobs with observable, replayable pipelines.

Why orchestrate this with Kestra

Oracle's own scheduler (DBMS_SCHEDULER) can run SQL on a clock, but it cannot store the captured batch as an artifact, retry individual steps, alert with the exact batch to replay, or give you execution-level lineage when a load half-fails. Kestra adds an event-driven polling trigger, per-task retries, a flow-level error handler, and declarative YAML you version in Git, turning a fragile "claim then load" job into a replayable, auditable pipeline.

Prerequisites

  • A reachable Oracle Database instance. The JDBC URL uses the thin driver service form (jdbc:oracle:thin:@//host:1521/service), set in the oracle_url variable.
  • An orders table with a numeric, sequence-generated order_id and a processed_flag column, a customers table to join against, an orders_enriched mart with a unique order_id, and an orders_enriched_stg staging table of the same shape.
  • Least privilege: SELECT on orders and customers, UPDATE on processed_flag, and INSERT/UPDATE/DELETE on the mart and staging tables.

Secrets

  • ORACLE_USERNAME: the Oracle database user used by the trigger and the mart load.
  • ORACLE_PASSWORD: the password for that Oracle user.
  • SLACK_WEBHOOK: the Slack Incoming Webhook URL the batch summary and failure alert post to.

Quick start

  1. Create the ORACLE_USERNAME, ORACLE_PASSWORD, and SLACK_WEBHOOK secrets.
  2. Update the oracle_url variable to point at your instance and service name.
  3. Create orders_enriched_stg matching orders_enriched, with a unique constraint on orders_enriched.order_id.
  4. Adjust the trigger sql, the batch_size variable, the staging INSERT, and the MERGE to match your schema.
  5. Enable the flow; the trigger starts polling automatically.

How to extend

  • Catch up on a backlog by raising batch_size or lowering the trigger interval (default drains about 6,000 rows per hour).
  • Swap the watermark: use a last_modified timestamp instead of processed_flag for update-aware syncs.
  • Fan out to more destinations by adding tasks after the MERGE (for example a downstream warehouse load).
  • On Oracle RAC or CACHE/NOORDER sequences, switch to ORDER NOCACHE to keep the monotonic-id guarantee.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.