feat(connectors): add Apache Airflow trigger sink#3716
Conversation
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
|
Thanks for the PR. It is labeled Slash commands (own line, regular comment) move it around the queue:
See CONTRIBUTING.md for details. |
Adjust table separators and link bare URLs so MD060/MD034 pass.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3716 +/- ##
============================================
- Coverage 74.66% 72.91% -1.75%
Complexity 950 950
============================================
Files 1304 1301 -3
Lines 149553 148290 -1263
Branches 125064 123956 -1108
============================================
- Hits 111663 108132 -3531
- Misses 34393 36766 +2373
+ Partials 3497 3392 -105
🚀 New features to boost your workflow:
|
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 | |||
There was a problem hiding this comment.
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:
- implement an
apply_functionlike the Kafka support in Airflow. That way the sink can be configured to trigger dag only when a message passes that filter. - Consider triggering Airflow on the batch and not individual messages.
There was a problem hiding this comment.
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.messagesis an array of{offset, id, timestamp, payload}for the batch- Optional
conf.iggycarries stream/topic/partition + first/last offset + message_count - Stream
batch_length/poll_intervalcontrol volume;batch_length = 1covers low-rate command topics - If
dag_id_headeryields 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 | |
There was a problem hiding this comment.
My Airflow knowledge maybe outdated, but afaik dag_run_id would be manual__timestamp which wouldn't be idempotant.
There was a problem hiding this comment.
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)", |
There was a problem hiding this comment.
I am not sure about skipping messages here.
Classifying errors into 3 parts-
- Transient Airflow related - Might not apply to the remainder of messages in the batch.
- Malformed Iggy message - Same as above.
- 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.
There was a problem hiding this comment.
Agreed the old per-message skip path was too loose for permanent failures.
With batch triggering the consume path is simpler:
- Transient (429/5xx/network): middleware retries, then
Errso the runtime can redeliver the same batch (samedag_run_id→ 409 if already created). - Malformed single payload: skip that entry when building
conf.messages, count an error; if every entry fails conversion, return error for the batch. - Permanent HTTP (401/403/404/400/422, auth/missing DAG, etc.): return
PermanentHttpErrorfor 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.
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.
|
/ready |
Summary
iggy_connector_airflow_sinkthat triggers Airflow DAG runs (POST /api/v1/dags/{dag_id}/dagRuns) from Iggy poll batches (one run per batch, not per message)conf.messagescarries the batch; optionalconf.iggymetadata; deterministic batchdag_run_id; HTTP 409 treated as successnone/basic/bearer; optionaldag_id_headergroups a poll into one run per DAG idDesign 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 cratecargo test -p iggy_connector_airflow_sink(12 unit tests)cargo test -p integration -- connectors::airflow(WireMock: 3 messages → 1 DAG-run POST)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.