Top-N emit decodes distinct rows, not emitted rows¶
Applies to: the append-only and retracting streaming Top-N rankers. With mini-batch off, each keeps the byte-identical per-input cascade.
The problem: decoding the same row over and over¶
The with-rank cascade emits the same Arc-shared buffered row at many rank positions — in a hot
partition, the same top-N rows appear over and over across a batch's cascades — but emit decoded
arrow-row state bytes per emitted row, not per distinct row. In the q19 profile that decode was
72% of the operator's CPU.
Emit now decodes each distinct row exactly once and rebuilds the emitted positions with a
vectorized take: output stays byte-identical, but decode cost drops from O(emitted) to
O(distinct). q19 gained +13% end to end on the generator profile loop, and the decode's CPU
share fell from 72% to 6%. The operator is then bound by materializing the cascade's output
volume itself — Flink's own changelog contract, not native overhead.
Under mini-batch: cut the volume itself¶
Decoding once per distinct row removes the decode cost, but the with-rank cascade still emits every rank transition on every input row. Mini-batching attacks that volume directly: the ranker carries each touched partition's preimage across physical Arrow batches and emits the net logical-bundle rank diff only at count, watermark, checkpoint, or end-of-input boundaries. This preserves the collapsed changelog exactly — the same first-preimage/final-postimage contract described on Logical mini-batches, decoupled from physical Arrow batches — while mini-batch off keeps the byte-identical per-input cascade.
Criterion on 4,096 rows, 64 partitions, ascending Top-10 with sustained boundary churn finds the logical diff 2.70× faster than a diff after every 256-row physical batch for membership output (6.34 vs. 2.35 M rows/s), and 2.28× faster with projected rank (7.25 vs. 3.18 M rows/s). It also beats the immediate per-input cascade by 1.40× and 3.41× respectively.
A five-second release profile puts most samples in Top-N mutation and arrow_row::Row::owned
allocation after coalescing; eliminating that ownership traffic is the next optimization frontier,
not further changelog materialization. The first-touch key/preimage staging is charged to the
operator's managed-memory reservation and released at flush; the shared metrics report its peak
bytes and the actual touched-partition count, not emitted rows as a proxy — see
Memory accounting designed off the hot path.
Retracting Top-N: only the first and final visible window¶
The same logical-window diff covers retracting Top-N. Its full per-partition buffer still applies
every input insert/retract, so rank N+1 promotion remains correct — nothing is skipped on the way
in — but only the first visible window and the final visible window are materialized as changelog.
Criterion over 4,096 rows, 64 partitions, and 256-row physical batches measures 3.16 M input
rows/s for one logical flush versus 1.24 M for immediate per-row diffs and 2.13 M for
per-physical-batch diffs — 2.54x and 1.48x faster. Rank-projected output uses the same
position-aware final diff. A deterministic SQL parity test covers the real retracting plan produced
by GROUP BY into Top-N; mini-batch-off keeps the original per-input behavior.