-
Notifications
You must be signed in to change notification settings - Fork 18
Multi-engine backends: pushdown DuckDB adapter, engine-neutral Arrow dataset, caching helpers #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Mmoncadaisla
wants to merge
99
commits into
xqlsystems:main
Choose a base branch
from
Mmoncadaisla:feat/multi-backend-duckdb
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+7,072
−316
Open
Changes from all commits
Commits
Show all changes
99 commits
Select commit
Hold shift + click to select a range
285ff96
feat: multi-engine backends prototype with a first DuckDB adapter
4f32b1e
perf: DuckDB pushdown via a pyarrow Dataset subclass (15-40x)
524abb3
feat: production-harden the DuckDB pushdown adapter
6ea9499
perf: whole-partition repeat/tile coordinate fast path in the pivot
469332b
docs: open rasters with lock=False for parallel tile reads
11a9ea8
feat: engine-neutral xql.arrow_dataset() — Polars works for free
8f59fb0
feat: fragments API — DataFusion register_dataset and Dask for free
eac735a
perf: affine scatter and grid reshape in the eager round-trip
acd4a7c
feat: engine-neutral materialize() and pyramid() caching helpers
abfaa95
docs: performance guide
f9919b6
fix: four adversarial-review findings
de5263a
fix: honor the batch_size scanner kwarg instead of silently ignoring it
a8b2ca0
feat: count_rows fast path via chunk arithmetic + strictness analysis
57df8f6
feat: opt-in coalescing of consecutive chunk reads (coalesce_rows)
8da7279
fix: share one pre-started prefetch pool across scans
18c40de
feat: lazy chunked round-trip beyond DataFusion (Polars; DuckDB eager)
a5bf765
docs: lazy round-trip engine matrix and the scan memory contract
610843f
bench: ARCO-ERA5 out-of-core benchmark with plan-shape assertions
bcba93a
feat: GeoArrow point-geometry columns at registration (geometry=)
3ef15f8
fix: exact float value-list windows on Polars (upstream is_in bug)
925a5f4
feat: max_result_bytes guard on the eager round-trip
85a8683
perf: hierarchical strictness — no more 4096-survivor cap, cross-dim …
a24d4e4
test: fix cross-dim refinement test (own 2D-chunked grid, numpy oracle)
55191e3
feat: prefetch_bytes — byte-budgeted admission for the prefetch pool
4f8582e
feat: xql.bbox_conjuncts — the pruning half of geometry predicates
e49b775
fix: bbox_conjuncts docstring terminated itself (triple quotes in exa…
728d424
feat: spill= — chunked reconstruction for DuckDB relations and one-sh…
64f8949
docs: spill= in the engine matrix; DuckDB/one-shot chunked now served
7312f09
docs: Behaviors & limitations page with decision diagrams
a233cbb
style: align with repo conventions (ruff-format, AGENTS.md, mypy)
30ab7b8
docs: engine behavior matrix; put sections where they belong
28e57cb
docs: separate how-it-works from known issues; Diataxis-style nav
c2fc218
docs: engine filter tabs on the Known issues page
f34081a
docs: give materialize and pyramid distinct explanations
3bc53f1
docs: say plainly that materialize is a thin CTAS wrapper
49f9775
refactor: remove materialize(); document the CTAS recipe instead
8ab2fda
docs: explain pyramid with the actual table it builds
488dcef
refactor: remove pyramid(); the recipe is plain SQL now
3274f08
refactor: remove the pyramid recipe entirely; make round-trip advice …
2aace35
docs: bring the DataFusion-era framing up to the multi-engine reality
821d80a
docs: per-engine notes section in the performance guide
8b1cac6
bench: per-engine matrix (DuckDB/Polars/DataFusion/xarray) on ARCO-ERA5
7547df5
docs: add engine-benchmark section to geospatial results
8c07db9
docs: machine results as tabs (n2-standard-8 / n2-standard-16)
c112448
docs: drop the interim engine-matrix section
85698c4
fix: handle ChunkedArray from pa.array in the pivot's batch builders
a40001e
bench+docs: run the geospatial suite across engines and machine sizes
64685b7
docs: single-machine engine table, restore native 07-09 numbers
4d68944
refactor: dead-code and comment sweep over the branch
426bd62
fix: gate range windows on whole-coordinate monotonicity
e1879c5
fix: force all prefetch pool threads to start at construction
b087a7f
fix: enforce max_result_bytes on LazyFrame and to_arrow_table results
7e5d7fc
fix: pin polars floor and guard collect(engine=) at runtime
c660401
fix: guard int32 offset overflow in the WKB point encoder
42814b1
fix: flatten float value-list windows with any_horizontal
f4a3814
fix: shut the shared prefetch pool down when the dataset dies
0f26502
fix: stop DuckDBHandle's runner thread when the handle dies
a2a3155
perf: vectorize the dense-size check's distinct counts
453b6ca
docs: fix the three broken links
26145b0
docs: fix stale alxmrs links and site-breaking relative links
9efe479
docs: put the DataFusion tab first in per-engine sections
0fda0d5
feat: add a polars extra to mirror the duckdb one
e3f95de
bench: make the DataFusion scan path an explicit engine choice
4b76f31
bench: provision the native module on benchmark VMs
8b61798
bench: dispatch EngineContext to per-engine strategy subclasses
42ca191
bench: harden the suite driver
c6a7827
bench: make engine_suite runnable as a uv script
f70ec23
bench: name the duckdb benchmark paths for what they are
b368022
tests: convert the ERA5 plan-shape benchmark into integration tests
9bbd664
bench: make duckdb_pushdown defend its own numbers
f78abde
docs: link the Lance precedent instead of assuming the reader knows it
e7c9438
docs: refresh the cross-engine tables with the four-engine run
02991e1
docs: add the zarr-datafusion counterpart to the duckdb-zarr note
c2af9ce
docs: show a complete example adapter in Adding an engine
a9e5379
docs: spell out RSS as resident set size
69f986c
build: test extra depends on the package's own engine extras
b012b57
Merge remote-tracking branch 'origin/main' into feat/multi-backend-du…
42556d1
chore: fix the mypy errors CI lint flags on the branch
ad71816
chore: strip trailing blank lines the end-of-file hook flags
d46eaf8
fix: reject non-positive batch_size at construction
9e87fa9
tests: organize the arrow-dataset suite around its contract
d76fe09
tests: rename the ERA5 suite after its subject and make it a matrix
891c38c
bench: harden the suite driver against dishonest greens
6ee508f
docs: define the seam concept where it is used
e5245b4
types: parameterize EngineAdapter over the connection type
5c7ec6a
types: thread the connection type through the public register
a536b1f
refactor: promote group_vars_by_dims to a shared df.py utility
5083f4a
docs: note the prefetch pool is not fork-safe, linking #145
50df567
refactor: rename _with_geometry to _batches_with_geometry
df39aca
refactor: group the batch-production pipeline methods together
5656231
docs: link the tracking issue for Dataset.filter (#239)
7262e8d
tests: drop the ERA5 plan-shape suite superseded by the engine matrix
bec3a38
tests: rescan across query shapes, restoring the multi-day pin
5998401
docs: link the tracking issue for sort_by/join delegation (#240)
71fa788
docs: document the strictness-analysis local functions
214df3f
docs: replace Sphinx markup with syntax the docs build renders
3f26136
types: DimSpec's first field is a two-value tag, type it as one
6729b6c
docs: define re-executable where DuckDBHandle claims it
d45dbbc
tests: pin multi-day pruning once at dataset level, keep rescans cheap
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| """Benchmark: DuckDB re-scannable stream vs pushdown dataset vs ceiling. | ||
|
|
||
| Times the three ways DuckDB can consume the same 10M-row synthetic | ||
| dataset — the re-scannable stream (no pushdown), the default | ||
| ``register()`` pushdown dataset, and an in-memory ``pyarrow.dataset`` | ||
| as the ceiling — and asserts at the end that all three returned the | ||
| same answers. Cross-engine comparisons live in | ||
| ``benchmarks/geospatial/``; this measures the adapter paths within one | ||
| engine. | ||
|
|
||
| Usage: python benchmarks/duckdb_pushdown.py (needs duckdb installed) | ||
| """ | ||
|
|
||
| import math | ||
| import statistics | ||
| import time | ||
|
|
||
| import duckdb | ||
| import numpy as np | ||
| import pandas as pd | ||
| import pyarrow.dataset as pads | ||
| import xarray as xr | ||
|
|
||
| import xarray_sql as xql | ||
| from xarray_sql.backends.duckdb import XarrayArrowStream | ||
|
|
||
| np.random.seed(0) | ||
| N_TIME, N_LAT, N_LON = 1000, 100, 100 # 10M rows | ||
| ds = xr.Dataset( | ||
| { | ||
| "temperature": ( | ||
| ["time", "lat", "lon"], | ||
| np.random.rand(N_TIME, N_LAT, N_LON), | ||
| ), | ||
| "humidity": ( | ||
| ["time", "lat", "lon"], | ||
| np.random.rand(N_TIME, N_LAT, N_LON), | ||
| ), | ||
| }, | ||
| coords={ | ||
| "time": pd.date_range("2020-01-01", periods=N_TIME, freq="h"), | ||
| "lat": np.linspace(-90, 90, N_LAT), | ||
| "lon": np.linspace(-180, 180, N_LON), | ||
| }, | ||
| ).chunk({"time": 50}) # 20 partitions | ||
|
|
||
| con = duckdb.connect() | ||
|
|
||
| QUERIES = { | ||
| "full AVG scan": "SELECT AVG(temperature) FROM {t}", | ||
| "1pct time filter": ( | ||
| "SELECT AVG(temperature) FROM {t} WHERE time < '2020-01-01 10:00:00'" | ||
| ), | ||
| "bbox filter": ( | ||
| "SELECT AVG(temperature) FROM {t} " | ||
| "WHERE lat BETWEEN 0 AND 10 AND lon BETWEEN 0 AND 20" | ||
| ), | ||
| "projection (1 of 2 vars)": "SELECT AVG(humidity) FROM {t}", | ||
| "count only": "SELECT COUNT(*) FROM {t}", | ||
| } | ||
|
|
||
|
|
||
| def bench(table, label, n=5): | ||
| """Times each query; returns {query: answer} for equivalence checks.""" | ||
| print(f"\n== {label} ==") | ||
| answers = {} | ||
| for qname, q in QUERIES.items(): | ||
| sql = q.format(t=table) | ||
| times = [] | ||
| for _ in range(n): | ||
| t0 = time.perf_counter() | ||
| r = con.sql(sql).fetchall() | ||
| times.append(time.perf_counter() - t0) | ||
| answers[qname] = r[0][0] | ||
| med = statistics.median(times) | ||
| print( | ||
| f" {qname:28s} {med:8.3f}s " | ||
| f"(min {min(times):.3f} / max {max(times):.3f}) -> {r[0][0]:.6g}" | ||
| ) | ||
| return answers | ||
|
|
||
|
|
||
| # re-scannable stream, registered via the stream wrapper explicitly: | ||
| # DuckDB scans every row, no filter/projection pushdown | ||
| con.register("t_stream", XarrayArrowStream(ds)) | ||
| stream = bench("t_stream", "stream (no pushdown)") | ||
|
|
||
| # default register(): the pushdown pyarrow-dataset path | ||
| xql.register(con, "t_pushdown", ds) | ||
| pushdown = bench("t_pushdown", "register() [pushdown]") | ||
|
|
||
| # ceiling: materialized pa.Table via pyarrow.dataset | ||
| table = xql.read_xarray(ds).read_all() | ||
| con.register("t_ceiling", pads.dataset(table)) | ||
| ceiling = bench("t_ceiling", "ceiling: in-memory pyarrow.dataset") | ||
|
|
||
| # The timings are only meaningful if every path computed the same thing. | ||
| for qname in QUERIES: | ||
| a, b, c = stream[qname], pushdown[qname], ceiling[qname] | ||
| assert math.isclose(a, b, rel_tol=1e-9) and math.isclose( | ||
| a, c, rel_tol=1e-9 | ||
| ), f"{qname}: paths disagree — stream={a} pushdown={b} ceiling={c}" | ||
| print("\nall paths agree") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.