Skip to content

Deduplication

Status: Native — all four variants (rowtime/proctime × keep-first/keep-last), mini-batch included, idle-state TTL included.

Flink recognizes ROW_NUMBER() OVER (PARTITION BY key ORDER BY time_col ASC|DESC) = 1 as its Deduplicate operator rather than a general rank filter whenever the order key is exactly one time column (rowtime or PROCTIME()) and the rank is 1. A rank-1 filter ordered by anything else is a value-ordered dedup, which Flink plans — and StreamFusion runs — as Top-N instead.

The four variants

  • Rowtime keep-first (ORDER BY rowtime ASC) — insert-only, watermark-released: a key's first row is held until the watermark passes it, then emitted once.
  • Rowtime keep-last (ORDER BY rowtime DESC) — retracting: each later row for a key replaces the previous emission.
  • Proctime keep-first / keep-last — arrival order, no watermark involved. The order key itself is materialized by the native PROCTIME() expression, so both proctime shapes run as ordinary time-ordered dedup over that generated column.

All four emit eagerly in arrival order — proctime dedup needs no wall-clock timer, unlike the windowed operators that fire on a processing-time clock.

Mini-batch

Every mini-batch shape replicates Flink's per-mode emission exactly:

  • Under mini-batch, a rowtime dedup — keep-first included — becomes Flink's bundled retracting function. Its flush emits every kept row's transition by default, or one net transition per key per bundle under table.exec.deduplicate.mini-batch.compact-changes-enabled.
  • A proctime flush emits one net transition per key.

Insert-sensitivity

table.exec.deduplicate.insert-update-after-sensitive-enabled (default true) is replicated too. With the option off, under a consumer that requests only UPDATE_AFTER (an upsert sink), every emission becomes a bare +U — a fresh key's first row included — and the proctime identical-row suppression is disabled, exactly as Flink's own helpers behave.

Idle-state TTL

Idle-state TTL runs natively here — see TTL semantics and Configuration for the flag surface. One shape needs a wrinkle beyond the standard last-write-timestamp rule: the watermark-buffered rowtime keep-first dedup TTLs only its emitted markers. The buffered candidate row itself mirrors Flink's deliberately un-TTL'd timer state — the watermark is what cleans it up, and expiring it early would lose data. The marker, written once when a key fires and never refreshed afterward, expires a fixed retention after that firing — which is what lets the key emit a second "first" row once its earlier marker has aged out.

Gap

A rank-1 filter that is not time-ordered — i.e. ordered by a value column rather than rowtime or PROCTIME() — is not a fallback for Deduplication; it is a different query shape, handled by Top-N.