Skip to content

feat(connectors): add Apache Airflow trigger sink - #3716

Open
avirajkhare00 wants to merge 8 commits into
apache:masterfrom
avirajkhare00:feat/connectors-airflow-trigger-sink
Open

feat(connectors): add Apache Airflow trigger sink#3716
avirajkhare00 wants to merge 8 commits into
apache:masterfrom
avirajkhare00:feat/connectors-airflow-trigger-sink

Conversation

@avirajkhare00

@avirajkhare00 avirajkhare00 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add iggy_connector_airflow_sink that triggers Airflow DAG runs (POST /api/v1/dags/{dag_id}/dagRuns) from Iggy poll batches (one run per batch, not per message)
  • conf.messages carries the batch; optional conf.iggy metadata; deterministic batch dag_run_id; HTTP 409 treated as success
  • Auth modes none / basic / bearer; optional dag_id_header groups a poll into one run per DAG id
  • Permanent 4xx fails the batch (no silent drop); WireMock + real Airflow e2e; tracked by feat(connectors): add Apache Airflow trigger connector #3715

Design note

Airflow runs are jobs, not stream events. High-traffic 1:1 message→run would overwhelm the scheduler. Volume is controlled via stream batch_length / poll_interval.

Test plan

  • cargo fmt / cargo sort --no-format --workspace / clippy on the sink crate
  • cargo test -p iggy_connector_airflow_sink (12 unit tests)
  • cargo test -p integration -- connectors::airflow (WireMock: 3 messages → 1 DAG-run POST)
  • Local e2e: Iggy server + connectors + Airflow 2.10.5 standalone (5 messages → 1 run iggy-0-0-4-5, re-POST 409)

Review follow-up

Addressed @kriti-sc feedback: batch trigger default, explicit idempotent dag_run_id, fail batch on permanent HTTP errors.

Add a sink plugin that consumes Iggy messages and creates Airflow DAG
runs via the stable REST API, with basic/bearer auth, deterministic
dag_run_id for idempotent retries, and WireMock integration tests.

Closes apache#3715
@github-actions

Copy link
Copy Markdown

Thanks for the PR. It is labeled S-waiting-on-review and queued for review.

Slash commands (own line, regular comment) move it around the queue:

  • /ready - back to S-waiting-on-review after addressing feedback
  • /author - flip to S-waiting-on-author while you finish changes
  • /request-review @user-or-team - request a reviewer

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Jul 21, 2026
Adjust table separators and link bare URLs so MD060/MD034 pass.
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.18%. Comparing base (c5fec1f) to head (2946a60).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3716      +/-   ##
============================================
- Coverage     74.54%   71.18%   -3.37%     
+ Complexity      969      950      -19     
============================================
  Files          1306     1301       -5     
  Lines        150028   148192    -1836     
  Branches     125462   123915    -1547     
============================================
- Hits         111840   105487    -6353     
- Misses        34686    39406    +4720     
+ Partials       3502     3299     -203     
Components Coverage Δ
Rust Core 74.62% <ø> (-0.22%) ⬇️
Java SDK 63.10% <ø> (+0.39%) ⬆️
C# SDK 33.11% <ø> (-39.17%) ⬇️
Python SDK 92.27% <ø> (ø)
PHP SDK 84.52% <ø> (ø)
Node SDK 80.77% <ø> (-11.48%) ⬇️
Go SDK 18.18% <ø> (-24.91%) ⬇️
see 210 files with indirect coverage changes
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

cargo-machete failed CI because tokio was declared but not used.
finalize_pr failed solely because LowLevelE2E_Client.GetClientsReflectsSessionRemovalAfterDisconnect failed (278 passed, 1 failed). No C++ changes in this PR.
@@ -0,0 +1,53 @@
# Licensed to the Apache Software Foundation (ASF) under one

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this sink is an anti-pattern because Airflow is meant for the batch processing usecase while Iggy is a streaming system. More so, because a lot of other Iggy sinks also operate on message batch level and not individual message level.

