Use native Icebird batches for faster queries - #982
Conversation
The prepared-batch refactor replaced every `AsyncDataSource` annotation in `visibility.js`, `parquet-source.test.js`, and `star-expansion-drifted-union.test.js` with `ScannableDataSource`, but left the now-unreferenced `AsyncDataSource` specifier in each file's `@import` list. Remove it, and fold the star-expansion test's two kernel-types imports back into one line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review record: PR #982, head
|
…sync Review round on 4c8df94. - llp/0294 named Icebird 0.8.23 and asserted that icebird#41 (position deletes applied against filtered ordinals) was still live, so the row fallbacks it keeps "remain exposed until Icebird fixes its row path". The PR pins 0.8.25, where the row path recovers each row's physical position before applying deletes and the prepared path passes a filter only as a non-strict range pruner. icebird#41 is closed. Restate the paragraph as the fixed state so the next reader does not believe `hyp purge` plus a filtered query can resurrect a row. - withHeapBudget's budgetedBatch wrapped every deferred column in an `async read()`, so a ColumnResult that squirreling would have resolved inline (resolveColumnResults / expression batch fast path) became a thenable. On the PR whose point is removing per-cell promises, sample the guard without changing the read's sync/async shape. - The benchmark's defaultTablePath called directoryStats() from inside a sort comparator, re-walking each candidate table's data tree O(n log n) times. Size each candidate once. - Add the missing regression test: nothing proved that a position-deleted row stays deleted on the new native batch route. purge-command.test.js only reads through readRowsFromTable/scanRowsFromTable, and a wrong answer here is a silently resurrected row, not an error. Cover deletes, residual filter, COUNT(*), and the union-global LIMIT/OFFSET over two real purged Iceberg partitions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…olumns helper, folded type imports Review round on 20cdc5e. - benchmarks/icebird-real-data.mjs hard-coded `maxHeapBytes: 0`, and executeQuerySql only installs withHeapBudget when the budget is positive. Every recorded number therefore measured a source stack that no real query uses: production wraps each source in budgetedPrepareScan/budgetedBatch, which sample process.memoryUsage() once per native batch and once per deferred column read. Add `--max-heap-mb` (default 0, so the published numbers still reproduce) and record the setting in the JSON, so the wrapper's cost on the very path this PR speeds up is measurable rather than invisible. - The same benchmark validated `--scenario` only after walking and sizing every candidate table, so a typo paid a full stat of the cache before failing. Validate first. - sql.js open-coded "the source's logical column names" (`columns ?? schema.fields.map(...)`) at three sites. Squirreling has the rule as dataSourceColumns but does not export it, so give the kernel one sourceColumnNames helper instead of three copies. - The ScannableDataSource refactor added a second @import line from hypaware-plugin-kernel-types.js in three more files rather than extending the existing one. That is the same nit round 1 raised and fixed in star-expansion-drifted-union.test.js; fold the other three. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ine plans from sourceColumnNames() documented itself as deriving names "the way the engine's own dataSourceColumns does", but inverted that helper's precedence: squirreling returns schema field names whenever a source offers both prepareScan and schema, and only falls back to columns otherwise. The divergence only shows on a source whose two lists disagree, which no in-repo source does today, but one of the helper's three call sites is the LLP 0105 local-only visibility gate. A third-party prepared source advertising a narrow columns list alongside a wider schema would have had the gate decided against a list the engine never reads, so a declared content column present only in the schema would have skipped the visibility wrapper. Reading the authoritative list can only widen the set of sources that get wrapped, which is the fail-closed direction LLP 0294 already chose for the prepared-only refusal next to it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review record: PR #982, head
|
|
Triage of residual review findings at head Four residual observations remain, all classified non-blocking (no wrong behavior, data loss, security exposure, crash, or perf regression past a stated budget). They are deferred to #983:
No blocker found; the PR can merge safely with #983 tracking the deferrals. |
Summary
Real-data benchmark
Measured on the same retired ai_gateway_messages snapshot for both revisions: 264,048 rows, 1,088 Parquet files, and 473.7 MiB compressed. Values are medians from five measured iterations after one warmup.
Every result checksum matched the baseline. Peak RSS includes the complete Node process lifetime and V8 retained memory, so it is noisier than elapsed time and is not a per-query allocation measurement. Post-GC retained heap deltas were near zero for both versions.
Run the benchmark with:
Validation
Full-suite note: npm test reported 5,037 passing, 2 skipped, and 2 unrelated failures. The unchanged CLI help assertion omits the already-rendered graph command. The Claude overlapping-body case was timing-sensitive in the concurrent run; its focused file passed 3/3 when rerun with loopback-listener permission.