-
Notifications
You must be signed in to change notification settings - Fork 26
Phase 1: Align the TPC-DS generation path with the TPC-H path #400
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
base: main
Are you sure you want to change the base?
Changes from all commits
356d9c7
b04df60
6763e52
62ccf19
56a7656
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import multiprocessing | ||
| import tempfile | ||
| from concurrent.futures import ProcessPoolExecutor | ||
| from contextlib import contextmanager | ||
| from pathlib import Path | ||
|
|
||
| import duckdb | ||
| import pyarrow.parquet as pq | ||
| from duckdb_utils import copy_to_parquet, get_select_query, init_benchmark_tables | ||
|
|
||
| # DuckDB Parquet sizing: https://duckdb.org/docs/current/data/parquet/tips | ||
| _ROW_GROUP_GRANULARITY = 2048 | ||
| _STAGE1_ROWS = 122_880 | ||
|
|
||
| _MAX_PROBE_SCALE_FACTOR = 10 | ||
| _PROBE_MEMORY_LIMIT = "8GB" | ||
|
Comment on lines
+18
to
+19
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probe MEM budget, perhaps a better value? Or remove it? |
||
|
|
||
|
|
||
| @contextmanager | ||
| def row_group_row_count_probe( | ||
| benchmark_type, target_bytes, target_scale_factor, convert_decimals_to_floats | ||
| ): | ||
| """Yield a callable that waits for row-count estimates from a spawned process.""" | ||
| # dbgen/dsdgen use process-global state and cannot run concurrently in one process. | ||
| with ProcessPoolExecutor( | ||
| max_workers=1, mp_context=multiprocessing.get_context("spawn") | ||
| ) as executor: | ||
| future = executor.submit( | ||
| get_row_group_row_counts, | ||
| benchmark_type, | ||
| target_bytes, | ||
| target_scale_factor, | ||
| convert_decimals_to_floats, | ||
| ) | ||
| yield future.result | ||
|
|
||
|
|
||
| def get_row_group_row_counts( | ||
| benchmark_type, target_bytes, target_scale_factor, convert_decimals_to_floats | ||
| ): | ||
| """Measure rows per row group for every table in a throwaway dataset.""" | ||
| scale_factor = ( | ||
| target_scale_factor | ||
| if target_scale_factor <= 1 | ||
| else min(target_scale_factor / 10, _MAX_PROBE_SCALE_FACTOR) | ||
| ) | ||
|
|
||
| row_counts = {} | ||
| with duckdb.connect() as conn: | ||
| conn.execute(f"SET memory_limit='{_PROBE_MEMORY_LIMIT}'") | ||
| init_benchmark_tables(benchmark_type, scale_factor, conn) | ||
| for (table_name,) in conn.execute("SHOW TABLES").fetchall(): | ||
| select_query = get_select_query(table_name, convert_decimals_to_floats, conn) | ||
| table_rows = conn.execute(f"SELECT COUNT(*) FROM {table_name}").fetchone()[0] | ||
| rows = _measure_row_group_rows(conn, select_query, table_rows, target_bytes) | ||
| if rows is not None: | ||
| row_counts[table_name] = rows | ||
| return row_counts | ||
|
|
||
|
|
||
| def _measure_row_group_rows(conn, select_query, table_rows, target_bytes): | ||
| """Rows per row group so that a row group weighs about target_bytes.""" | ||
| if table_rows == 0: | ||
| return None | ||
|
|
||
| with tempfile.TemporaryDirectory() as tmp_dir: | ||
| probe_path = Path(tmp_dir) / "probe.parquet" | ||
|
|
||
| # First pass: estimate bytes/row using DuckDB's default row-group size. | ||
| copy_to_parquet(f"{select_query} LIMIT {_STAGE1_ROWS}", probe_path, conn=conn) | ||
| rows = _rows_for_target(_bytes_per_row(probe_path), target_bytes) | ||
| if rows is None or rows >= table_rows: | ||
| # A second write cannot fill the estimated row group or improve the result. | ||
| return rows | ||
|
|
||
| # Second pass: bytes/row changes with row-group size, so remeasure near the requested size. | ||
| copy_to_parquet(f"{select_query} LIMIT {rows}", probe_path, rows, conn) | ||
| refined = _rows_for_target(_bytes_per_row(probe_path), target_bytes) | ||
|
|
||
| return rows if refined is None else refined | ||
|
|
||
|
|
||
| def _bytes_per_row(path): | ||
| """Read bytes/row from the probe's only row group.""" | ||
| row_group = pq.ParquetFile(path).metadata.row_group(0) | ||
| return row_group.total_byte_size / row_group.num_rows | ||
|
|
||
|
|
||
| def _rows_for_target(bytes_per_row, target_bytes): | ||
| """Round to the nearest 2,048-row multiple instead of letting DuckDB round up.""" | ||
| if not bytes_per_row: | ||
| return None | ||
| rows = round(target_bytes / bytes_per_row / _ROW_GROUP_GRANULARITY) | ||
| return max(rows, 1) * _ROW_GROUP_GRANULARITY | ||
|
Comment on lines
+92
to
+97
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This matters mainly for small row-group targets. DuckDB rounds |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,8 @@ | |
| TEST_NON_DEFAULT_COMPRESSION_PATH = TESTS_DIR / "test_codec_definitions_non_default_compression.json" | ||
| TEST_INVALID_COMPRESSION_PATH = TESTS_DIR / "test_codec_definitions_invalid_compression.json" | ||
|
|
||
| pytestmark = pytest.mark.parametrize("setup_and_teardown", ["tpch"], indirect=True) | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes every test in |
||
|
|
||
|
|
||
| def test_default_codec_defs_applied(setup_and_teardown): | ||
| """Generate data with default codec defs and verify encodings in parquet metadata. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we always do a conversion here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conversion is not unconditional.
get_column_projection()is only called inside theconvert_decimals_to_floatsbranch ofget_select_query(). Without-c,get_select_query()returnsSELECT *, so no casts are generated.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to
get_column_projection_with_decimals_as_double