This sink creates a dag run for each Iggy message, so for a high-traffic topics, it will end up overwhelming Airflow.

Two solutions:

  1. implement an apply_function like the Kafka support in Airflow. That way the sink can be configured to trigger dag only when a message passes that filter.
  2. Consider triggering Airflow on the batch and not individual messages.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — one DAG run per message is the wrong unit of work for Airflow.

Pushed a redesign: one DAG run per consume poll batch (not per message).

  • conf.messages is an array of {offset, id, timestamp, payload} for the batch
  • Optional conf.iggy carries stream/topic/partition + first/last offset + message_count
  • Stream batch_length / poll_interval control volume; batch_length = 1 covers low-rate command topics
  • If dag_id_header yields multiple DAG ids in one poll, we still do one run per DAG-id group, not one run per message

Verified with unit tests, WireMock integration (3 msgs → 1 POST), and real Airflow 2.10.5 e2e (5 msgs → 1 run iggy-0-0-4-5).

In-sink apply_function-style filtering is still out of scope for this PR; selective triggering is better via a dedicated topic (or a later transform). Happy to discuss if you want that in a follow-up.

| **Type** | Sink (trigger) |
| **Direction** | Iggy → Airflow |
| **API** | Airflow REST (`api_prefix` default `/api/v1`) |
| **Idempotency** | Deterministic `dag_run_id`; HTTP **409** treated as success |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My Airflow knowledge maybe outdated, but afaik dag_run_id would be manual__timestamp which wouldn't be idempotant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on the default Airflow form — that would only apply if we omitted dag_run_id.

We always set an explicit deterministic id, so Airflow does not generate manual__timestamp:

  • Before (per-message): iggy-{partition}-{offset}-{message_id_hex}
  • Now (batch): iggy-{partition}-{first_offset}-{last_offset}-{message_count}

Same poll range → same id on redelivery; Airflow returns 409, which we treat as success. Documented in the README with the request shape and a note that we never rely on auto-generated run ids.

let skipped = (total as u64).saturating_sub(processed);
error!(
"{CONNECTOR_NAME} ID: {} aborting batch after {} consecutive failures \
({} remaining messages skipped)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about skipping messages here.
Classifying errors into 3 parts-

  1. Transient Airflow related - Might not apply to the remainder of messages in the batch.
  2. Malformed Iggy message - Same as above.
  3. Permanent error such as auth or Airflow down - Might be better to fail the sink.

Feel free to add your reasoning or cases I might have missed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed the old per-message skip path was too loose for permanent failures.

With batch triggering the consume path is simpler:

  1. Transient (429/5xx/network): middleware retries, then Err so the runtime can redeliver the same batch (same dag_run_id → 409 if already created).
  2. Malformed single payload: skip that entry when building conf.messages, count an error; if every entry fails conversion, return error for the batch.
  3. Permanent HTTP (401/403/404/400/422, auth/missing DAG, etc.): return PermanentHttpError for the whole batch — do not silent-drop and advance as if the run was created.

Auth / Airflow-down style permanent failures no longer drop messages and continue.

@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Jul 23, 2026
Airflow runs are jobs; one DAG run per message overwhelms the
scheduler on busy topics. Create one run per consume batch with
conf.messages, a deterministic batch dag_run_id, and fail the batch
on permanent HTTP errors instead of silent drops.

Addresses review feedback on apache#3716.
@avirajkhare00

Copy link
Copy Markdown
Contributor Author

/ready

@github-actions github-actions Bot added S-waiting-on-review PR is waiting on a reviewer and removed S-waiting-on-author PR is waiting on author response labels Jul 23, 2026
@avirajkhare00
avirajkhare00 requested a review from kriti-sc July 27, 2026 08:37
@slbotbm

slbotbm commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@avirajkhare00 looks like your lock file is stale which is leading to CI failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review PR is waiting on a reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants