Fix DuckDB scans over Vortex being merged or stalling - #9803
Fix DuckDB scans over Vortex being merged or stalling#9803joseph-isaacs wants to merge 2 commits into
Conversation
Two bugs surfaced by running the 99 TPC-DS queries against Vortex through DuckDB in one connection. DuckDB's common-subplan optimizer compares scans by their serialized form. The Vortex table function keeps pushed-down filters, projections and aggregates in its FFI bind data, which has no serializer, so two scans of the same file with different pushed-down filters serialized identically and were merged into one shared CTE. TPC-DS q9, q28 and similar queries then returned the same aggregate for several differently filtered subqueries. Mark the table function as not serializable, which is how DuckDB expects functions with opaque bind data to opt out, and add an SLT regression test. `ObjectStoreFileSystem::local` read files through `object_store`'s `LocalFileSystem`, which routes reads through the ambient tokio runtime's blocking pool whenever the calling thread has one. Awaiting those from a task driven by the DuckDB extension's non-tokio runtime eventually lost a wakeup and the scan stalled forever; the same query sequence completed in seconds when the calling thread had no tokio context. Read local files through `FileReadAt` on the runtime's own blocking pool instead. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L57FHoJ15heJVNaqgRq7xB
Merging this PR will improve performance by 10.39%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Simulation | allocate_drop_bytes[0] |
575.7 ns | 521.6 ns | +10.39% |
Tip
Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.
Comparing claude/duckdb-vortex-scan-fixes (385e8ec) with develop (85b70cf)2
Footnotes
-
206 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
-
No successful run was found on
develop(5c8c397) during the generation of this report, so 85b70cf was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩
With Vortex scans excluded from DuckDB's common-subplan optimizer, q11's scalar subquery over partsupp is no longer merged with the outer scan into a shared CTE, so its expected plan changes. Results are unchanged. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L57FHoJ15heJVNaqgRq7xB
Codecov Report✅ All modified and coverable lines are covered by tests. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Summary
Two bugs surfaced by running all 99 TPC-DS queries against Vortex through DuckDB in a single connection (the TPC-DS SLT suite is stacked on this PR). Both make DuckDB over Vortex either return wrong results or hang, so they are split out here for separate review.
Changes
Wrong results from DuckDB's common-subplan optimizer (
vortex-duckdb). DuckDB 1.5's common-subplan optimizer keys scans on their serialized form. The Vortex table function keeps pushed-down filters, projections and aggregates in its FFI bind data, which has no serializer, so two scans of the same file with different pushed-down filters serialized identically and were merged into one shared CTE. TPC-DS q9 returned the q41-60 bucket's average for four of its five buckets; q28 was affected the same way. Parquet was unaffected because its filters stay intable_filters, which are serialized. The fix setsverify_serialization = falseon the table function, which is how DuckDB expects functions with opaque bind data to opt out of plan serialization (and therefore of subplan merging).slt/duckdb/common_subplan_pushdown.sltis a regression test: scalar subqueries over one Vortex file with different pushed-down filters must return different results, and the plan must not contain a__common_subplanCTE.Stalled scans when the calling thread has a tokio context (
vortex-io).ObjectStoreFileSystem::localread files throughobject_store'sLocalFileSystem, whose local reads go throughtokio::task::spawn_blockingwhenever the calling thread has a tokio runtime context. When the awaiting task is driven by the DuckDB extension's non-tokioCurrentThreadRuntime(the SLT runner drives DuckDB from inside a tokioblock_on), a long sequence of scans eventually lost a wakeup: tracing showed the read task callingget_optsand never receiving a response, with every thread parked. The same 49-query sequence completed in about ten seconds with no tokio context on the thread. Local files are now opened withFileReadAt, which reads on the Vortex runtime's own blocking pool, so local reads no longer depend on the ambient tokio runtime. With this change the previously hanging sequence passes under the tokio-driven runner.Checks run:
cargo nextest run -p vortex-io(114 passed), fullcargo nextest run -p vortex-sqllogictest(144 passed, TPC-H skipped without data), clippy onvortex-ioandvortex-sqllogictest, nightly fmt, clang-format on the C++ change.🤖 Generated with Claude Code
https://claude.ai/code/session_01L57FHoJ15heJVNaqgRq7xB
Generated by Claude Code