GitHub user Leondon9 created a discussion: Coverage-based completeness check 
for partitioned assets (streaming producers)

> **TL;DR** Streaming producers publish many small, sometimes irregular, 
> partitions. Downstream consumers usually ask one question: *"Is `[start, 
> end)` fully covered, and if not, which gaps are missing?"* The current 
> partition model (AIP-76) answers *"have these exact keys arrived?"* This 
> proposes letting asset events carry an interval, so Airflow can answer the 
> coverage question. I'd like feedback before writing any code.

## The use case

| Producer | Emits | Consumer wants |
|---|---|---|
| Spark Structured Streaming, 5-minute micro-batches | one asset event per 
batch (`partition_key=…T10:05:00`) | run hourly / daily aggregation only when 
the period is complete |
| Iceberg commit job, every 5 minutes | one event per snapshot commit | "is 
June 2025 fully loaded?" before a backfill or a report |

Example question from a consumer task:

```text
asset = A, start = 2025-06-01, end = 2025-07-01
→ covered: False
→ missing: [2025-06-14T03:10, 2025-06-14T03:25)
```

## Why the current model does not express this

AIP-76 rollup (`RollupMapper` + `Window` + `WaitPolicy`, from #64571 and 
#66848) works on **discrete keys**:

1. `RollupMapper.to_upstream(downstream_key)` lists the exact keys one 
downstream period needs.
2. The scheduler compares that set with the received `source_partition_key` 
values via `WaitPolicy.is_satisfied_by_keys(matched, expected)`.

That fits producers with fixed, aligned partitions. It breaks down for 
streaming:

- **An event has no end.** `AssetEvent` stores `partition_key` (a string), 
`extra`, and `timestamp`. A key like `2025-06-01T10:05:00` tells us when the 
slice starts, not when it ends. The window fills that gap by assuming a fixed 
unit (one minute for `HourWindow`).
- **Batch boundaries drift.** Trigger delays, restarts, and reprocessing (one 
1-hour event replacing twelve 5-minute ones) produce keys that a fixed key list 
never matches.
- **"Missing" is a range, not a key.** Consumers want gaps as time ranges, 
which set difference over keys only approximates.

## Proposal: let events describe the interval they cover

Each event says which `[start, end)` it covers. Completeness becomes interval 
arithmetic: sort, merge overlaps, and take the complement inside the requested 
range. That runs in O(n log n) and returns the gaps directly.

How an event could carry its interval:

| Option | Example | Cost |
|---|---|---|
| a. Convention in `extra` | `{"interval_start": "…T10:05", "interval_end": 
"…T10:10"}` | No schema change; needs a documented convention |
| b. ISO 8601 interval as the key | 
`partition_key="2025-06-01T10:05/2025-06-01T10:10"` | No schema change; affects 
key parsing in mappers |
| c. New `AssetEvent` columns | `interval_start`, `interval_end` (indexed) | DB 
migration; efficient range queries |

## Where the check could live

### A. Task-side sensor / operator (small)

Use the existing Execution API asset-event lookup from a task:

```python
events = inlet_events[A].partition_key_regexp_pattern(r"^2025-06-")
intervals = sorted((e.extra["interval_start"], e.extra["interval_end"]) for e 
in events)
missing = complement(merge(intervals), within=(start, end))
```

- Works today with option (a), with no core change.
- Gaps: no key-range filter (`>=`, `<`) on the asset-event endpoints, so ranges 
are narrowed with a regex. Large ranges need pagination: a month of 5-minute 
events is about 8,640 rows. The asset must also be declared in the task's 
`inlets`.
- Natural home: a sensor in `providers/standard`.

### B. Scheduler-side coverage rollup (large)

Let the scheduler create the downstream Dag run when the period is covered, the 
way `RollupMapper` does today for key sets.

| Area | Today | Would need |
|---|---|---|
| Expected upstream | `to_upstream()` returns a key set | an interval |
| `WaitPolicy` | set comparison | interval union |
| `PartitionedAssetKeyLog` | `source_partition_key` string | interval columns, 
migration, index |
| UI progress | "12 / 24 received" | covered ratio and gap list |
| `MinimumCount(n)` | count of keys | needs new semantics |

B changes the AIP-76 model itself, so it needs community agreement before 
implementation.

## Relationship to the smaller `step` change

I have opened a separate, much smaller PR (#74594): an optional `step` on 
`HourWindow`, `DayWindow`, and `WeekWindow`, for example 
`HourWindow(step=timedelta(minutes=5))`. It keeps the key-set model and covers 
the common case of aligned, fixed-size producers.

This coverage idea is **complementary**. It is for irregular or reprocessed 
slices, and for consumers who need the gaps as time ranges.

## Questions for discussion

1. Is "coverage of `[start, end)`" a use case others see with partitioned 
assets, especially for streaming producers?
2. Which representation fits best: `extra` convention, interval keys, or 
columns?
3. Should we start with A (a sensor) and only consider B after the sensor sees 
real use?
4. For A, is a key-range filter on the Execution API asset-event endpoints 
acceptable?
5. How should overlapping or superseding events (a reprocessed hour replacing 
twelve 5-minute events) be treated: union, or latest wins?

Feedback is welcome, including "this is out of scope for Airflow".

---
Drafted-by: Claude Code (Opus 5.5); reviewed by @Leondon9 before posting


GitHub link: https://github.com/apache/airflow/discussions/74595

----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: [email protected]

Reply via email to