{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# DuckDB GeoParquet Benchmarks\n", "\n", "This notebook measures direct DuckDB queries against local STAC GeoParquet outputs.\n", "\n", "The benchmark goal is to compare Parquet layouts, not hash generation speed. Run `scripts/sync-benchmark-data.sh` before timing queries. Use `scripts/generate-file-count-matched-geoparquet.sh` if you want to rebuild the 12-file hashed layout locally." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup\n", "\n", "Expected Python packages:\n", "\n", "- `duckdb`\n", "- `stac-hash`, only if you want to compute hash range parameters in Python\n", "\n", "The notebook uses DuckDB's spatial extension for exact geometry queries and `httpfs` for the optional remote S3 benchmark." ] }, { "cell_type": "code", "execution_count": 49, "id": "ef1a6def", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'1.5.5'" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from __future__ import annotations\n", "\n", "from dataclasses import dataclass\n", "from datetime import datetime, timezone\n", "from pathlib import Path\n", "import json\n", "import statistics\n", "import time\n", "\n", "import duckdb\n", "import pandas as pd\n", "\n", "con = duckdb.connect(database=':memory:')\n", "con.execute('INSTALL spatial')\n", "con.execute('LOAD spatial')\n", "con.execute('INSTALL httpfs')\n", "con.execute('LOAD httpfs')\n", "con.execute(\"\"\"\n", "CREATE OR REPLACE SECRET benchmark_public_s3 (\n", " TYPE s3,\n", " PROVIDER config,\n", " REGION 'us-west-2',\n", " ENDPOINT 's3.us-west-2.amazonaws.com',\n", " URL_STYLE 'path',\n", " USE_SSL true\n", ")\n", "\"\"\")\n", "con.execute('PRAGMA threads = 8')\n", "con.execute('SET enable_external_file_cache = false')\n", "\n", "duckdb.__version__" ] }, { "cell_type": "markdown", "id": "21124d02", "metadata": {}, "source": [ "## Dataset Variants\n", "\n", "Point each local variant at a GeoParquet file or glob. Remote variants use the same Source Cooperative layouts over S3 so object-store listing, range reads, and network latency are included. Keep all variants semantically equivalent so row counts match across layouts." ] }, { "cell_type": "code", "execution_count": 50, "id": "492f8030", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "({'microsoft': '../data/benchmarks/source/mspc-sentinel-2-l2a/*.parquet',\n", " 'hashed_128_files': '../data/benchmarks/source/mspc-sentinel-2-l2a-sorted/**/*.parquet',\n", " 'hashed_12_files': '../data/benchmarks/generated/mspc-sentinel-2-l2a-sorted-12-files/*.parquet'},\n", " {'remote_microsoft': 's3://us-west-2.opendata.source.coop/developmentseed/stac-geoparquet/mspc-sentinel-2-l2a/*.parquet',\n", " 'remote_hashed_128_files': 's3://us-west-2.opendata.source.coop/developmentseed/stac-geoparquet/mspc-sentinel-2-l2a-sorted/**/*.parquet',\n", " 'remote_hashed_12_files': 's3://us-west-2.opendata.source.coop/developmentseed/stac-geoparquet/mspc-sentinel-2-l2a-sorted-12-files/*.parquet'})" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "DATASETS = {\n", " 'microsoft': '../data/benchmarks/source/mspc-sentinel-2-l2a/*.parquet',\n", " 'hashed_128_files': '../data/benchmarks/source/mspc-sentinel-2-l2a-sorted/**/*.parquet',\n", " 'hashed_12_files': '../data/benchmarks/generated/mspc-sentinel-2-l2a-sorted-12-files/*.parquet',\n", "}\n", "\n", "SOURCE_COOPERATIVE_PREFIX = 's3://us-west-2.opendata.source.coop/developmentseed/stac-geoparquet'\n", "REMOTE_DATASETS = {\n", " 'remote_microsoft': f'{SOURCE_COOPERATIVE_PREFIX}/mspc-sentinel-2-l2a/*.parquet',\n", " 'remote_hashed_128_files': f'{SOURCE_COOPERATIVE_PREFIX}/mspc-sentinel-2-l2a-sorted/**/*.parquet',\n", " 'remote_hashed_12_files': f'{SOURCE_COOPERATIVE_PREFIX}/mspc-sentinel-2-l2a-sorted-12-files/*.parquet',\n", "}\n", "\n", "DATASETS, REMOTE_DATASETS" ] }, { "cell_type": "markdown", "id": "950d8fff", "metadata": {}, "source": [ "## Query Parameters\n", "\n", "Use fixed AOIs, time windows, collections, and ids so runs are comparable. Choose IDs that exist in every dataset variant. The source data currently covers 2025 Sentinel-2 L2A items.\n", "\n", "The following cell can auto-fill `PARAMS['id']` with a real item id if the placeholder is left unchanged.\n" ] }, { "cell_type": "code", "execution_count": 51, "id": "e675713b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'collection': 'sentinel-2-l2a',\n", " 'id': 'REPLACE_WITH_REAL_ITEM_ID',\n", " 'start_datetime': datetime.datetime(2025, 6, 1, 0, 0, tzinfo=datetime.timezone.utc),\n", " 'end_datetime': datetime.datetime(2025, 7, 1, 0, 0, tzinfo=datetime.timezone.utc),\n", " 'minx': -109.0,\n", " 'miny': 37.0,\n", " 'maxx': -102.0,\n", " 'maxy': 41.0,\n", " 'aoi_wkt': 'POLYGON((-109 37, -102 37, -102 41, -109 41, -109 37))',\n", " 'max_cloud_cover': 20.0,\n", " 'min_hash': 0,\n", " 'max_hash': 9223372036854775807}" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "PARAMS = {\n", " 'collection': 'sentinel-2-l2a',\n", " 'id': 'REPLACE_WITH_REAL_ITEM_ID',\n", " 'start_datetime': datetime(2025, 6, 1, tzinfo=timezone.utc),\n", " 'end_datetime': datetime(2025, 7, 1, tzinfo=timezone.utc),\n", " 'minx': -109.0,\n", " 'miny': 37.0,\n", " 'maxx': -102.0,\n", " 'maxy': 41.0,\n", " 'aoi_wkt': 'POLYGON((-109 37, -102 37, -102 41, -109 41, -109 37))',\n", " 'max_cloud_cover': 20.0,\n", " # Fill these once the hash range strategy is selected.\n", " 'min_hash': 0,\n", " 'max_hash': 9223372036854775807,\n", "}\n", "\n", "PARAMS" ] }, { "cell_type": "code", "execution_count": 52, "id": "0969c899", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'S2B_MSIL2A_20250101T031029_R075_T52VCJ_20250101T050301'" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Auto-fill a real item id for the needle-in-a-haystack query.\n", "# Override PARAMS['id'] manually above if you want a specific item.\n", "if PARAMS['id'] == 'REPLACE_WITH_REAL_ITEM_ID':\n", " PARAMS['id'] = con.execute(\n", " \"SELECT id FROM read_parquet(?, hive_partitioning = false) LIMIT 1\",\n", " [DATASETS['microsoft']],\n", " ).fetchone()[0]\n", "\n", "PARAMS['id']\n" ] }, { "cell_type": "markdown", "id": "fb2f6590", "metadata": {}, "source": [ "## Helpers" ] }, { "cell_type": "code", "execution_count": 53, "id": "c4b89a2f", "metadata": {}, "outputs": [], "source": [ "@dataclass(frozen=True)\n", "class BenchmarkResult:\n", " dataset: str\n", " query: str\n", " rows: int | None\n", " best_seconds: float\n", " median_seconds: float\n", " runs: tuple[float, ...]\n", "\n", "\n", "def sql_literal(value):\n", " if isinstance(value, datetime):\n", " return \"TIMESTAMPTZ '\" + value.isoformat().replace('+00:00', 'Z') + \"'\"\n", " if isinstance(value, str):\n", " return \"'\" + value.replace(\"'\", \"''\") + \"'\"\n", " if value is None:\n", " return 'NULL'\n", " return str(value)\n", "\n", "\n", "def render(template: str, dataset_glob: str, params: dict) -> str:\n", " values = {'parquet_glob': sql_literal(dataset_glob)}\n", " values.update({key: sql_literal(value) for key, value in params.items()})\n", " return template.format(**values)\n", "\n", "\n", "def run_sql(sql: str):\n", " return con.execute(sql).fetchall()\n", "\n", "\n", "def time_query(sql: str, repeats: int = 5) -> tuple[int | None, tuple[float, ...]]:\n", " rows = None\n", " timings = []\n", " for _ in range(repeats):\n", " started = time.perf_counter()\n", " result = con.execute(sql).fetchall()\n", " timings.append(time.perf_counter() - started)\n", " if len(result) == 1 and len(result[0]) == 1 and isinstance(result[0][0], int):\n", " rows = result[0][0]\n", " else:\n", " rows = len(result)\n", " return rows, tuple(timings)\n", "\n", "\n", "def explain_analyze(sql: str) -> str:\n", " rows = con.execute('EXPLAIN ANALYZE ' + sql).fetchall()\n", " return '\\n'.join(str(row[1] if len(row) > 1 else row[0]) for row in rows)\n", "\n", "\n", "def explain_analyze_json(sql: str):\n", " rows = con.execute('EXPLAIN (ANALYZE, FORMAT json) ' + sql).fetchall()\n", " payload = rows[0][1] if len(rows[0]) > 1 else rows[0][0]\n", " return json.loads(payload)" ] }, { "cell_type": "markdown", "id": "5e729410", "metadata": {}, "source": [ "## Parquet Metadata\n", "\n", "Run this before timing queries. It verifies file counts, row groups, and whether the important columns have useful row-group min/max statistics." ] }, { "cell_type": "code", "execution_count": 54, "id": "d01c2588", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "## microsoft\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
file_namerow_groupsmax_row_group_rowscompressed_bytes
0../data/benchmarks/source/mspc-sentinel-2-l2a/...16120481.010473e+11
1../data/benchmarks/source/mspc-sentinel-2-l2a/...14420489.619865e+10
2../data/benchmarks/source/mspc-sentinel-2-l2a/...21120481.346860e+11
3../data/benchmarks/source/mspc-sentinel-2-l2a/...22620481.577516e+11
4../data/benchmarks/source/mspc-sentinel-2-l2a/...23420481.592138e+11
5../data/benchmarks/source/mspc-sentinel-2-l2a/...22520481.498662e+11
6../data/benchmarks/source/mspc-sentinel-2-l2a/...23220481.465726e+11
7../data/benchmarks/source/mspc-sentinel-2-l2a/...23120481.465465e+11
8../data/benchmarks/source/mspc-sentinel-2-l2a/...22520481.437244e+11
9../data/benchmarks/source/mspc-sentinel-2-l2a/...21520481.363797e+11
10../data/benchmarks/source/mspc-sentinel-2-l2a/...17320481.094628e+11
11../data/benchmarks/source/mspc-sentinel-2-l2a/...18620481.175325e+11
\n", "
" ], "text/plain": [ " file_name row_groups \\\n", "0 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 161 \n", "1 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 144 \n", "2 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 211 \n", "3 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 226 \n", "4 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 234 \n", "5 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 225 \n", "6 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 232 \n", "7 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 231 \n", "8 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 225 \n", "9 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 215 \n", "10 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 173 \n", "11 ../data/benchmarks/source/mspc-sentinel-2-l2a/... 186 \n", "\n", " max_row_group_rows compressed_bytes \n", "0 2048 1.010473e+11 \n", "1 2048 9.619865e+10 \n", "2 2048 1.346860e+11 \n", "3 2048 1.577516e+11 \n", "4 2048 1.592138e+11 \n", "5 2048 1.498662e+11 \n", "6 2048 1.465726e+11 \n", "7 2048 1.465465e+11 \n", "8 2048 1.437244e+11 \n", "9 2048 1.363797e+11 \n", "10 2048 1.094628e+11 \n", "11 2048 1.175325e+11 " ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "## hashed_128_files\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
file_namerow_groupsmax_row_group_rowscompressed_bytes
0../data/benchmarks/source/mspc-sentinel-2-l2a-...1393075.473829e+09
1../data/benchmarks/source/mspc-sentinel-2-l2a-...1393075.607146e+09
2../data/benchmarks/source/mspc-sentinel-2-l2a-...1393085.866470e+09
3../data/benchmarks/source/mspc-sentinel-2-l2a-...1393075.287036e+09
4../data/benchmarks/source/mspc-sentinel-2-l2a-...1393085.906282e+09
...............
123../data/benchmarks/source/mspc-sentinel-2-l2a-...1393085.430317e+09
124../data/benchmarks/source/mspc-sentinel-2-l2a-...1393075.536509e+09
125../data/benchmarks/source/mspc-sentinel-2-l2a-...1393085.454389e+09
126../data/benchmarks/source/mspc-sentinel-2-l2a-...1393075.435148e+09
127../data/benchmarks/source/mspc-sentinel-2-l2a-...1393085.564445e+09
\n", "

128 rows × 4 columns

\n", "
" ], "text/plain": [ " file_name row_groups \\\n", "0 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", "1 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", "2 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", "3 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", "4 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", ".. ... ... \n", "123 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", "124 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", "125 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", "126 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", "127 ../data/benchmarks/source/mspc-sentinel-2-l2a-... 1 \n", "\n", " max_row_group_rows compressed_bytes \n", "0 39307 5.473829e+09 \n", "1 39307 5.607146e+09 \n", "2 39308 5.866470e+09 \n", "3 39307 5.287036e+09 \n", "4 39308 5.906282e+09 \n", ".. ... ... \n", "123 39308 5.430317e+09 \n", "124 39307 5.536509e+09 \n", "125 39308 5.454389e+09 \n", "126 39307 5.435148e+09 \n", "127 39308 5.564445e+09 \n", "\n", "[128 rows x 4 columns]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "## hashed_12_files\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
file_namerow_groupsmax_row_group_rowscompressed_bytes
0../data/benchmarks/generated/mspc-sentinel-2-l...41228804.857495e+10
1../data/benchmarks/generated/mspc-sentinel-2-l...41228805.161138e+10
2../data/benchmarks/generated/mspc-sentinel-2-l...41228805.364427e+10
3../data/benchmarks/generated/mspc-sentinel-2-l...41228804.633041e+10
4../data/benchmarks/generated/mspc-sentinel-2-l...41228805.140086e+10
5../data/benchmarks/generated/mspc-sentinel-2-l...41228805.413710e+10
6../data/benchmarks/generated/mspc-sentinel-2-l...41228804.819585e+10
7../data/benchmarks/generated/mspc-sentinel-2-l...41228805.287735e+10
8../data/benchmarks/generated/mspc-sentinel-2-l...41228805.418228e+10
9../data/benchmarks/generated/mspc-sentinel-2-l...41228804.735390e+10
10../data/benchmarks/generated/mspc-sentinel-2-l...41228805.370755e+10
11../data/benchmarks/generated/mspc-sentinel-2-l...41228805.168503e+10
\n", "
" ], "text/plain": [ " file_name row_groups \\\n", "0 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "1 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "2 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "3 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "4 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "5 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "6 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "7 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "8 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "9 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "10 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "11 ../data/benchmarks/generated/mspc-sentinel-2-l... 4 \n", "\n", " max_row_group_rows compressed_bytes \n", "0 122880 4.857495e+10 \n", "1 122880 5.161138e+10 \n", "2 122880 5.364427e+10 \n", "3 122880 4.633041e+10 \n", "4 122880 5.140086e+10 \n", "5 122880 5.413710e+10 \n", "6 122880 4.819585e+10 \n", "7 122880 5.287735e+10 \n", "8 122880 5.418228e+10 \n", "9 122880 4.735390e+10 \n", "10 122880 5.370755e+10 \n", "11 122880 5.168503e+10 " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "metadata_sql = \"\"\"\n", "SELECT\n", " file_name,\n", " count(DISTINCT row_group_id) AS row_groups,\n", " max(row_group_num_rows) AS max_row_group_rows,\n", " sum(row_group_compressed_bytes) AS compressed_bytes\n", "FROM parquet_metadata({parquet_glob})\n", "GROUP BY file_name\n", "ORDER BY file_name\n", "\"\"\"\n", "\n", "for name, glob in DATASETS.items():\n", " print('\\n##', name)\n", " try:\n", " display(con.execute(render(metadata_sql, glob, PARAMS)).df())\n", " except Exception as error:\n", " print(error)" ] }, { "cell_type": "code", "execution_count": 55, "id": "aa43939c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "## microsoft\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
path_in_schemarow_groupsglobal_minglobal_max
0collection2463sentinel-2-l2asentinel-2-l2a
1datetime24632025-01-01 00:04:39.024+002025-12-31 23:51:41.025+00
2id2463S2A_MSIL2A_20250101T004031_R002_T56TPT_2025010...S2C_MSIL2A_20251231T235141_R073_T59UPV_2026010...
\n", "
" ], "text/plain": [ " path_in_schema row_groups \\\n", "0 collection 2463 \n", "1 datetime 2463 \n", "2 id 2463 \n", "\n", " global_min \\\n", "0 sentinel-2-l2a \n", "1 2025-01-01 00:04:39.024+00 \n", "2 S2A_MSIL2A_20250101T004031_R002_T56TPT_2025010... \n", "\n", " global_max \n", "0 sentinel-2-l2a \n", "1 2025-12-31 23:51:41.025+00 \n", "2 S2C_MSIL2A_20251231T235141_R073_T59UPV_2026010... " ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "## hashed_128_files\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
path_in_schemarow_groupsglobal_minglobal_max
0collection128sentinel-2-l2asentinel-2-l2a
1datetime1282025-01-01 00:04:39.024+002025-12-31 23:51:41.025+00
2hash:hash1281060626393919962612974454625439932830
3id128S2A_MSIL2A_20250101T004031_R002_T56TPT_2025010...S2C_MSIL2A_20251231T235141_R073_T59UPV_2026010...
\n", "
" ], "text/plain": [ " path_in_schema row_groups \\\n", "0 collection 128 \n", "1 datetime 128 \n", "2 hash:hash 128 \n", "3 id 128 \n", "\n", " global_min \\\n", "0 sentinel-2-l2a \n", "1 2025-01-01 00:04:39.024+00 \n", "2 1060626393919962612 \n", "3 S2A_MSIL2A_20250101T004031_R002_T56TPT_2025010... \n", "\n", " global_max \n", "0 sentinel-2-l2a \n", "1 2025-12-31 23:51:41.025+00 \n", "2 974454625439932830 \n", "3 S2C_MSIL2A_20251231T235141_R073_T59UPV_2026010... " ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "## hashed_12_files\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
path_in_schemarow_groupsglobal_minglobal_max
0collection48sentinel-2-l2asentinel-2-l2a
1datetime482025-01-01 00:04:39.024+002025-12-31 23:51:41.025+00
2hash:hash4810813270132779229699186779992691430940
3id48S2A_MSIL2A_20250101T004031_R002_T56TPT_2025010...S2C_MSIL2A_20251231T235141_R073_T59UPV_2026010...
\n", "
" ], "text/plain": [ " path_in_schema row_groups \\\n", "0 collection 48 \n", "1 datetime 48 \n", "2 hash:hash 48 \n", "3 id 48 \n", "\n", " global_min \\\n", "0 sentinel-2-l2a \n", "1 2025-01-01 00:04:39.024+00 \n", "2 1081327013277922969 \n", "3 S2A_MSIL2A_20250101T004031_R002_T56TPT_2025010... \n", "\n", " global_max \n", "0 sentinel-2-l2a \n", "1 2025-12-31 23:51:41.025+00 \n", "2 9186779992691430940 \n", "3 S2C_MSIL2A_20251231T235141_R073_T59UPV_2026010... " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "stats_sql = \"\"\"\n", "SELECT\n", " path_in_schema,\n", " count(*) AS row_groups,\n", " min(stats_min_value) AS global_min,\n", " max(stats_max_value) AS global_max\n", "FROM parquet_metadata({parquet_glob})\n", "WHERE path_in_schema IN (\n", " 'hash:hash',\n", " 'datetime',\n", " 'collection',\n", " 'id',\n", " 'bbox.xmin',\n", " 'bbox.ymin',\n", " 'bbox.xmax',\n", " 'bbox.ymax'\n", ")\n", "GROUP BY path_in_schema\n", "ORDER BY path_in_schema\n", "\"\"\"\n", "\n", "for name, glob in DATASETS.items():\n", " print('\\n##', name)\n", " try:\n", " display(con.execute(render(stats_sql, glob, PARAMS)).df())\n", " except Exception as error:\n", " print(error)" ] }, { "cell_type": "markdown", "id": "bf4d3c3b", "metadata": {}, "source": [ "## Query Suite" ] }, { "cell_type": "code", "execution_count": 56, "id": "1f7bd68a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['q01_full_dataset_count',\n", " 'q02_time_range_count',\n", " 'q03_bbox_count',\n", " 'q04_stac_search_count',\n", " 'q05_hash_range_search',\n", " 'q06_search_page_datetime_order',\n", " 'q06_search_page_hash_order',\n", " 'q07_attribute_filter',\n", " 'q08_exact_geometry_intersects',\n", " 'q09_grouped_aggregation',\n", " 'q10_collection_latest_items',\n", " 'q11_specific_id_lookup',\n", " 'q11_scoped_id_lookup']" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "QUERIES = {\n", " 'q01_full_dataset_count': \"\"\"\n", "SELECT count(*)\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "\"\"\",\n", " 'q02_time_range_count': \"\"\"\n", "SELECT count(*)\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE datetime >= {start_datetime}\n", " AND datetime < {end_datetime}\n", "\"\"\",\n", " 'q03_bbox_count': \"\"\"\n", "SELECT count(*)\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE bbox.xmax >= {minx}\n", " AND bbox.xmin <= {maxx}\n", " AND bbox.ymax >= {miny}\n", " AND bbox.ymin <= {maxy}\n", "\"\"\",\n", " 'q04_stac_search_count': \"\"\"\n", "SELECT count(*)\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE collection = {collection}\n", " AND datetime >= {start_datetime}\n", " AND datetime < {end_datetime}\n", " AND bbox.xmax >= {minx}\n", " AND bbox.xmin <= {maxx}\n", " AND bbox.ymax >= {miny}\n", " AND bbox.ymin <= {maxy}\n", "\"\"\",\n", " 'q05_hash_range_search': \"\"\"\n", "SELECT count(*)\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE \"hash:hash\" BETWEEN {min_hash} AND {max_hash}\n", " AND datetime >= {start_datetime}\n", " AND datetime < {end_datetime}\n", " AND bbox.xmax >= {minx}\n", " AND bbox.xmin <= {maxx}\n", " AND bbox.ymax >= {miny}\n", " AND bbox.ymin <= {maxy}\n", "\"\"\",\n", " 'q06_search_page_datetime_order': \"\"\"\n", "SELECT id, collection, datetime\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE collection = {collection}\n", " AND datetime >= {start_datetime}\n", " AND datetime < {end_datetime}\n", " AND bbox.xmax >= {minx}\n", " AND bbox.xmin <= {maxx}\n", " AND bbox.ymax >= {miny}\n", " AND bbox.ymin <= {maxy}\n", "ORDER BY datetime, id\n", "LIMIT 100\n", "\"\"\",\n", " 'q06_search_page_hash_order': \"\"\"\n", "SELECT id, collection, datetime\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE collection = {collection}\n", " AND datetime >= {start_datetime}\n", " AND datetime < {end_datetime}\n", " AND bbox.xmax >= {minx}\n", " AND bbox.xmin <= {maxx}\n", " AND bbox.ymax >= {miny}\n", " AND bbox.ymin <= {maxy}\n", "ORDER BY \"hash:hash\", id\n", "LIMIT 100\n", "\"\"\",\n", " 'q07_attribute_filter': \"\"\"\n", "SELECT count(*)\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE collection = {collection}\n", " AND datetime >= {start_datetime}\n", " AND datetime < {end_datetime}\n", " AND bbox.xmax >= {minx}\n", " AND bbox.xmin <= {maxx}\n", " AND bbox.ymax >= {miny}\n", " AND bbox.ymin <= {maxy}\n", " AND \"eo:cloud_cover\" <= {max_cloud_cover}\n", "\"\"\",\n", " 'q08_exact_geometry_intersects': \"\"\"\n", "SELECT count(*)\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE datetime >= {start_datetime}\n", " AND datetime < {end_datetime}\n", " AND bbox.xmax >= {minx}\n", " AND bbox.xmin <= {maxx}\n", " AND bbox.ymax >= {miny}\n", " AND bbox.ymin <= {maxy}\n", " AND ST_Intersects(geometry, ST_GeomFromText({aoi_wkt}))\n", "\"\"\",\n", " 'q09_grouped_aggregation': \"\"\"\n", "SELECT\n", " collection,\n", " date_trunc('month', datetime) AS month,\n", " count(*) AS items\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "GROUP BY collection, month\n", "ORDER BY collection, month\n", "\"\"\",\n", " 'q10_collection_latest_items': \"\"\"\n", "SELECT id, collection, datetime\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE collection = {collection}\n", "ORDER BY datetime DESC\n", "LIMIT 100\n", "\"\"\",\n", " 'q11_specific_id_lookup': \"\"\"\n", "SELECT id, collection, datetime\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE id = {id}\n", "\"\"\",\n", " 'q11_scoped_id_lookup': \"\"\"\n", "SELECT id, collection, datetime\n", "FROM read_parquet({parquet_glob}, hive_partitioning = false)\n", "WHERE collection = {collection}\n", " AND id = {id}\n", "\"\"\",\n", "}\n", "\n", "list(QUERIES)" ] }, { "cell_type": "markdown", "id": "3b704df6", "metadata": {}, "source": [ "## Run Benchmarks\n", "\n", "This cell runs the full query suite across all dataset variants. Hash-only queries are automatically skipped for the Microsoft dataset because it does not have the `\"hash:hash\"` column.\n", "\n", "Use `median_seconds` for comparisons. `best_seconds` is useful for spotting warm-cache potential, but it can be optimistic.\n" ] }, { "cell_type": "code", "execution_count": 57, "id": "04940b05", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "microsoft / q01_full_dataset_count: rows=5031349 best=0.8521s median=0.8705s\n", "microsoft / q02_time_range_count: rows=459093 best=0.1377s median=0.1416s\n", "microsoft / q03_bbox_count: rows=8842 best=0.2135s median=0.2149s\n", "microsoft / q04_stac_search_count: rows=783 best=0.1478s median=0.1499s\n", "microsoft / q06_search_page_datetime_order: rows=100 best=0.1429s median=0.1449s\n", "microsoft / q07_attribute_filter: rows=488 best=0.1490s median=0.1537s\n", "microsoft / q08_exact_geometry_intersects: rows=783 best=0.1540s median=0.1859s\n", "microsoft / q09_grouped_aggregation: rows=13 best=0.1961s median=0.1988s\n", "microsoft / q10_collection_latest_items: rows=100 best=0.1869s median=0.1905s\n", "microsoft / q11_specific_id_lookup: rows=1 best=0.1832s median=0.1839s\n", "microsoft / q11_scoped_id_lookup: rows=1 best=0.1941s median=0.1957s\n", "hashed_128_files / q01_full_dataset_count: rows=5031349 best=0.0320s median=0.0323s\n", "hashed_128_files / q02_time_range_count: rows=459093 best=0.0147s median=0.0152s\n", "hashed_128_files / q03_bbox_count: rows=8842 best=0.0149s median=0.0152s\n", "hashed_128_files / q04_stac_search_count: rows=783 best=0.0143s median=0.0144s\n", "hashed_128_files / q05_hash_range_search: rows=783 best=0.0165s median=0.0167s\n", "hashed_128_files / q06_search_page_datetime_order: rows=100 best=0.0158s median=0.0165s\n", "hashed_128_files / q06_search_page_hash_order: rows=100 best=0.0155s median=0.0157s\n", "hashed_128_files / q07_attribute_filter: rows=488 best=0.0155s median=0.0158s\n", "hashed_128_files / q08_exact_geometry_intersects: rows=783 best=0.0157s median=0.0160s\n", "hashed_128_files / q09_grouped_aggregation: rows=13 best=0.0337s median=0.0349s\n", "hashed_128_files / q10_collection_latest_items: rows=100 best=0.0223s median=0.0245s\n", "hashed_128_files / q11_specific_id_lookup: rows=1 best=0.0340s median=0.0365s\n", "hashed_128_files / q11_scoped_id_lookup: rows=1 best=0.0354s median=0.0355s\n", "hashed_12_files / q01_full_dataset_count: rows=5031349 best=0.0141s median=0.0149s\n", "hashed_12_files / q02_time_range_count: rows=459093 best=0.0045s median=0.0046s\n", "hashed_12_files / q03_bbox_count: rows=8842 best=0.0085s median=0.0094s\n", "hashed_12_files / q04_stac_search_count: rows=783 best=0.0060s median=0.0060s\n", "hashed_12_files / q05_hash_range_search: rows=783 best=0.0067s median=0.0067s\n", "hashed_12_files / q06_search_page_datetime_order: rows=100 best=0.0093s median=0.0095s\n", "hashed_12_files / q06_search_page_hash_order: rows=100 best=0.0101s median=0.0103s\n", "hashed_12_files / q07_attribute_filter: rows=488 best=0.0073s median=0.0073s\n", "hashed_12_files / q08_exact_geometry_intersects: rows=783 best=0.0202s median=0.0212s\n", "hashed_12_files / q09_grouped_aggregation: rows=13 best=0.0141s median=0.0144s\n", "hashed_12_files / q10_collection_latest_items: rows=100 best=0.0204s median=0.0212s\n", "hashed_12_files / q11_specific_id_lookup: rows=1 best=0.0270s median=0.0291s\n", "hashed_12_files / q11_scoped_id_lookup: rows=1 best=0.0282s median=0.0284s\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
querydatasetrowsbest_secondsmedian_seconds
0q01_full_dataset_counthashed_12_files50313490.0141330.014875
1q01_full_dataset_counthashed_128_files50313490.0319870.032251
2q01_full_dataset_countmicrosoft50313490.8520510.870463
3q02_time_range_counthashed_12_files4590930.0045290.004574
4q02_time_range_counthashed_128_files4590930.0146910.015225
5q02_time_range_countmicrosoft4590930.1376970.141585
6q03_bbox_counthashed_12_files88420.0085380.009385
7q03_bbox_counthashed_128_files88420.0149150.015246
8q03_bbox_countmicrosoft88420.2135230.214881
9q04_stac_search_counthashed_12_files7830.0059850.006046
10q04_stac_search_counthashed_128_files7830.0142510.014443
11q04_stac_search_countmicrosoft7830.1477530.149947
12q05_hash_range_searchhashed_12_files7830.0066520.006733
13q05_hash_range_searchhashed_128_files7830.0165150.016729
14q06_search_page_datetime_orderhashed_12_files1000.0093340.009472
15q06_search_page_datetime_orderhashed_128_files1000.0157560.016459
16q06_search_page_datetime_ordermicrosoft1000.1429080.144857
17q06_search_page_hash_orderhashed_12_files1000.0101460.010341
18q06_search_page_hash_orderhashed_128_files1000.0154870.015679
19q07_attribute_filterhashed_12_files4880.0073010.007330
20q07_attribute_filterhashed_128_files4880.0155200.015809
21q07_attribute_filtermicrosoft4880.1489530.153744
22q08_exact_geometry_intersectshashed_128_files7830.0157190.015985
23q08_exact_geometry_intersectshashed_12_files7830.0201630.021185
24q08_exact_geometry_intersectsmicrosoft7830.1540220.185925
25q09_grouped_aggregationhashed_12_files130.0140760.014371
26q09_grouped_aggregationhashed_128_files130.0336750.034884
27q09_grouped_aggregationmicrosoft130.1960650.198808
28q10_collection_latest_itemshashed_12_files1000.0204150.021245
29q10_collection_latest_itemshashed_128_files1000.0223290.024467
30q10_collection_latest_itemsmicrosoft1000.1868830.190527
31q11_scoped_id_lookuphashed_12_files10.0282470.028409
32q11_scoped_id_lookuphashed_128_files10.0354030.035527
33q11_scoped_id_lookupmicrosoft10.1941430.195707
34q11_specific_id_lookuphashed_12_files10.0269630.029061
35q11_specific_id_lookuphashed_128_files10.0339880.036518
36q11_specific_id_lookupmicrosoft10.1832080.183944
\n", "
" ], "text/plain": [ " query dataset rows best_seconds \\\n", "0 q01_full_dataset_count hashed_12_files 5031349 0.014133 \n", "1 q01_full_dataset_count hashed_128_files 5031349 0.031987 \n", "2 q01_full_dataset_count microsoft 5031349 0.852051 \n", "3 q02_time_range_count hashed_12_files 459093 0.004529 \n", "4 q02_time_range_count hashed_128_files 459093 0.014691 \n", "5 q02_time_range_count microsoft 459093 0.137697 \n", "6 q03_bbox_count hashed_12_files 8842 0.008538 \n", "7 q03_bbox_count hashed_128_files 8842 0.014915 \n", "8 q03_bbox_count microsoft 8842 0.213523 \n", "9 q04_stac_search_count hashed_12_files 783 0.005985 \n", "10 q04_stac_search_count hashed_128_files 783 0.014251 \n", "11 q04_stac_search_count microsoft 783 0.147753 \n", "12 q05_hash_range_search hashed_12_files 783 0.006652 \n", "13 q05_hash_range_search hashed_128_files 783 0.016515 \n", "14 q06_search_page_datetime_order hashed_12_files 100 0.009334 \n", "15 q06_search_page_datetime_order hashed_128_files 100 0.015756 \n", "16 q06_search_page_datetime_order microsoft 100 0.142908 \n", "17 q06_search_page_hash_order hashed_12_files 100 0.010146 \n", "18 q06_search_page_hash_order hashed_128_files 100 0.015487 \n", "19 q07_attribute_filter hashed_12_files 488 0.007301 \n", "20 q07_attribute_filter hashed_128_files 488 0.015520 \n", "21 q07_attribute_filter microsoft 488 0.148953 \n", "22 q08_exact_geometry_intersects hashed_128_files 783 0.015719 \n", "23 q08_exact_geometry_intersects hashed_12_files 783 0.020163 \n", "24 q08_exact_geometry_intersects microsoft 783 0.154022 \n", "25 q09_grouped_aggregation hashed_12_files 13 0.014076 \n", "26 q09_grouped_aggregation hashed_128_files 13 0.033675 \n", "27 q09_grouped_aggregation microsoft 13 0.196065 \n", "28 q10_collection_latest_items hashed_12_files 100 0.020415 \n", "29 q10_collection_latest_items hashed_128_files 100 0.022329 \n", "30 q10_collection_latest_items microsoft 100 0.186883 \n", "31 q11_scoped_id_lookup hashed_12_files 1 0.028247 \n", "32 q11_scoped_id_lookup hashed_128_files 1 0.035403 \n", "33 q11_scoped_id_lookup microsoft 1 0.194143 \n", "34 q11_specific_id_lookup hashed_12_files 1 0.026963 \n", "35 q11_specific_id_lookup hashed_128_files 1 0.033988 \n", "36 q11_specific_id_lookup microsoft 1 0.183208 \n", "\n", " median_seconds \n", "0 0.014875 \n", "1 0.032251 \n", "2 0.870463 \n", "3 0.004574 \n", "4 0.015225 \n", "5 0.141585 \n", "6 0.009385 \n", "7 0.015246 \n", "8 0.214881 \n", "9 0.006046 \n", "10 0.014443 \n", "11 0.149947 \n", "12 0.006733 \n", "13 0.016729 \n", "14 0.009472 \n", "15 0.016459 \n", "16 0.144857 \n", "17 0.010341 \n", "18 0.015679 \n", "19 0.007330 \n", "20 0.015809 \n", "21 0.153744 \n", "22 0.015985 \n", "23 0.021185 \n", "24 0.185925 \n", "25 0.014371 \n", "26 0.034884 \n", "27 0.198808 \n", "28 0.021245 \n", "29 0.024467 \n", "30 0.190527 \n", "31 0.028409 \n", "32 0.035527 \n", "33 0.195707 \n", "34 0.029061 \n", "35 0.036518 \n", "36 0.183944 " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "HASH_ONLY_QUERIES = {\n", " 'q05_hash_range_search',\n", " 'q06_search_page_hash_order',\n", "}\n", "HASHED_DATASET_SUFFIXES = ('hashed_128_files', 'hashed_12_files')\n", "def dataset_has_hash(dataset_name: str) -> bool:\n", " return dataset_name.endswith(HASHED_DATASET_SUFFIXES)\n", "\n", "\n", "def run_benchmark_matrix(datasets: dict[str, str], queries: dict[str, str], repeats: int = 5):\n", " results = []\n", " for dataset_name, dataset_glob in datasets.items():\n", " for query_name, template in queries.items():\n", " if query_name in HASH_ONLY_QUERIES and not dataset_has_hash(dataset_name):\n", " continue\n", " sql = render(template, dataset_glob, PARAMS)\n", " try:\n", " rows, timings = time_query(sql, repeats=repeats)\n", " except Exception as error:\n", " print(f'{dataset_name} / {query_name}: {error}')\n", " continue\n", " results.append(BenchmarkResult(\n", " dataset=dataset_name,\n", " query=query_name,\n", " rows=rows,\n", " best_seconds=min(timings),\n", " median_seconds=statistics.median(timings),\n", " runs=timings,\n", " ))\n", " print(f'{dataset_name} / {query_name}: rows={rows} best={min(timings):0.4f}s median={statistics.median(timings):0.4f}s')\n", " return results\n", "\n", "\n", "def results_table(results: list[BenchmarkResult]):\n", " rows = [\n", " {\n", " 'query': result.query,\n", " 'dataset': result.dataset,\n", " 'rows': result.rows,\n", " 'best_seconds': result.best_seconds,\n", " 'median_seconds': result.median_seconds,\n", " }\n", " for result in results\n", " ]\n", " if not rows:\n", " return pd.DataFrame(columns=['query', 'dataset', 'rows', 'best_seconds', 'median_seconds'])\n", " return pd.DataFrame(rows).sort_values(['query', 'median_seconds']).reset_index(drop=True)\n", "\n", "\n", "results = run_benchmark_matrix(DATASETS, QUERIES, repeats=5)\n", "summary = results_table(results)\n", "display(summary)\n" ] }, { "cell_type": "markdown", "id": "7be4bbc4", "metadata": {}, "source": [ "### Remote Object-Store Run\n", "\n", "Run this cell to benchmark the same query suite directly against Source Cooperative S3. These timings include object-store listing, network latency, and HTTP range reads, so compare them separately from the local-first results above." ] }, { "cell_type": "code", "execution_count": 58, "id": "905032a4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "remote_microsoft / q01_full_dataset_count: rows=5031349 best=2.5981s median=2.9397s\n", "remote_microsoft / q02_time_range_count: rows=459093 best=3.6311s median=3.9620s\n", "remote_microsoft / q03_bbox_count: rows=8842 best=71.0025s median=72.0031s\n", "remote_microsoft / q04_stac_search_count: rows=783 best=12.0099s median=12.2135s\n", "remote_microsoft / q06_search_page_datetime_order: rows=100 best=7.9093s median=8.7381s\n", "remote_microsoft / q07_attribute_filter: rows=488 best=14.3093s median=14.6723s\n", "remote_microsoft / q08_exact_geometry_intersects: rows=783 best=10.6404s median=11.3679s\n", "remote_microsoft / q09_grouped_aggregation: rows=13 best=52.6225s median=54.3369s\n", "remote_microsoft / q10_collection_latest_items: rows=100 best=26.3156s median=28.1502s\n", "remote_microsoft / q11_specific_id_lookup: rows=1 best=25.9754s median=26.3912s\n", "remote_microsoft / q11_scoped_id_lookup: rows=1 best=38.5690s median=38.6381s\n", "remote_hashed_128_files / q01_full_dataset_count: rows=5031349 best=4.0914s median=4.3767s\n", "remote_hashed_128_files / q02_time_range_count: rows=459093 best=4.8746s median=4.9197s\n", "remote_hashed_128_files / q03_bbox_count: rows=8842 best=5.2841s median=5.2943s\n", "remote_hashed_128_files / q04_stac_search_count: rows=783 best=4.5157s median=4.6776s\n", "remote_hashed_128_files / q05_hash_range_search: rows=783 best=4.3911s median=4.4901s\n", "remote_hashed_128_files / q06_search_page_datetime_order: rows=100 best=4.4843s median=4.5451s\n", "remote_hashed_128_files / q06_search_page_hash_order: rows=100 best=4.5019s median=4.6599s\n", "remote_hashed_128_files / q07_attribute_filter: rows=488 best=4.0703s median=4.3655s\n", "remote_hashed_128_files / q08_exact_geometry_intersects: rows=783 best=4.6839s median=4.8462s\n", "remote_hashed_128_files / q09_grouped_aggregation: rows=13 best=7.3265s median=7.9991s\n", "remote_hashed_128_files / q10_collection_latest_items: rows=100 best=5.5255s median=5.7226s\n", "remote_hashed_128_files / q11_specific_id_lookup: rows=1 best=7.4086s median=7.6844s\n", "remote_hashed_128_files / q11_scoped_id_lookup: rows=1 best=8.1795s median=8.3105s\n", "remote_hashed_12_files / q01_full_dataset_count: rows=5031349 best=0.7411s median=0.7971s\n", "remote_hashed_12_files / q02_time_range_count: rows=459093 best=1.0877s median=1.0941s\n", "remote_hashed_12_files / q03_bbox_count: rows=8842 best=1.4447s median=1.4833s\n", "remote_hashed_12_files / q04_stac_search_count: rows=783 best=1.5439s median=1.6488s\n", "remote_hashed_12_files / q05_hash_range_search: rows=783 best=0.9995s median=1.2122s\n", "remote_hashed_12_files / q06_search_page_datetime_order: rows=100 best=1.5180s median=1.5477s\n", "remote_hashed_12_files / q06_search_page_hash_order: rows=100 best=1.5915s median=1.6447s\n", "remote_hashed_12_files / q07_attribute_filter: rows=488 best=1.6306s median=1.6514s\n", "remote_hashed_12_files / q08_exact_geometry_intersects: rows=783 best=1.2469s median=1.5986s\n", "remote_hashed_12_files / q09_grouped_aggregation: rows=13 best=2.0327s median=2.2132s\n", "remote_hashed_12_files / q10_collection_latest_items: rows=100 best=2.3241s median=2.3461s\n", "remote_hashed_12_files / q11_specific_id_lookup: rows=1 best=2.0912s median=2.1962s\n", "remote_hashed_12_files / q11_scoped_id_lookup: rows=1 best=2.7425s median=2.8153s\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
querydatasetrowsbest_secondsmedian_seconds
0q01_full_dataset_countremote_hashed_12_files50313490.7410870.797104
1q01_full_dataset_countremote_microsoft50313492.5980672.939739
2q01_full_dataset_countremote_hashed_128_files50313494.0913974.376748
3q02_time_range_countremote_hashed_12_files4590931.0876561.094050
4q02_time_range_countremote_microsoft4590933.6310583.962012
5q02_time_range_countremote_hashed_128_files4590934.8745564.919652
6q03_bbox_countremote_hashed_12_files88421.4446691.483335
7q03_bbox_countremote_hashed_128_files88425.2841425.294268
8q03_bbox_countremote_microsoft884271.00248272.003055
9q04_stac_search_countremote_hashed_12_files7831.5438811.648777
10q04_stac_search_countremote_hashed_128_files7834.5157444.677643
11q04_stac_search_countremote_microsoft78312.00994812.213530
12q05_hash_range_searchremote_hashed_12_files7830.9995221.212228
13q05_hash_range_searchremote_hashed_128_files7834.3911194.490073
14q06_search_page_datetime_orderremote_hashed_12_files1001.5180441.547722
15q06_search_page_datetime_orderremote_hashed_128_files1004.4842664.545145
16q06_search_page_datetime_orderremote_microsoft1007.9093478.738074
17q06_search_page_hash_orderremote_hashed_12_files1001.5914611.644658
18q06_search_page_hash_orderremote_hashed_128_files1004.5019114.659922
19q07_attribute_filterremote_hashed_12_files4881.6305551.651420
20q07_attribute_filterremote_hashed_128_files4884.0703194.365493
21q07_attribute_filterremote_microsoft48814.30926414.672340
22q08_exact_geometry_intersectsremote_hashed_12_files7831.2469261.598570
23q08_exact_geometry_intersectsremote_hashed_128_files7834.6839004.846160
24q08_exact_geometry_intersectsremote_microsoft78310.64044511.367901
25q09_grouped_aggregationremote_hashed_12_files132.0327352.213163
26q09_grouped_aggregationremote_hashed_128_files137.3264697.999093
27q09_grouped_aggregationremote_microsoft1352.62247054.336930
28q10_collection_latest_itemsremote_hashed_12_files1002.3241492.346078
29q10_collection_latest_itemsremote_hashed_128_files1005.5255025.722617
30q10_collection_latest_itemsremote_microsoft10026.31564728.150243
31q11_scoped_id_lookupremote_hashed_12_files12.7424622.815297
32q11_scoped_id_lookupremote_hashed_128_files18.1795148.310530
33q11_scoped_id_lookupremote_microsoft138.56896938.638126
34q11_specific_id_lookupremote_hashed_12_files12.0912352.196206
35q11_specific_id_lookupremote_hashed_128_files17.4086017.684417
36q11_specific_id_lookupremote_microsoft125.97542326.391167
\n", "
" ], "text/plain": [ " query dataset rows \\\n", "0 q01_full_dataset_count remote_hashed_12_files 5031349 \n", "1 q01_full_dataset_count remote_microsoft 5031349 \n", "2 q01_full_dataset_count remote_hashed_128_files 5031349 \n", "3 q02_time_range_count remote_hashed_12_files 459093 \n", "4 q02_time_range_count remote_microsoft 459093 \n", "5 q02_time_range_count remote_hashed_128_files 459093 \n", "6 q03_bbox_count remote_hashed_12_files 8842 \n", "7 q03_bbox_count remote_hashed_128_files 8842 \n", "8 q03_bbox_count remote_microsoft 8842 \n", "9 q04_stac_search_count remote_hashed_12_files 783 \n", "10 q04_stac_search_count remote_hashed_128_files 783 \n", "11 q04_stac_search_count remote_microsoft 783 \n", "12 q05_hash_range_search remote_hashed_12_files 783 \n", "13 q05_hash_range_search remote_hashed_128_files 783 \n", "14 q06_search_page_datetime_order remote_hashed_12_files 100 \n", "15 q06_search_page_datetime_order remote_hashed_128_files 100 \n", "16 q06_search_page_datetime_order remote_microsoft 100 \n", "17 q06_search_page_hash_order remote_hashed_12_files 100 \n", "18 q06_search_page_hash_order remote_hashed_128_files 100 \n", "19 q07_attribute_filter remote_hashed_12_files 488 \n", "20 q07_attribute_filter remote_hashed_128_files 488 \n", "21 q07_attribute_filter remote_microsoft 488 \n", "22 q08_exact_geometry_intersects remote_hashed_12_files 783 \n", "23 q08_exact_geometry_intersects remote_hashed_128_files 783 \n", "24 q08_exact_geometry_intersects remote_microsoft 783 \n", "25 q09_grouped_aggregation remote_hashed_12_files 13 \n", "26 q09_grouped_aggregation remote_hashed_128_files 13 \n", "27 q09_grouped_aggregation remote_microsoft 13 \n", "28 q10_collection_latest_items remote_hashed_12_files 100 \n", "29 q10_collection_latest_items remote_hashed_128_files 100 \n", "30 q10_collection_latest_items remote_microsoft 100 \n", "31 q11_scoped_id_lookup remote_hashed_12_files 1 \n", "32 q11_scoped_id_lookup remote_hashed_128_files 1 \n", "33 q11_scoped_id_lookup remote_microsoft 1 \n", "34 q11_specific_id_lookup remote_hashed_12_files 1 \n", "35 q11_specific_id_lookup remote_hashed_128_files 1 \n", "36 q11_specific_id_lookup remote_microsoft 1 \n", "\n", " best_seconds median_seconds \n", "0 0.741087 0.797104 \n", "1 2.598067 2.939739 \n", "2 4.091397 4.376748 \n", "3 1.087656 1.094050 \n", "4 3.631058 3.962012 \n", "5 4.874556 4.919652 \n", "6 1.444669 1.483335 \n", "7 5.284142 5.294268 \n", "8 71.002482 72.003055 \n", "9 1.543881 1.648777 \n", "10 4.515744 4.677643 \n", "11 12.009948 12.213530 \n", "12 0.999522 1.212228 \n", "13 4.391119 4.490073 \n", "14 1.518044 1.547722 \n", "15 4.484266 4.545145 \n", "16 7.909347 8.738074 \n", "17 1.591461 1.644658 \n", "18 4.501911 4.659922 \n", "19 1.630555 1.651420 \n", "20 4.070319 4.365493 \n", "21 14.309264 14.672340 \n", "22 1.246926 1.598570 \n", "23 4.683900 4.846160 \n", "24 10.640445 11.367901 \n", "25 2.032735 2.213163 \n", "26 7.326469 7.999093 \n", "27 52.622470 54.336930 \n", "28 2.324149 2.346078 \n", "29 5.525502 5.722617 \n", "30 26.315647 28.150243 \n", "31 2.742462 2.815297 \n", "32 8.179514 8.310530 \n", "33 38.568969 38.638126 \n", "34 2.091235 2.196206 \n", "35 7.408601 7.684417 \n", "36 25.975423 26.391167 " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "remote_results = run_benchmark_matrix(REMOTE_DATASETS, QUERIES, repeats=3)\n", "remote_summary = results_table(remote_results)\n", "display(remote_summary)\n" ] }, { "cell_type": "markdown", "id": "97f0622b", "metadata": {}, "source": [ "## Analyze Results\n", "\n", "These derived tables make the first-pass interpretation easier. The speedup table compares median runtimes by query. The file summary helps identify whether differences are caused by hash ordering or by lower-level Parquet characteristics such as file size and row-group layout.\n" ] }, { "cell_type": "code", "execution_count": 59, "id": "0d1e5809", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
datasetqueryhashed_128_fileshashed_12_filesmicrosoftmicrosoft_vs_hashed_12_speeduphashed_128_vs_hashed_12_speedup
0q01_full_dataset_count0.0322510.0148750.87046358.5175492.168101
1q02_time_range_count0.0152250.0045740.14158530.9526613.328333
2q03_bbox_count0.0152460.0093850.21488122.8959571.624508
3q04_stac_search_count0.0144430.0060460.14994724.8002102.388825
4q05_hash_range_search0.0167290.006733NaNNaN2.484600
5q06_search_page_datetime_order0.0164590.0094720.14485715.2927671.737593
6q06_search_page_hash_order0.0156790.010341NaNNaN1.516112
7q07_attribute_filter0.0158090.0073300.15374420.9753972.156838
8q08_exact_geometry_intersects0.0159850.0211850.1859258.7763550.754545
9q09_grouped_aggregation0.0348840.0143710.19880813.8336882.427357
10q10_collection_latest_items0.0244670.0212450.1905278.9682331.151668
11q11_scoped_id_lookup0.0355270.0284090.1957076.8888031.250527
12q11_specific_id_lookup0.0365180.0290610.1839446.3294751.256586
\n", "
" ], "text/plain": [ "dataset query hashed_128_files hashed_12_files \\\n", "0 q01_full_dataset_count 0.032251 0.014875 \n", "1 q02_time_range_count 0.015225 0.004574 \n", "2 q03_bbox_count 0.015246 0.009385 \n", "3 q04_stac_search_count 0.014443 0.006046 \n", "4 q05_hash_range_search 0.016729 0.006733 \n", "5 q06_search_page_datetime_order 0.016459 0.009472 \n", "6 q06_search_page_hash_order 0.015679 0.010341 \n", "7 q07_attribute_filter 0.015809 0.007330 \n", "8 q08_exact_geometry_intersects 0.015985 0.021185 \n", "9 q09_grouped_aggregation 0.034884 0.014371 \n", "10 q10_collection_latest_items 0.024467 0.021245 \n", "11 q11_scoped_id_lookup 0.035527 0.028409 \n", "12 q11_specific_id_lookup 0.036518 0.029061 \n", "\n", "dataset microsoft microsoft_vs_hashed_12_speedup \\\n", "0 0.870463 58.517549 \n", "1 0.141585 30.952661 \n", "2 0.214881 22.895957 \n", "3 0.149947 24.800210 \n", "4 NaN NaN \n", "5 0.144857 15.292767 \n", "6 NaN NaN \n", "7 0.153744 20.975397 \n", "8 0.185925 8.776355 \n", "9 0.198808 13.833688 \n", "10 0.190527 8.968233 \n", "11 0.195707 6.888803 \n", "12 0.183944 6.329475 \n", "\n", "dataset hashed_128_vs_hashed_12_speedup \n", "0 2.168101 \n", "1 3.328333 \n", "2 1.624508 \n", "3 2.388825 \n", "4 2.484600 \n", "5 1.737593 \n", "6 1.516112 \n", "7 2.156838 \n", "8 0.754545 \n", "9 2.427357 \n", "10 1.151668 \n", "11 1.250527 \n", "12 1.256586 " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
datasetqueryremote_hashed_128_filesremote_hashed_12_filesremote_microsoftremote_microsoft_vs_hashed_12_speedupremote_hashed_128_vs_hashed_12_speedup
0q01_full_dataset_count4.3767480.7971042.9397393.6880255.490814
1q02_time_range_count4.9196521.0940503.9620123.6214174.496734
2q03_bbox_count5.2942681.48333572.00305548.5413173.569164
3q04_stac_search_count4.6776431.64877712.2135307.4076302.837038
4q05_hash_range_search4.4900731.212228NaNNaN3.703984
5q06_search_page_datetime_order4.5451451.5477228.7380745.6457632.936667
6q06_search_page_hash_order4.6599221.644658NaNNaN2.833369
7q07_attribute_filter4.3654931.65142014.6723408.8846812.643478
8q08_exact_geometry_intersects4.8461601.59857011.3679017.1112933.031559
9q09_grouped_aggregation7.9990932.21316354.33693024.5517023.614325
10q10_collection_latest_items5.7226172.34607828.15024311.9988532.439227
11q11_scoped_id_lookup8.3105302.81529738.63812613.7243512.951919
12q11_specific_id_lookup7.6844172.19620626.39116712.0167073.498950
\n", "
" ], "text/plain": [ "dataset query remote_hashed_128_files \\\n", "0 q01_full_dataset_count 4.376748 \n", "1 q02_time_range_count 4.919652 \n", "2 q03_bbox_count 5.294268 \n", "3 q04_stac_search_count 4.677643 \n", "4 q05_hash_range_search 4.490073 \n", "5 q06_search_page_datetime_order 4.545145 \n", "6 q06_search_page_hash_order 4.659922 \n", "7 q07_attribute_filter 4.365493 \n", "8 q08_exact_geometry_intersects 4.846160 \n", "9 q09_grouped_aggregation 7.999093 \n", "10 q10_collection_latest_items 5.722617 \n", "11 q11_scoped_id_lookup 8.310530 \n", "12 q11_specific_id_lookup 7.684417 \n", "\n", "dataset remote_hashed_12_files remote_microsoft \\\n", "0 0.797104 2.939739 \n", "1 1.094050 3.962012 \n", "2 1.483335 72.003055 \n", "3 1.648777 12.213530 \n", "4 1.212228 NaN \n", "5 1.547722 8.738074 \n", "6 1.644658 NaN \n", "7 1.651420 14.672340 \n", "8 1.598570 11.367901 \n", "9 2.213163 54.336930 \n", "10 2.346078 28.150243 \n", "11 2.815297 38.638126 \n", "12 2.196206 26.391167 \n", "\n", "dataset remote_microsoft_vs_hashed_12_speedup \\\n", "0 3.688025 \n", "1 3.621417 \n", "2 48.541317 \n", "3 7.407630 \n", "4 NaN \n", "5 5.645763 \n", "6 NaN \n", "7 8.884681 \n", "8 7.111293 \n", "9 24.551702 \n", "10 11.998853 \n", "11 13.724351 \n", "12 12.016707 \n", "\n", "dataset remote_hashed_128_vs_hashed_12_speedup \n", "0 5.490814 \n", "1 4.496734 \n", "2 3.569164 \n", "3 2.837038 \n", "4 3.703984 \n", "5 2.936667 \n", "6 2.833369 \n", "7 2.643478 \n", "8 3.031559 \n", "9 3.614325 \n", "10 2.439227 \n", "11 2.951919 \n", "12 3.498950 " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pivot = summary.pivot(index='query', columns='dataset', values='median_seconds')\n", "speedups = pivot.copy()\n", "if {'microsoft', 'hashed_12_files'}.issubset(speedups.columns):\n", " speedups['microsoft_vs_hashed_12_speedup'] = speedups['microsoft'] / speedups['hashed_12_files']\n", "if {'hashed_128_files', 'hashed_12_files'}.issubset(speedups.columns):\n", " speedups['hashed_128_vs_hashed_12_speedup'] = speedups['hashed_128_files'] / speedups['hashed_12_files']\n", "\n", "display(speedups.reset_index())\n", "\n", "if 'remote_summary' in globals() and not remote_summary.empty:\n", " remote_pivot = remote_summary.pivot(index='query', columns='dataset', values='median_seconds')\n", " remote_speedups = remote_pivot.copy()\n", " if {'remote_microsoft', 'remote_hashed_12_files'}.issubset(remote_speedups.columns):\n", " remote_speedups['remote_microsoft_vs_hashed_12_speedup'] = remote_speedups['remote_microsoft'] / remote_speedups['remote_hashed_12_files']\n", " if {'remote_hashed_128_files', 'remote_hashed_12_files'}.issubset(remote_speedups.columns):\n", " remote_speedups['remote_hashed_128_vs_hashed_12_speedup'] = remote_speedups['remote_hashed_128_files'] / remote_speedups['remote_hashed_12_files']\n", " display(remote_speedups.reset_index())\n" ] }, { "cell_type": "code", "execution_count": 60, "id": "e3bf3ff6", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
datasetfilesrow_groupscompressed_gbmedian_row_groups_per_filemedian_max_row_group_rows
0hashed_128_files128128701.7647381.039307.0
1hashed_12_files1248613.7009404.0122880.0
2microsoft1224631598.982122220.02048.0
\n", "
" ], "text/plain": [ " dataset files row_groups compressed_gb \\\n", "0 hashed_128_files 128 128 701.764738 \n", "1 hashed_12_files 12 48 613.700940 \n", "2 microsoft 12 2463 1598.982122 \n", "\n", " median_row_groups_per_file median_max_row_group_rows \n", "0 1.0 39307.0 \n", "1 4.0 122880.0 \n", "2 220.0 2048.0 " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "metadata_rows = []\n", "for dataset, glob in DATASETS.items():\n", " df = con.execute(render(metadata_sql, glob, PARAMS)).df()\n", " metadata_rows.append({\n", " 'dataset': dataset,\n", " 'files': len(df),\n", " 'row_groups': int(df['row_groups'].sum()),\n", " 'compressed_gb': float(df['compressed_bytes'].sum() / 1_000_000_000),\n", " 'median_row_groups_per_file': float(df['row_groups'].median()),\n", " 'median_max_row_group_rows': float(df['max_row_group_rows'].median()),\n", " })\n", "\n", "file_summary = pd.DataFrame(metadata_rows).sort_values('dataset').reset_index(drop=True)\n", "display(file_summary)\n" ] }, { "cell_type": "markdown", "id": "73ed4497", "metadata": {}, "source": [ "### Initial Reading Checklist\n", "\n", "- If `microsoft_vs_hashed_12_speedup` is high for `q04_stac_search_count`, the generated hashed 12-file layout is materially better for the STAC-style query.\n", "- If `hashed_128_vs_hashed_12_speedup` is above 1, the 12-file rewrite is faster than the 128-file layout for that query. That is a file-count or rewrite effect, not a hash effect.\n", "- If `q01_full_dataset_count` is dramatically different, do not attribute all wins to hash sorting. It usually indicates major Parquet-level differences such as compressed size, metadata, row groups, or schema encoding.\n", "- Treat `q05_hash_range_search` as provisional until the hash bounds are computed from the actual query window instead of placeholder min/max values.\n" ] }, { "cell_type": "markdown", "id": "e49911ed", "metadata": {}, "source": [ "## How To Read The Summary\n", "\n", "For each query, compare rows with the same `query` value:\n", "\n", "- `microsoft` vs `hashed_12_files` isolates sort/layout effects while keeping file count at 12.\n", "- `hashed_128_files` vs `hashed_12_files` shows how much file count changes timing for the same hashed data.\n", "- `q01_full_dataset_count` is mostly a raw scan control. If this differs a lot, file count/compression/schema overhead may be dominating.\n", "- `q04_stac_search_count` is the main STAC-style query to watch. A hashed win here is the strongest signal.\n", "- `q05_hash_range_search` is only meaningful once `PARAMS['min_hash']` and `PARAMS['max_hash']` represent the AOI/time window. Until then, treat it as provisional.\n", "- `q11_specific_id_lookup` is a control. Hash sorting is not expected to help much for a bare id lookup.\n", "\n", "Use `median_seconds` for comparison.\n" ] }, { "cell_type": "markdown", "id": "f6b260eb", "metadata": {}, "source": [ "## Inspect a Plan\n", "\n", "Use this when a timing difference looks interesting. The key line to look for is `Total Files Read`; if that number drops, DuckDB is pruning files. Also inspect filters shown under `TABLE_SCAN`.\n", "\n", "Start with `q04_stac_search_count` on `microsoft` and `hashed_12_files`, then compare their plans side by side.\n" ] }, { "cell_type": "code", "execution_count": 61, "id": "3aa37255", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "SELECT count(*)\n", "FROM read_parquet('../data/benchmarks/generated/mspc-sentinel-2-l2a-sorted-12-files/*.parquet', hive_partitioning = false)\n", "WHERE collection = 'sentinel-2-l2a'\n", " AND datetime >= TIMESTAMPTZ '2025-06-01T00:00:00Z'\n", " AND datetime < TIMESTAMPTZ '2025-07-01T00:00:00Z'\n", " AND bbox.xmax >= -109.0\n", " AND bbox.xmin <= -102.0\n", " AND bbox.ymax >= 37.0\n", " AND bbox.ymin <= 41.0\n", "\n", "┌─────────────────────────────────────┐\n", "│┌───────────────────────────────────┐│\n", "││ Query Profiling Information ││\n", "│└───────────────────────────────────┘│\n", "└─────────────────────────────────────┘\n", "EXPLAIN ANALYZE SELECT count(*) FROM read_parquet('../data/benchmarks/generated/mspc-sentinel-2-l2a-sorted-12-files/*.parquet', hive_partitioning = false) WHERE collection = 'sentinel-2-l2a' AND datetime >= TIMESTAMPTZ '2025-06-01T00:00:00Z' AND datetime < TIMESTAMPTZ '2025-07-01T00:00:00Z' AND bbox.xmax >= -109.0 AND bbox.xmin <= -102.0 AND bbox.ymax >= 37.0 AND bbox.ymin <= 41.0 \n", "┌─────────────────────────────────────┐\n", "│┌───────────────────────────────────┐│\n", "││ HTTPFS HTTP Stats ││\n", "││ ││\n", "││ in: 0 bytes ││\n", "││ out: 0 bytes ││\n", "││ #HEAD: 0 ││\n", "││ #GET: 0 ││\n", "││ #PUT: 0 ││\n", "││ #POST: 0 ││\n", "││ #DELETE: 0 ││\n", "│└───────────────────────────────────┘│\n", "└─────────────────────────────────────┘\n", "┌────────────────────────────────────────────────┐\n", "│┌──────────────────────────────────────────────┐│\n", "││ Total Time: 0.0093s ││\n", "│└──────────────────────────────────────────────┘│\n", "└────────────────────────────────────────────────┘\n", "┌───────────────────────────┐\n", "│ QUERY │\n", "└─────────────┬─────────────┘\n", "┌─────────────┴─────────────┐\n", "│ EXPLAIN_ANALYZE │\n", "│ ──────────────────── │\n", "│ │\n", "│ 0 rows │\n", "│ 0.00s │\n", "└─────────────┬─────────────┘\n", "┌─────────────┴─────────────┐\n", "│ UNGROUPED_AGGREGATE │\n", "│ ──────────────────── │\n", "│ Aggregates: │\n", "│ count_star() │\n", "│ │\n", "│ │\n", "│ │\n", "│ 1 row │\n", "│ 0.00s │\n", "└─────────────┬─────────────┘\n", "┌─────────────┴─────────────┐\n", "│ TABLE_SCAN │\n", "│ ──────────────────── │\n", "│ Function: │\n", "│ READ_PARQUET │\n", "│ │\n", "│ Filters: │\n", "│collection='sentinel-2-l2a'│\n", "│ datetime>='2025-06-01 00 │\n", "│ :00:00+00'::TIMESTAMP WITH│\n", "│ TIME ZONE AND datetime< │\n", "│ '2025-07-01 00:00:00+00': │\n", "│ :TIMESTAMP WITH TIME ZONE │\n", "│ bbox.xmax>=-109.0 AND bbox│\n", "│ .xmin<=-102.0 AND bbox │\n", "│ .ymax>=37.0 AND bbox.ymin<│\n", "│ =41.0 │\n", "│ │\n", "│ Total Files Read: 12 │\n", "│ │\n", "│ Filename(s): │\n", "│ ../data/benchmarks │\n", "│ /generated/mspc-sentinel-2│\n", "│ -l2a-sorted-12-files/* │\n", "│ .parquet, ... │\n", "│ │\n", "│ │\n", "│ │\n", "│ 783 rows │\n", "│ 0.04s │\n", "└───────────────────────────┘\n", "\n" ] } ], "source": [ "dataset_name = 'hashed_12_files'\n", "query_name = 'q04_stac_search_count'\n", "\n", "sql = render(QUERIES[query_name], DATASETS[dataset_name], PARAMS)\n", "print(sql)\n", "print(explain_analyze(sql))" ] }, { "cell_type": "code", "execution_count": 62, "id": "b3143c35", "metadata": {}, "outputs": [], "source": [ "# JSON profile for downstream parsing. This is mostly useful once you know which query/dataset pair matters.\n", "# Uncomment when needed.\n", "# profile = explain_analyze_json(sql)\n", "# profile\n" ] }, { "cell_type": "markdown", "id": "79d41b73", "metadata": {}, "source": [ "## Export Results\n", "\n", "Run this after `results` exists and you want to save the timings for sharing or comparison.\n" ] }, { "cell_type": "code", "execution_count": 63, "id": "ca8fec01", "metadata": {}, "outputs": [], "source": [ "def write_results(results: list[BenchmarkResult], path: str | Path):\n", " path = Path(path)\n", " path.parent.mkdir(parents=True, exist_ok=True)\n", " rows = [\n", " {\n", " 'dataset': result.dataset,\n", " 'query': result.query,\n", " 'rows': result.rows,\n", " 'best_seconds': result.best_seconds,\n", " 'median_seconds': result.median_seconds,\n", " 'runs': list(result.runs),\n", " 'duckdb_version': duckdb.__version__,\n", " }\n", " for result in results\n", " ]\n", " path.write_text(json.dumps(rows, indent=2), encoding='utf-8')\n", " return path\n", "\n", "\n", "write_results(results, '../benchmark-results/duckdb-geoparquet-results.json')\n", "if 'remote_results' in globals():\n", " write_results(remote_results, '../benchmark-results/duckdb-geoparquet-remote-results.json')\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3" } }, "nbformat": 4, "nbformat_minor": 5 }