T17 typed ordering and compact top-k
Implemented on 8ee2a074 (T16), on PR #1412. T17 adds multi-key measure ORDER BY,
budgeted full sorting and a finite-LIMIT heap using the same typed comparator.
Committed as b13cdc6d. The measurements below describe the T17 verification
revision. The subsequent legacy integration repair
resolves its nine legacy-suite failures and passes 934/934 measure cases in each
topology. The revised concurrent admission contract
separates expected overload rejection from timed-query acceptance. Successful 100k/1m throughput is now explicitly deferred by the
2026-10-19 acceptance revision;
the revised harness verification
records the passing overload/recovery and timing-protocol checks;
the historical measurements below are not rerun or reclassified by that repair.
Execution contract
The planner resolves source columns, visible aliases, ordinal references and
completed aggregate/group outputs once. Each execution binds its own parameters,
output types and mutable operator state. Computed timestamp aliases resolve
::TAG against the bound output container, including a timestamp and field
sharing an alias. Alias names never enter a per-row lookup. Simple native
timestamp and explicit index-rule ordering retain their existing paths.
native column batches
-> WHERE
-> grouping / aggregation
-> HAVING and completed projections
-> visible-tuple DISTINCT
-> full sort or compact top-k
-> OFFSET / LIMIT and visible output
-> existing measure response
Optional stages are omitted when absent. Sorting sees every completed row after the preceding stages. Hidden WHERE inputs remain in the native scan projection; hidden ordering columns and aggregate calls survive until sorting and are then excluded from the response. DISTINCT permits only keys derivable from its visible tuple. LIMIT 0 performs no native scan.
Each key has independent ASC/DESC and NULLS FIRST/LAST. Defaults are ASC NULLS LAST and DESC NULLS FIRST. Integers compare exactly, strings and bytes compare byte by byte, and floating NaNs follow other non-null numerics in ascending order. Descending reverses non-null ordering without reversing NULL placement. Complete ties have unspecified relative order; test goldens include a unique tie key.
Unlimited results use cancellable heapsort with bounded comparator work. Finite
LIMIT uses a max-heap of the best k = uint64(OFFSET) + uint64(LIMIT) rows, then
sorts those candidates before slicing. The maximum supported sum is 8,589,934,590;
it does not wrap uint32. Large offsets can approach full-sort retention.
Accepted candidates copy all reachable variable values, including strings, binary data, arrays and boxed protobuf values. Eviction reuses candidate cell slices. No candidate retains a source-batch owner. The shared tracker reserves key buffers, candidate descriptors/payload and output before allocation; candidate payload capacity is bounded by twice the live payload plus declared descriptor capacity. Boxed integer-array accounting includes native slice storage, because packed wire size can be much smaller than the cloned Go allocation. Full sort and top-k fail on budget exhaustion, with no spill or unbounded fallback. Close, cancellation and source errors release reservations and owners.
Generated endpoint verification
The independent finite model generates 32 sorting families with three bindings, 12 no-scan rejections and four resource specs: 48 T17 specs per topology. Cold/warm and bypass calls, forced full-sort parity, compact-budget controls and recovery queries produce 259 verification RPCs per topology. Readiness uses 12 QL-only pages; actual retry-inclusive counts are logged separately. No new case uses a native query for readiness or expected output.
Coverage includes F ordering and pagination, defaults and explicit NULL placement, hidden keys, alias precedence, exact int64s above 2^53, strings, NaNs, grouped and hidden aggregate keys, HAVING, bucket outputs, computed/ordinal grouping with AVG, visible DISTINCT, changed expression/time/page bindings, LIMIT 0, wide offsets, multi-batch input, cancellation, real candidate-budget errors and cleanup. Four cross-stage families use a hidden WHERE-only field in raw, computed, grouped and DISTINCT queries. The computed family also orders a qualified timestamp alias.
All 1,569 earlier generated cases and their expected responses are retained.
Only the two obsolete T03 sorting-rejection queries change to genuinely absent,
qualified keys; their original supported forms now have positive T17 witnesses.
The complete artifact contains 1,695 cases. Its SHA256 is
3c2de0c2474d059b86402101535ebce27af5ed5418a6a9500aec443e2ddb84fe.
The audited grammar fingerprint includes the new ORDER BY parser.
Focused four-CPU, 8-GiB Docker runs with no additional swap pass all 48 specs in
both topologies. Each reports exactly 259 verification RPCs. Standalone readiness
uses 12 calls; distributed uses 22 calls for the same 12 pages.
Evidence is .omx/t17-generated-focused-{standalone,distributed}-v4.{log,json}
and .omx/t17-generated-focused-v4-summary.json.
Public tests cover Prepare/Compile/Bind, WrapRaw/WrapSort and final response
egress, with independent expected values. RED/GREEN regressions retain evidence
for hidden WHERE projection, qualified timestamp aliases, invalid ordinals and
under-accounted boxed arrays. Normal/tagged runtime and planner suites, focused
race/ownership checks, liaison tests, the focused benchmark harness and
make lint pass. Logs are .omx/t17-*-final.log,
.omx/t17-planner-reviewed-*.log and .omx/t17-runtime-*.log.
The complete measure selection includes every measure-ql case, without a
ticket filter, plus existing native measure and Top-N compatibility cases:
| Topology | Ticketed QL | Legacy QL | Native measure | Top-N | Total passing |
|---|---|---|---|---|---|
| Standalone | 637/637 | 142/151 | 120/120 | 20/20 | 919/928 |
| Distributed | 637/637 | 142/151 | 120/120 | 20/20 | 919/928 |
At the T17 verification revision, both full suites were red on the same nine
documented legacy regressions (resolved by the follow-up linked above).
Eight indexed-array cases fail in the native variable-array decoder:
generated_projection_0, generated_having_labels,
generated_not_having_labels and generated_pair_002/004/008/013/017.
generated_in_int_list_0 reports that a series is required for empty entity IN.
Their expectations and active status were unchanged; that run did not count them
as passed tests or sorting regressions. Historical full reports are
.omx/t17-generated-full-{standalone,distributed}-v2.json, with category analyses
and .omx/t17-generated-full-v2-summary.json.
Other existing compatibility cases also pass:
| Topology | Stream | Trace | Property |
|---|---|---|---|
| Standalone | 97/97 | 68/68 | 24/24 |
| Distributed | 97/97 | 68/68 | 12/12 |
The standalone property selection includes 12 default and 12 on-disk cases.
Reports are .omx/t17-other-compat-{standalone,distributed}.json and
.omx/t17-generated-property-{standalone,distributed}.json. The complete
functional ledger is .omx/t17-generated-verification-summary.json.
Focused benchmark protocol
The kernel matrix has nine orthogonal shapes, avoiding a Cartesian expansion: 1/3/8 keys, deterministic random/ordered/reverse/tied input, narrow/wide payloads, k=10/100/1,000 and a large offset. Random source order uses an independent seeded permutation, including the one-key control. Standard-library tuple sorting is an independent oracle; full sort and top-k copy and return the same page. Actual key roots, row applications, kernel work, comparisons, retained candidates, descriptor/payload bounds and cleanup are checked before timing. A separate 32-batch probe poisons 1-MiB source buffers after copying one narrow candidate from each batch and checks owner and pool references.
The RPC comparison uses the same three-key SQL and ten-row page at 1,024 and 32,768 inputs. A querymetrics-only context forces reference full sorting through the existing liaison test hook; the default path uses top-k. The only additional workload is the planned composed filter/group/HAVING/order/page query with three changing bindings and 1/8/32 callers. No unrelated cache or older-ticket matrix is added. Warm-ups and retained timed samples check exact results; every timed call checks row count and contributes actual work and cleanup counters. Cache counters report observed reuse; they do not claim measured uncached planning savings.
These successful RPC sizes were fixed after admission, before final timing. The kernel supplies the 100,000-row algorithm comparison. Successful 100,000- and 1,000,000-row RPC throughput is not established: earlier tickets already record the unchanged native input-materialization ceiling, and T17 does not repeat that failing large-fixture approach or change the ceiling. See T16’s resource evidence. The PR #1412 legacy performance matrix is also not rerun; existing native golden tests establish correctness, not a new G1 latency verdict. These limits prevent closing the full planned performance acceptance from the focused sort measurements alone.
Final timing uses five fresh resource-limited Docker repetitions and prebuilt
binaries. The kernel uses a normal build; RPC work counters require querymetrics.
Source, binary, generated-artifact and retained-failure digests guard the run.
The repeatable driver is
test/integration/distributed/querybench/run-t17-frozen.sh; raw samples and
CPU/heap profiles remain under .omx/bench-reports/qlb-t17-*-final.
RPC measurements
All five repetitions validate the four admitted variants at both sizes: 3,120 successful timed RPCs, including 240 retained samples checked against the independent exact oracle. Every sort pair returns the same ten-row page. The following are medians of the five per-run measurements. CPU and allocations include the client, liaison and two data nodes; they do not isolate sort CPU.
| Input rows | Mode | p50 ms | p95 ms | p99 ms | CPU ms/RPC | Allocated KiB/RPC |
|---|---|---|---|---|---|---|
| 1,024 | Full sort + slice | 5.602 | 8.544 | 9.659 | 12.7 | 2,505.17 |
| 1,024 | Top-k | 4.037 | 6.268 | 7.908 | 10.6 | 1,645.05 |
| 32,768 | Full sort + slice | 143.733 | 177.362 | 185.521 | 239.5 | 71,082.98 |
| 32,768 | Top-k | 45.120 | 55.160 | 58.330 | 102.0 | 38,598.39 |
The ratios of median top-k/full-sort p50 are 0.721 and 0.314; median paired ratios are 0.753 and 0.310. These are observations, separate from the kernel’s 0.80 algorithm gate. Comparator calls are exactly 17,303 versus 1,403 at 1,024 rows and 881,572 versus 33,376 at 32,768 rows across the five repetitions. Keys evaluate once per active row, with two native leaves per RPC, no intermediate DataPoints and identical completed output. Source/frame counters include both replicas and report twice the logical sort input.
Tracked per-query sort peak reservations fall from 1,361,088 to 61,496 bytes at 1,024 rows and from 42,247,360 to 61,496 bytes at 32,768 rows. Candidate counts fall from N to 15. These peaks include conservative descriptor/payload bounds and key/output buffers; they are not measured RSS. The process-wide concurrent tracker high-water marks are about 58.47 and 260.9 MiB for these comparisons, and cannot independently identify native frame residency. Cumulative tracker lease charges per RPC fall from 53.15 to 51.90 MiB and 167.97 to 127.48 MiB; spent charges are not peak live memory.
The composed workload validates three different result hashes and, in every variant/repetition, 96 measured cache hits with zero measured compiles/bypasses. At 1,024 inputs, 96 calls examine 81,920 rows, pass 27,968 and complete/sort 1,536 groups; at 32,768 they examine 2,621,440 rows, pass 937,664 and complete/sort the same 1,536 groups. Each group has the expected three SUM/MAX state lanes; the repeated SUM in HAVING adds no state. The residual field predicate requires local grouping, so this workload does not claim new native aggregate pushdown. Existing T08 endpoint cases remain compatibility coverage.
| Input rows | Callers | p50 ms | QPS | Process tracker peak MiB |
|---|---|---|---|---|
| 1,024 | 1 | 3.156 | 307.34 | 57.09 |
| 1,024 | 8 | 8.609 | 811.45 | 422.03 |
| 32,768 | 1 | 40.735 | 27.01 | 228.00 |
| 32,768 | 8 | 106.529 | 69.91 | 1,298.82 |
The composed operator retains two sort candidates and about 17.69 KiB of sort peak reservations per query. Higher client concurrency increases both throughput and latency/residency; it does not reduce per-query work. All measured variants restore live reservations, native results, owner pins and flatten-pool references to baseline.
Native retained-frame bytes, per-tracker peaks, aggregate peaks, total parent
lease charges, physical storage rows and uncached planning-time savings remain
unavailable; the report names them explicitly. Fresh Docker invocations are
recorded by the frozen driver; absent cgroup-v2 container-ID metadata is not
invented. Exact samples, counters and profiles are retained in
.omx/bench-reports/qlb-t17-rpc-final/r{1,2,3,4,5} and
rpc-analysis.{json,md}.
Kernel measurements
Five fresh Docker repetitions produce all 135 expected records: nine shapes, three modes and five samples. Both 100,000-row small-k gates pass. Median top-k/full-sort time ratios are 0.0251 for k=10 and 0.0265 for k=100, below the specified 0.80 bound. The following are median milliseconds per complete operator execution, or per independent tuple sort.
| Rows | Keys / input / payload | k / offset | Tuple | Full sort + slice | Top-k | Top-k/full |
|---|---|---|---|---|---|---|
| 100,000 | 3 / random / 16 B | 10 / 0 | 44.403 | 378.005 | 9.488 | 0.0251 |
| 100,000 | 3 / random / 16 B | 100 / 0 | 45.518 | 382.801 | 10.155 | 0.0265 |
| 100,000 | 3 / random / 16 B | 90,100 / 90,000 | 45.995 | 391.749 | 361.375 | 0.9225 |
| 1,024 | 1 / ordered / 16 B | 10 / 0 | 0.073 | 1.018 | 0.057 | 0.0561 |
| 32,768 | 1 / random / 1 KiB | 100 / 0 | 13.080 | 69.978 | 2.196 | 0.0314 |
| 32,768 | 3 / reverse / 1 KiB | 1,000 / 0 | 2.259 | 81.301 | 45.872 | 0.5642 |
| 32,768 | 3 / reverse / 16 B | 100 / 0 | 2.978 | 60.828 | 26.427 | 0.4345 |
| 32,768 | 8 / random / 16 B | 100 / 0 | 14.327 | 123.288 | 7.570 | 0.0614 |
| 32,768 | 8 / tied / 16 B | 1,000 / 0 | 15.750 | 176.934 | 17.190 | 0.0972 |
The tuple baseline shallow-copies tuple descriptors and borrows fixture payloads. The public operator includes typed evaluation, owned candidate copies, tracked allocation, output and cleanup. Full sorting costs substantially more than that plain baseline, especially when the standard-library sorter recognizes ordered input. The comparison makes that cost visible; the full-sort path has no speed gate against the tuple baseline.
For both 100,000-row small-k controls, full sort performs 3,019,492 comparisons, below the 6,800,000 bound. Top-k performs 100,440 and 108,507. All three keys have 300,000 row applications, 294 actual root entries across 98 batches and 100,000 arithmetic-kernel rows. Candidate peaks are exactly 10/100; tracked sort peaks are 44,560/124,480 bytes, versus about 81.1 MB for full sort.
Large offsets retain much of the input: the 90,000-offset page holds 90,100
candidates, about 72.8 MB of tracked peak reservations, and approaches full-sort
time. Reverse input causes frequent candidate replacements; the 1-KiB, k=1,000
case allocates about 34.1 MB cumulatively while retaining about 2.94 MB at peak.
Bounded live state does not imply bounded total allocation over the entire scan.
Every shape passes key-work, comparator, ownership, capacity and cleanup checks.
The 1-MiB poisoned-source probe is correctness/resource evidence outside timing.
Raw records, profiles and the passed numerical gate are retained in
.omx/bench-reports/qlb-t17-kernel-final and kernel-summary.json.
Frozen provenance
Both timing phases use Go 1.26.9, four CPUs, 8 GiB and no additional swap, with
immutable image
sha256:4cec35c89954c6a562dcc10d347b67e18a46d32347dbd02d47405191935771ac.
The source fingerprint, including generated QL cases, is
53f8d0b6cfd41f22e2ea8bdab7635c44c014ebdb4a1c683113a16d126ee80e1a.
The querymetrics RPC binary SHA256 is
2af184567918fc6b75b72a00ecdf79176001540741284be45e4c1bb1cc19c65f;
the normal kernel binary SHA256 is
dfe7d2fd45bfa5298c3f1d46e3ad442e00715aa652085fe6f36d2c34985188f7.
The driver checks these digests and the retained 32-caller failure evidence around
each repetition. Builds, tests and functional Docker runs finish before timing.
The ledger is .omx/t17-final-provenance.json; the complete driver log is
.omx/t17-final-timing-driver.log.
Native concurrency limitation
The subsequent admission diagnosis reproduces this
blocker at b4be414f and proves that 32 held fanout reservations exceed the
configured shared pool. It preserves the original blocked performance verdict.
Untimed admission succeeds for the raw full-sort/top-k pair and composed
1/8-caller variants at both sizes, without changing the 256-MiB tracker setting.
The 1,024-row 32-caller composed probe fails at native broadcast admission with
ResourceExhausted: query memory pool exhausted. A separate forced-full-sort
32-caller control reproduces the same failure: 95 of 96 calls complete with exact
results, and the rejected call never reaches WHERE, aggregation or sorting.
Both controls restore all tracked resources to zero.
Native fanout reserves receive/parser buffers and part of the remaining allowance before opening streams. Concurrent reservations can exhaust the service-wide pool even though local sort candidates remain small. This is existing native input residency, not top-k retention. Protector policy, receive buffering and memory limits are unchanged. The same 32-caller failure is not repeated at the larger size.
The historical benchmark protocol required all five variants. The revised
harness separates overload and recovery evidence
from its four timed variants.
The later criteria revision separates the 32-caller overload check; the following
status describes the original protocol, not a new resource-gate verdict. An explicit
retained failure report enables partial measurements of the four admitted variants while
keeping the 32-caller variant marked blocked, with zero latency/allocation
samples and the evidence digest. Overall status remains
partial_known_32_blocked; it cannot be reported as a passed acceptance gate.
Artifacts are .omx/t17-admission-1024-r3/t17-admission-1024.json,
.omx/t17-admission-32768/t17-admission-32768.json and
.omx/t17-concurrency-control/fullsort32-control/t17-admission-1024.json.
Compatibility boundary
T17 changes QL parsing/planning/execution and test infrastructure. Its diff from
8ee2a074 leaves protobuf definitions/generated APIs, native execution operators,
transport/frame formats, persisted data and protector policy unchanged. The
earlier approved T15 shared-buffer changes remain part of the parent history;
they are not new T17 changes. Temporary-file spilling remains deferred.