Choose a merge rule

Splitting rows means each engine computes a partial answer, and not every result can be reassembled from partials. A MergeRule says what a query’s partials are and how they combine. This guide picks the rule for each result shape.

Totals

Sums and counts add. Name the outputs and the rule is merge_totals:

from dau_scheduler import ADDITIVE, MergeRule, merge_totals

rule = MergeRule(
    terminal="reductions",
    combine=lambda partials: merge_totals(partials, ("revenue", "len")),
    mergeable=ADDITIVE,
)

merge_totals takes an optional finalize callable for the step after the addition, which is where a deferred mean is divided.

A mean

A mean is not row-partitionable. Carry the sum and the count as separate outputs, add them, and divide in finalize. Declare DEFERRED_MEAN as mergeable only when your partials really do carry both; that declaration is how you tell the checker the shape supports it.

Grouped results

Two helpers, for two kinds of engine:

  • merge_ordered_groups(partials, key=..., fold=...) is for rows sorted on the group key. Only the group straddling the boundary can appear in both arms, so it is folded and the rest concatenate. The precondition is checked: unsorted rows are refused rather than merged into an answer no run produced.

  • merge_keyed_groups(partials, key=..., fold=...) is for an engine that keeps one accumulator per distinct key. A key can appear in both arms, so every key is looked up, and the result comes back key-ascending.

fold combines two partial records of the same group; key reads a record’s group key.

Top-k and sorted output

  • merge_top_k(partials, limit=k, key=..., descending=True) re-ranks the arms’ k-lists. A row in the whole query’s top k ranks at least as high within its own range, so the arms’ lists are the complete candidate set. The re-rank is stable and the arms are given in row order, which keeps an earliest-arrival tie-break true of the whole table.

  • merge_sorted_runs(partials, key=..., descending=False) merges two sorted runs, taking the earlier range first on equal keys for the same reason.

Boundary statistics

min, max, first and last are decided at the edges of a range rather than accumulated across it. They need the arms’ order to be defined, which it is: the device arm takes the leading rows. Declare BOUNDARY_STATS when your combine step handles them.

What is refused

check_split(rule, aggregates) runs before anything executes. An aggregate kind outside the rule’s mergeable set is refused by output name, with a reason from AGGREGATE_REFUSALS where one exists: a mean, an exact distinct count, a median, a standard deviation or a variance each say what to carry instead. A rule with refusal set cannot be split at all.

An arm that failed

An engine that invalidated its own work returns a partial that says so. Give the rule a failed predicate and a merge that sees such a partial returns it, failure and all, rather than a plausible short total nobody can tell is missing rows.