|
17 | 17 |
|
18 | 18 | """Run TPC-H Q1 across worker processes, and compare against one process. |
19 | 19 |
|
| 20 | + uv pip install tpchgen-cli |
20 | 21 | python examples/distributed/run_tpch.py --partitions 4 |
21 | 22 |
|
22 | | -Needs the TPC-H data the repository's other examples use:: |
23 | | -
|
24 | | - mkdir -p examples/tpch/data && cd examples/tpch/data |
25 | | - uv pip install tpchgen-cli && uv run --no-project tpchgen-cli -s 1 --format=parquet |
26 | | -
|
27 | | -`tpchgen-cli` writes one file per table, so `lineitem.parquet` is a single |
28 | | -220 MB file -- one partition, and nothing to fan out. This script re-shards |
29 | | -the columns Q1 needs into `--partitions` files first, which is also a fair |
30 | | -illustration of the real constraint: a distributed engine can only spread work |
31 | | -as widely as the data is split. |
| 23 | +Generates its own `lineitem` with `tpchgen-cli`, which shards natively: one |
| 24 | +`tpchgen-cli parquet --parts N` call writes N Parquet files. That is also a |
| 25 | +fair illustration of the real constraint -- a distributed engine can only |
| 26 | +spread work as widely as the data is split -- so the number of files is the |
| 27 | +same `--partitions` the engine is told to use. |
32 | 28 | """ |
33 | 29 |
|
34 | 30 | from __future__ import annotations |
35 | 31 |
|
36 | 32 | import argparse |
37 | 33 | import pathlib |
38 | 34 | import shutil |
39 | | -import sys |
| 35 | +import subprocess |
40 | 36 | import tempfile |
41 | 37 | import time |
42 | 38 |
|
43 | 39 | import pyarrow as pa |
44 | | -import pyarrow.parquet as pq |
45 | 40 | from dfx_engine.driver import run_distributed, run_local |
46 | 41 | from dfx_engine.session import SessionSpec |
47 | 42 |
|
48 | | -# Q1 without the `l_shipdate` filter and the `avg` columns, so the shard below |
49 | | -# stays small. The shape that matters is unchanged: group by two low-cardinality |
50 | | -# columns, aggregate, order. |
| 43 | +# Q1 without the `l_shipdate` filter and the `avg` columns. The shape that |
| 44 | +# matters is unchanged: group by two low-cardinality columns, aggregate, order. |
51 | 45 | Q1 = """ |
52 | 46 | select l_returnflag, |
53 | 47 | l_linestatus, |
|
61 | 55 | order by l_returnflag, l_linestatus |
62 | 56 | """ |
63 | 57 |
|
64 | | -COLUMNS = [ |
65 | | - "l_returnflag", |
66 | | - "l_linestatus", |
67 | | - "l_quantity", |
68 | | - "l_extendedprice", |
69 | | - "l_discount", |
70 | | - "l_tax", |
71 | | -] |
72 | | - |
73 | | - |
74 | | -def reshard( |
75 | | - source: pathlib.Path, into: pathlib.Path, partitions: int, rows: int |
76 | | -) -> int: |
77 | | - """Write the first `rows` rows of `source` as `partitions` Parquet files.""" |
78 | | - into.mkdir(parents=True, exist_ok=True) |
79 | | - table = pq.read_table(source, columns=COLUMNS) |
80 | | - if rows: |
81 | | - table = table.slice(0, rows) |
82 | | - |
83 | | - per_file = max(1, table.num_rows // partitions) |
84 | | - written = 0 |
85 | | - for index in range(partitions): |
86 | | - offset = index * per_file |
87 | | - length = table.num_rows - offset if index == partitions - 1 else per_file |
88 | | - if length <= 0: |
89 | | - break |
90 | | - pq.write_table(table.slice(offset, length), into / f"part-{index}.parquet") |
91 | | - written += 1 |
92 | | - return written |
| 58 | + |
| 59 | +def generate(into: pathlib.Path, partitions: int, scale: float) -> pathlib.Path: |
| 60 | + """Write `lineitem` as `partitions` Parquet files, and return their directory. |
| 61 | +
|
| 62 | + `tpchgen-cli` puts a sharded table in a subdirectory named for it, so the |
| 63 | + directory this returns is `into/lineitem` -- which is what the storage |
| 64 | + library's table provider wants, since it scans `*.parquet` under a |
| 65 | + directory and makes one partition per file. |
| 66 | + """ |
| 67 | + executable = shutil.which("tpchgen-cli") |
| 68 | + if executable is None: |
| 69 | + message = ( |
| 70 | + "tpchgen-cli not found on PATH; install it with " |
| 71 | + "`uv pip install tpchgen-cli`" |
| 72 | + ) |
| 73 | + raise RuntimeError(message) |
| 74 | + |
| 75 | + subprocess.run( # noqa: S603 |
| 76 | + [ |
| 77 | + executable, |
| 78 | + "parquet", |
| 79 | + f"--scale-factor={scale}", |
| 80 | + "--tables=lineitem", |
| 81 | + f"--parts={partitions}", |
| 82 | + f"--output-dir={into}", |
| 83 | + "--no-progress", |
| 84 | + "--quiet", |
| 85 | + ], |
| 86 | + check=True, |
| 87 | + ) |
| 88 | + return into / "lineitem" |
93 | 89 |
|
94 | 90 |
|
95 | 91 | def compare(table: pa.Table, reference: pa.Table) -> None: |
@@ -131,37 +127,20 @@ def compare(table: pa.Table, reference: pa.Table) -> None: |
131 | 127 |
|
132 | 128 | def main(argv: list[str] | None = None) -> int: |
133 | 129 | parser = argparse.ArgumentParser(description=__doc__) |
134 | | - parser.add_argument( |
135 | | - "--data", |
136 | | - type=pathlib.Path, |
137 | | - default=pathlib.Path(__file__).resolve().parents[1] |
138 | | - / "tpch" |
139 | | - / "data" |
140 | | - / "lineitem.parquet", |
141 | | - ) |
142 | 130 | parser.add_argument("--partitions", type=int, default=4) |
143 | 131 | parser.add_argument( |
144 | | - "--rows", |
145 | | - type=int, |
146 | | - default=2_000_000, |
147 | | - help="rows to use; 0 for all of them (SF 1 lineitem is ~6M)", |
| 132 | + "--scale", |
| 133 | + type=float, |
| 134 | + default=0.1, |
| 135 | + help="TPC-H scale factor; 1 is the full ~6M row lineitem", |
148 | 136 | ) |
149 | 137 | args = parser.parse_args(argv) |
150 | 138 |
|
151 | | - if not args.data.exists(): |
152 | | - sys.stderr.write( |
153 | | - f"{args.data} not found. Generate it with:\n" |
154 | | - " mkdir -p examples/tpch/data && cd examples/tpch/data\n" |
155 | | - " uv pip install tpchgen-cli\n" |
156 | | - " uv run --no-project tpchgen-cli -s 1 --format=parquet\n" |
157 | | - ) |
158 | | - return 2 |
159 | | - |
160 | 139 | workspace = pathlib.Path(tempfile.mkdtemp(prefix="dfx-tpch-")) |
161 | 140 | try: |
162 | | - data = workspace / "lineitem" |
163 | | - count = reshard(args.data, data, args.partitions, args.rows) |
164 | | - print(f"resharded into {count} file(s) under {data}") |
| 141 | + data = generate(workspace, args.partitions, args.scale) |
| 142 | + count = len(list(data.glob("*.parquet"))) |
| 143 | + print(f"generated {count} file(s) under {data}") |
165 | 144 |
|
166 | 145 | spec = SessionSpec( |
167 | 146 | tables={"lineitem": str(data)}, |
|
0 commit comments