From cc6407a2e40105ecf800c1174ecf944d2877f0f9 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 23 Jul 2026 17:09:09 +0000 Subject: [PATCH 1/3] Add Dask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements https://github.com/ClickHouse/ClickBench/issues/892. Dask is "pandas in parallel", so this mirrors the pandas port: a FastAPI server wraps a Dask DataFrame behind the install/start/check/stop/load/ query/data-size interface, and queries.sql holds one Python expression per line (eval()'d by the server), just like pandas/polars. Dask-specific choices: - Uses a local cluster of worker processes (dask.distributed.LocalCluster) for real multi-core parallelism; the frame is persisted in cluster memory so load time is comparable to the pandas/polars in-memory model. - Reads the partitioned dataset (hits_0..99.parquet) — one partition per file is Dask's natural layout. - Operations are lazy, so the server materialises each result with dask.compute() and rolls that into the query timing. - Queries are close to the pandas port, diverging only where Dask's API does: COUNT(DISTINCT) combined with other aggregates (Q10, Q23) uses Dask's documented custom nunique aggregation, LIMIT/OFFSET tails (Q39-43) compute the small top-N to pandas then slice (Dask has no positional iloc), and Q8/Q26/Q27 use nlargest/sort_values since Dask Series lacks sort_values. All 43 queries validated against a synthetic dataset both with the threaded scheduler and a real distributed LocalCluster, and end-to-end through the HTTP server; cluster teardown on shutdown leaves no orphaned workers across the harness's stop/start/reload cycles. Co-Authored-By: Claude Opus 4.8 (1M context) --- dask/benchmark.sh | 13 ++++ dask/check | 4 ++ dask/data-size | 4 ++ dask/install | 12 ++++ dask/load | 25 ++++++++ dask/queries.sql | 43 +++++++++++++ dask/query | 28 +++++++++ dask/server.py | 147 +++++++++++++++++++++++++++++++++++++++++++++ dask/start | 12 ++++ dask/stop | 17 ++++++ dask/template.json | 7 +++ 11 files changed, 312 insertions(+) create mode 100755 dask/benchmark.sh create mode 100755 dask/check create mode 100755 dask/data-size create mode 100755 dask/install create mode 100755 dask/load create mode 100644 dask/queries.sql create mode 100755 dask/query create mode 100755 dask/server.py create mode 100755 dask/start create mode 100755 dask/stop create mode 100644 dask/template.json diff --git a/dask/benchmark.sh b/dask/benchmark.sh new file mode 100755 index 0000000000..712ab13cbe --- /dev/null +++ b/dask/benchmark.sh @@ -0,0 +1,13 @@ +#!/bin/bash +export BENCH_DOWNLOAD_SCRIPT="download-hits-parquet-partitioned" +export BENCH_DURABLE=no +# Dask runs Python expressions directly (server eval()s them). +# queries.sql holds those Python expressions, one per line, so the +# default BENCH_QUERIES_FILE=queries.sql in lib/benchmark-common.sh +# picks them up unchanged. +# Skip the pre-snapshot ./stop+./start cycle: the loaded +# state lives only in the daemon's process memory (in-process +# DataFrame, cluster worker heaps) and stopping wipes it. The +# playground agent reads this and snapshots the running daemon. +export PLAYGROUND_SKIP_RESTART_BEFORE_SNAPSHOT=yes +exec ../lib/benchmark-common.sh diff --git a/dask/check b/dask/check new file mode 100755 index 0000000000..491bdd44fc --- /dev/null +++ b/dask/check @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +curl -sf http://127.0.0.1:8000/health | grep -q '"ok":true' diff --git a/dask/data-size b/dask/data-size new file mode 100755 index 0000000000..365ad4ecc8 --- /dev/null +++ b/dask/data-size @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +curl -sS http://127.0.0.1:8000/data-size | python3 -c 'import json,sys; print(json.load(sys.stdin)["bytes"])' diff --git a/dask/install b/dask/install new file mode 100755 index 0000000000..a05745f705 --- /dev/null +++ b/dask/install @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +sudo apt-get update -y +sudo apt-get install -y python3-pip python3-venv + +if [ ! -d myenv ]; then + python3 -m venv myenv +fi +# shellcheck disable=SC1091 +source myenv/bin/activate +pip install --quiet "dask[dataframe,distributed]" pyarrow fastapi uvicorn diff --git a/dask/load b/dask/load new file mode 100755 index 0000000000..c49ad543a4 --- /dev/null +++ b/dask/load @@ -0,0 +1,25 @@ +#!/bin/bash +set -e + +# See duckdb-memory/load for the rationale — the old one-liner +# elapsed=$(curl -sS ... | python3 -c '...elapsed...') +# masked curl/JSON failures with `set -e` and produced a successful-looking +# zero-timing run when the server died mid-ingest. Capture body and +# diagnose explicitly. +body=$(curl -sS -X POST http://127.0.0.1:8000/load 2>&1) || { + echo "load: curl to /load failed:" >&2 + printf '%s\n' "$body" >&2 + echo "load: server may have been OOM-killed during ingest" >&2 + exit 1 +} + +elapsed=$(printf '%s' "$body" | python3 -c \ + 'import json,sys;print(json.load(sys.stdin)["elapsed"])' 2>&1) || { + echo "load: /load did not return valid {\"elapsed\": ...} JSON:" >&2 + printf '%s\n' "$body" >&2 + exit 1 +} + +echo "Load (server-reported): ${elapsed}s" + +sync diff --git a/dask/queries.sql b/dask/queries.sql new file mode 100644 index 0000000000..91111a3f32 --- /dev/null +++ b/dask/queries.sql @@ -0,0 +1,43 @@ +hits.count() +hits[hits['AdvEngineID'] != 0].count() +(hits['AdvEngineID'].sum(), hits.shape[0], hits['ResolutionWidth'].mean()) +hits['UserID'].mean() +hits['UserID'].nunique() +hits['SearchPhrase'].nunique() +(hits['EventDate'].min(), hits['EventDate'].max()) +hits[hits['AdvEngineID'] != 0].groupby('AdvEngineID').size().rename('c').reset_index().sort_values('c', ascending=False) +hits.groupby('RegionID')['UserID'].nunique().nlargest(10) +hits.groupby('RegionID').agg(AdvEngineID=('AdvEngineID', 'sum'), c=('WatchID', 'size'), ResolutionWidth=('ResolutionWidth', 'mean'), UserID=('UserID', nunique)).nlargest(10, 'c') +hits[hits['MobilePhoneModel'] != ''].groupby('MobilePhoneModel')['UserID'].nunique().nlargest(10) +hits[hits['MobilePhoneModel'] != ''].groupby(['MobilePhone', 'MobilePhoneModel'])['UserID'].nunique().nlargest(10) +hits[hits['SearchPhrase'] != ''].groupby('SearchPhrase').size().nlargest(10) +hits[hits['SearchPhrase'] != ''].groupby('SearchPhrase')['UserID'].nunique().nlargest(10) +hits[hits['SearchPhrase'] != ''].groupby(['SearchEngineID', 'SearchPhrase']).size().nlargest(10) +hits.groupby('UserID').size().nlargest(10) +hits.groupby(['UserID', 'SearchPhrase']).size().nlargest(10) +hits.groupby(['UserID', 'SearchPhrase']).size().head(10) +hits.groupby([hits['UserID'], hits['EventTime'].dt.minute, 'SearchPhrase']).size().nlargest(10) +hits[hits['UserID'] == 435090932899640449] +hits[hits['URL'].str.contains('google')].shape[0] +hits[hits['URL'].str.contains('google') & (hits['SearchPhrase'] != '')].groupby('SearchPhrase').agg(URL=('URL', 'min'), c=('SearchPhrase', 'size')).nlargest(10, 'c') +hits[hits['Title'].str.contains('Google') & ~hits['URL'].str.contains('.google.') & (hits['SearchPhrase'] != '')].groupby('SearchPhrase').agg(URL=('URL', 'min'), Title=('Title', 'min'), c=('SearchPhrase', 'size'), UserID=('UserID', nunique)).nlargest(10, 'c') +hits[hits['URL'].str.contains('google')].nsmallest(10, 'EventTime') +hits[hits['SearchPhrase'] != ''].nsmallest(10, 'EventTime').compute()[['SearchPhrase']] +hits[hits['SearchPhrase'] != ''][['SearchPhrase']].sort_values('SearchPhrase').head(10, npartitions=-1) +hits[hits['SearchPhrase'] != ''].sort_values(['EventTime', 'SearchPhrase']).head(10, npartitions=-1)[['SearchPhrase']] +hits[hits['URL'] != ''].assign(l=hits['URL'].str.len()).groupby('CounterID').agg(l=('l', 'mean'), c=('URL', 'size')).query('c > 100000').nlargest(25, 'l') +hits[hits['Referer'] != ''].assign(k=lambda d: d['Referer'].str.extract('^https?://(?:www\\.)?([^/]+)/.*$')[0], l=lambda d: d['Referer'].str.len()).groupby('k').agg(l=('l', 'mean'), c=('Referer', 'size'), min_referer=('Referer', 'min')).query('c > 100000').nlargest(25, 'l') +[(hits['ResolutionWidth'] + i).sum() for i in range(90)] +hits[hits['SearchPhrase'] != ''].groupby(['SearchEngineID', 'ClientIP']).agg(c=('SearchEngineID', 'size'), IsRefreshSum=('IsRefresh', 'sum'), AvgResolutionWidth=('ResolutionWidth', 'mean')).nlargest(10, 'c') +hits[hits['SearchPhrase'] != ''].groupby(['WatchID', 'ClientIP']).agg(c=('WatchID', 'size'), IsRefreshSum=('IsRefresh', 'sum'), AvgResolutionWidth=('ResolutionWidth', 'mean')).nlargest(10, 'c') +hits.groupby(['WatchID', 'ClientIP']).agg(c=('WatchID', 'size'), IsRefreshSum=('IsRefresh', 'sum'), AvgResolutionWidth=('ResolutionWidth', 'mean')).nlargest(10, 'c') +hits.groupby('URL').size().rename('c').nlargest(10).reset_index() +hits.groupby('URL').size().rename('c').nlargest(10).reset_index() +hits.assign(**{f'ClientIP_minus_{i}': hits['ClientIP'] - i for i in range(1, 4)}).groupby(['ClientIP', 'ClientIP_minus_1', 'ClientIP_minus_2', 'ClientIP_minus_3']).size().rename('c').nlargest(10).reset_index() +hits[(hits['CounterID'] == 62) & (hits['EventDate'] >= '2013-07-01') & (hits['EventDate'] <= '2013-07-31') & (hits['DontCountHits'] == 0) & (hits['IsRefresh'] == 0) & (hits['URL'] != '')].groupby('URL').size().nlargest(10) +hits[(hits['CounterID'] == 62) & (hits['EventDate'] >= '2013-07-01') & (hits['EventDate'] <= '2013-07-31') & (hits['DontCountHits'] == 0) & (hits['IsRefresh'] == 0) & (hits['Title'] != '')].groupby('Title').size().nlargest(10) +hits[(hits['CounterID'] == 62) & (hits['EventDate'] >= '2013-07-01') & (hits['EventDate'] <= '2013-07-31') & (hits['IsRefresh'] == 0) & (hits['IsLink'] != 0) & (hits['IsDownload'] == 0)].groupby('URL').size().rename('PageViews').nlargest(1010).reset_index().compute().iloc[1000:1010] +hits[(hits['CounterID'] == 62) & (hits['EventDate'] >= '2013-07-01') & (hits['EventDate'] <= '2013-07-31') & (hits['IsRefresh'] == 0)].assign(Src=lambda d: d['Referer'].where((d['SearchEngineID'] == 0) & (d['AdvEngineID'] == 0), '')).groupby(['TraficSourceID', 'SearchEngineID', 'AdvEngineID', 'Src', 'URL']).size().rename('PageViews').nlargest(1010).reset_index().compute().iloc[1000:1010] +hits[(hits['CounterID'] == 62) & (hits['EventDate'] >= '2013-07-01') & (hits['EventDate'] <= '2013-07-31') & (hits['IsRefresh'] == 0) & hits['TraficSourceID'].isin([-1, 6]) & (hits['RefererHash'] == 3594120000172545465)].groupby(['URLHash', 'EventDate']).size().rename('PageViews').nlargest(110).reset_index().compute().iloc[100:110] +hits[(hits['CounterID'] == 62) & (hits['EventDate'] >= '2013-07-01') & (hits['EventDate'] <= '2013-07-31') & (hits['IsRefresh'] == 0) & (hits['DontCountHits'] == 0) & (hits['URLHash'] == 2868770270353813622)].groupby(['WindowClientWidth', 'WindowClientHeight']).size().rename('PageViews').nlargest(10010).reset_index().compute().iloc[10000:10010] +hits[(hits['CounterID'] == 62) & (hits['EventDate'] >= '2013-07-14') & (hits['EventDate'] <= '2013-07-15') & (hits['IsRefresh'] == 0) & (hits['DontCountHits'] == 0)].assign(M=lambda d: d['EventTime'].dt.floor('min')).groupby('M').size().rename('PageViews').reset_index().compute().sort_values('M').iloc[1000:1010] diff --git a/dask/query b/dask/query new file mode 100755 index 0000000000..5cf3ec5337 --- /dev/null +++ b/dask/query @@ -0,0 +1,28 @@ +#!/bin/bash +# Reads a query from stdin, dispatches to the running in-VM server. +# Stdout: result (rendered table or scalar from the server). +# Stderr: query runtime in fractional seconds on the last line. +# Exit non-zero on error. +set -e + +query=$(cat) + +tmp=$(mktemp) +status=$(curl -sS -o "$tmp" -w '%{http_code}' \ + -X POST --data-binary @- http://127.0.0.1:8000/query <<<"$query") + +body=$(cat "$tmp") +rm -f "$tmp" + +if [ "$status" != "200" ]; then + echo "query failed: HTTP $status: $body" >&2 + exit 1 +fi + +# Pull `result` for stdout and `elapsed` for stderr (host timing protocol). +python3 - "$body" <<'PY' +import json, sys +d = json.loads(sys.argv[1]) +print(d.get("result", "")) +sys.stderr.write(str(d["elapsed"]) + "\n") +PY diff --git a/dask/server.py b/dask/server.py new file mode 100755 index 0000000000..2497cc3019 --- /dev/null +++ b/dask/server.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""FastAPI wrapper around Dask so it conforms to the ClickBench +install/start/check/stop/load/query interface. + +Dask is "pandas in parallel": its DataFrame mirrors the pandas API but +splits the data into partitions and runs across a local cluster of worker +processes. We therefore mirror the pandas port closely — queries.sql holds +Python expressions, one per line — and only diverge where Dask's API does: +operations are lazy, so the server materialises the result with +dask.compute() and rolls that into the query timing. + +Routes: + GET /health -> 200 OK once the cluster is up + POST /load -> reads hits_*.parquet from the working directory, fixes + column types, persists the DataFrame in cluster memory, + and returns {"elapsed": } + POST /query -> body: a Python expression. eval()s it against the + loaded DataFrame (`hits`, `dd`, `dask`, `pd`, and the + `nunique` aggregation in scope), forces computation + with dask.compute(), and returns {"elapsed": }. + GET /data-size -> bytes the DataFrame occupies in memory (memory_usage) + +The /query endpoint takes a Python expression directly rather than an SQL +string mapped to a hardcoded lambda. The workload lives in queries.sql, +one Python expression per line (the filename matches the cross-system +convention; the contents are not SQL). +""" + +import os +import timeit +from contextlib import asynccontextmanager + +import dask +import dask.dataframe as dd +import pandas as pd +import uvicorn +from dask.distributed import Client, LocalCluster, wait +from fastapi import FastAPI, HTTPException, Request + +# Dask's built-in groupby.agg() doesn't offer nunique, so COUNT(DISTINCT) +# combined with other aggregates in one pass (queries 10 and 23) needs the +# documented custom aggregation. Exposed in the query scope as `nunique`. +# https://docs.dask.org/en/stable/dataframe-groupby.html#aggregate +nunique = dd.Aggregation( + name="nunique", + chunk=lambda s: s.apply(lambda x: list(set(x))), + agg=lambda s0: s0.obj.groupby(level=list(range(s0.obj.index.nlevels))).sum(), + finalize=lambda s1: s1.apply(lambda final: len(set(final))), +) + +# Dask reads a directory of parquet files as one partition per file, which +# is the natural, idiomatic layout for it — so we use the partitioned +# dataset (hits_0.parquet … hits_99.parquet). Resolve to an absolute glob so +# the read doesn't depend on the worker processes' CWD. +PARQUET_GLOB = os.environ.get( + "BENCH_DASK_PARQUET", + os.path.abspath("hits_*.parquet"), +) + +client: Client | None = None +cluster: LocalCluster | None = None +hits = None + + +@asynccontextmanager +async def lifespan(app: FastAPI): + yield + # The benchmark harness stops+starts this server before every cold + # query (BENCH_DURABLE=no). Tear the cluster down on shutdown so its + # worker processes don't orphan and pile up across the ~40 restarts. + if client is not None: + client.close() + if cluster is not None: + cluster.close() + + +app = FastAPI(lifespan=lifespan) + + +@app.get("/health") +def health(): + # Only healthy once the cluster is actually up. + return {"ok": client is not None} + + +@app.post("/load") +def load(): + global hits + start = timeit.default_timer() + df = dd.read_parquet(PARQUET_GLOB) + # Match the pandas port's type fixups: epoch-seconds -> datetime and + # epoch-days -> datetime. Dask already reads string columns as compact + # pyarrow-backed `string` dtype, so no object->str pass is needed. + df["EventTime"] = dd.to_datetime(df["EventTime"], unit="s") + df["EventDate"] = dd.to_datetime(df["EventDate"], unit="D") + # Hold the whole frame in cluster memory (the pandas/polars in-memory + # model) and block until every partition has actually materialised, so + # the reported load time is honest. + hits = df.persist() + wait(hits) + elapsed = round(timeit.default_timer() - start, 3) + return {"elapsed": elapsed} + + +@app.post("/query") +async def query(request: Request): + if hits is None: + raise HTTPException(status_code=409, detail="DataFrame not loaded; POST /load first") + code = (await request.body()).decode("utf-8").strip() + if not code: + raise HTTPException(status_code=400, detail="empty query") + try: + compiled = compile(code, "", "eval") + except SyntaxError as e: + raise HTTPException(status_code=400, detail=f"syntax error: {e}") + scope = {"hits": hits, "dd": dd, "dask": dask, "pd": pd, "nunique": nunique} + start = timeit.default_timer() + # eval() builds the (lazy) Dask graph; dask.compute() runs it. Timing + # both mirrors the pandas port, where eval() itself does all the work. + # dask.compute() recurses into tuples/lists, so queries that return a + # tuple of scalars (3, 7) or a list of sums (30) compute in one pass. + result = dask.compute(eval(compiled, scope))[0] + elapsed = round(timeit.default_timer() - start, 3) + # Render the result as a string so the playground UI sees the actual + # query output instead of just the timing. Truncated by the agent + # to OUTPUT_LIMIT before it reaches the browser. + return {"elapsed": elapsed, "result": str(result)} + + +@app.get("/data-size") +def data_size(): + if hits is None: + return {"bytes": 0} + return {"bytes": int(hits.memory_usage(deep=True).sum().compute())} + + +if __name__ == "__main__": + # A local cluster of worker processes gives Dask real multi-core + # parallelism. Defaults pick a sensible worker/thread split for the + # machine; the dashboard is disabled so the benchmark doesn't bind an + # extra port. Created here (guarded by __main__ so spawned workers + # don't re-run it) before uvicorn serves, so /health only answers + # once the cluster is ready. + cluster = LocalCluster(dashboard_address=None) + client = Client(cluster) + port = int(os.environ.get("BENCH_DASK_PORT", "8000")) + uvicorn.run(app, host="127.0.0.1", port=port, log_level="warning") diff --git a/dask/start b/dask/start new file mode 100755 index 0000000000..e3fab72731 --- /dev/null +++ b/dask/start @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +# Idempotent: if already running, leave it alone. +if [ -f server.pid ] && kill -0 "$(cat server.pid)" 2>/dev/null; then + exit 0 +fi + +# shellcheck disable=SC1091 +source myenv/bin/activate +nohup python3 server.py >server.log 2>&1 & +echo $! > server.pid diff --git a/dask/stop b/dask/stop new file mode 100755 index 0000000000..787b35abcc --- /dev/null +++ b/dask/stop @@ -0,0 +1,17 @@ +#!/bin/bash + +if [ -f server.pid ]; then + pid=$(cat server.pid) + if kill -0 "$pid" 2>/dev/null; then + kill "$pid" || true + # Wait up to 10s for graceful exit. + for _ in $(seq 1 10); do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + sleep 1 + done + kill -9 "$pid" 2>/dev/null || true + fi + rm -f server.pid +fi diff --git a/dask/template.json b/dask/template.json new file mode 100644 index 0000000000..18f4d9ce40 --- /dev/null +++ b/dask/template.json @@ -0,0 +1,7 @@ +{ + "system": "Dask (DataFrame)", + "proprietary": "no", + "hardware": "cpu", + "tuned": "no", + "tags": ["Python", "dataframe", "in-memory", "column-oriented", "lukewarm-cold-run"] +} From af0df5225fc34a25aa864e402ee9e46dfdf165ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:30:05 +0000 Subject: [PATCH 2/3] Add benchmark results for dask (c6a.metal) --- dask/results/20260723/c6a.metal.json | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 dask/results/20260723/c6a.metal.json diff --git a/dask/results/20260723/c6a.metal.json b/dask/results/20260723/c6a.metal.json new file mode 100644 index 0000000000..c69aad93f3 --- /dev/null +++ b/dask/results/20260723/c6a.metal.json @@ -0,0 +1,60 @@ +{ + "system": "Dask (DataFrame)", + "date": "2026-07-23", + "machine": "c6a.metal", + "cluster_size": 1, + "proprietary": "no", + "hardware": "cpu", + "tuned": "no", + "tags": ["Python","dataframe","in-memory","column-oriented","lukewarm-cold-run"], + "load_time": 40, + "data_size": 174168110180, + "concurrent_qps": 0.082, + "concurrent_error_ratio": 0.155, + "result": [ + [64.222, 7.563, 7.727], + [58.304, 0.71, 0.707], + [57.661, 0.409, 0.254], + [57.432, 0.278, 0.257], + [58.667, 1.227, 1.162], + [59.447, 2.335, 2.309], + [57.666, 0.256, 0.311], + [58.037, 0.719, 0.758], + [59.549, 2.382, 2.306], + [null, null, null], + [67.082, 10.4, 9.805], + [68.578, 11.581, 11.438], + [61.524, 4.222, 4.296], + [67.714, 9.871, 11.01], + [113.814, 53.797, 54.739], + [59.166, 1.54, 1.588], + [142.554, 90.388, 88.26], + [64.71, 7.225, 7.211], + [136.769, 71.584, 68.834], + [57.715, 0.58, 0.514], + [null, null, null], + [null, null, null], + [null, null, null], + [null, null, null], + [58.351, 0.89, 0.948], + [null, null, null], + [63.587, 6.332, 6.151], + [60.525, 3.193, 3.226], + [null, null, null], + [61.464, 4.507, 4.705], + [71.409, 13.903, 14.178], + [209.28, 153.635, 153.406], + [208.328, 155.147, 154.055], + [69.802, 12.435, 12.374], + [69.473, 12.61, 12.722], + [76.846, 20.591, 20.255], + [59.272, 1.771, 2.214], + [59.065, 1.468, 1.418], + [58.574, 0.849, 0.917], + [61.266, 3.688, 3.947], + [58.726, 1.01, 1.018], + [58.432, 0.918, 0.903], + [58.448, 0.932, 0.923] +] + } + \ No newline at end of file From d4a9521d3406d2ee267fb7d0a67c0f3f60a38a9c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 00:49:58 +0000 Subject: [PATCH 3/3] Add benchmark results for dask (c6a.metal, c7a.metal-48xl, c8g.metal-48xl) --- dask/results/20260724/c6a.metal.json | 60 +++++++++++++++++++++++ dask/results/20260724/c7a.metal-48xl.json | 60 +++++++++++++++++++++++ dask/results/20260724/c8g.metal-48xl.json | 60 +++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 dask/results/20260724/c6a.metal.json create mode 100644 dask/results/20260724/c7a.metal-48xl.json create mode 100644 dask/results/20260724/c8g.metal-48xl.json diff --git a/dask/results/20260724/c6a.metal.json b/dask/results/20260724/c6a.metal.json new file mode 100644 index 0000000000..04d3dc0706 --- /dev/null +++ b/dask/results/20260724/c6a.metal.json @@ -0,0 +1,60 @@ +{ + "system": "Dask (DataFrame)", + "date": "2026-07-24", + "machine": "c6a.metal", + "cluster_size": 1, + "proprietary": "no", + "hardware": "cpu", + "tuned": "no", + "tags": ["Python","dataframe","in-memory","column-oriented","lukewarm-cold-run"], + "load_time": 71, + "data_size": 174168110180, + "concurrent_qps": 0.08, + "concurrent_error_ratio": 0.172, + "result": [ + [64.327, 7.488, 7.706], + [58.413, 0.784, 0.911], + [57.779, 0.421, 0.251], + [57.608, 0.277, 0.276], + [58.342, 1.256, 1.233], + [59.416, 1.898, 2.104], + [57.507, 0.288, 0.287], + [57.919, 0.729, 0.703], + [60.01, 2.351, 2.709], + [null, null, null], + [67.568, 10.112, 10.153], + [68.678, 11.419, 11.295], + [61.52, 4.124, 4.377], + [67.667, 10.49, 10.105], + [105.971, 52.709, 54.968], + [58.969, 1.561, 1.493], + [141.452, 96.276, 86.095], + [64.34, 7.333, 6.791], + [134.602, 71.966, 75.828], + [57.882, 0.556, 0.475], + [null, null, null], + [null, null, null], + [null, null, null], + [null, null, null], + [58.34, 0.908, 0.868], + [null, null, null], + [63.947, 6.158, 6.867], + [61.09, 3.037, 3.529], + [null, null, null], + [61.51, 4.562, 4.68], + [71.334, 14.211, 13.895], + [209.84, 152.482, 154.958], + [209.852, 154.998, 151.511], + [69.724, 11.831, 12.081], + [69.544, 12.503, 12.443], + [77.534, 19.754, 20.131], + [59.624, 1.754, 2.013], + [58.879, 1.275, 1.494], + [58.487, 0.936, 0.879], + [61.248, 3.826, 3.788], + [58.826, 1.12, 1.043], + [58.374, 1.048, 0.831], + [58.59, 1.009, 0.974] +] + } + \ No newline at end of file diff --git a/dask/results/20260724/c7a.metal-48xl.json b/dask/results/20260724/c7a.metal-48xl.json new file mode 100644 index 0000000000..fc70597653 --- /dev/null +++ b/dask/results/20260724/c7a.metal-48xl.json @@ -0,0 +1,60 @@ +{ + "system": "Dask (DataFrame)", + "date": "2026-07-24", + "machine": "c7a.metal-48xl", + "cluster_size": 1, + "proprietary": "no", + "hardware": "cpu", + "tuned": "no", + "tags": ["Python","dataframe","in-memory","column-oriented","lukewarm-cold-run"], + "load_time": 69, + "data_size": 174168110180, + "concurrent_qps": 0.092, + "concurrent_error_ratio": 0.167, + "result": [ + [64.311, 6.044, 6.273], + [58.61, 0.59, 0.584], + [57.611, 0.287, 0.244], + [57.14, 0.225, 0.216], + [58.022, 1.115, 1.018], + [59.562, 1.782, 1.591], + [57.425, 0.241, 0.22], + [57.787, 0.545, 0.563], + [58.246, 1.871, 1.906], + [null, null, null], + [66.509, 9.188, 9.383], + [67.914, 10.232, 10.365], + [61.034, 3.578, 3.719], + [66.957, 9.32, 9.115], + [102.899, 47.573, 47.836], + [58.648, 1.288, 1.311], + [140.049, 91.377, 83.673], + [63.275, 6.373, 6.311], + [132.172, 71.46, 68.367], + [57.783, 0.454, 0.388], + [null, null, null], + [null, null, null], + [null, null, null], + [null, null, null], + [57.919, 0.823, 0.774], + [null, null, null], + [62.798, 5.355, 5.517], + [59.995, 2.525, 2.717], + [null, null, null], + [60.473, 3.712, 3.754], + [71.95, 14.743, 14.428], + [231.807, 174.428, 176.522], + [233.801, 173.07, 177.225], + [68.069, 11.209, 11.097], + [67.907, 11.658, 11.041], + [78.273, 20.86, 20.238], + [58.939, 1.52, 1.522], + [58.652, 1.115, 1.019], + [58.262, 0.761, 0.632], + [60.146, 3.115, 3.207], + [58.084, 0.778, 0.782], + [58.284, 0.8, 0.616], + [58.039, 0.824, 0.764] +] + } + \ No newline at end of file diff --git a/dask/results/20260724/c8g.metal-48xl.json b/dask/results/20260724/c8g.metal-48xl.json new file mode 100644 index 0000000000..1b7cb90b28 --- /dev/null +++ b/dask/results/20260724/c8g.metal-48xl.json @@ -0,0 +1,60 @@ +{ + "system": "Dask (DataFrame)", + "date": "2026-07-24", + "machine": "c8g.metal-48xl", + "cluster_size": 1, + "proprietary": "no", + "hardware": "cpu", + "tuned": "no", + "tags": ["Python","dataframe","in-memory","column-oriented","lukewarm-cold-run"], + "load_time": 69, + "data_size": 174168110180, + "concurrent_qps": 0.108, + "concurrent_error_ratio": 0.156, + "result": [ + [62.696, 4.739, 4.583], + [58.581, 0.485, 0.484], + [58.325, 0.204, 0.193], + [56.962, 0.143, 0.165], + [57.693, 0.798, 0.808], + [58.185, 1.346, 1.292], + [56.986, 0.135, 0.136], + [57.324, 0.426, 0.424], + [58.487, 1.599, 1.616], + [null, null, null], + [63.786, 6.839, 6.854], + [64.769, 7.835, 7.924], + [60.103, 3.324, 3.296], + [63.45, 6.64, 6.689], + [94.583, 37.84, 37.699], + [57.987, 1.127, 1.113], + [120.212, 62.556, 62.337], + [61.859, 5.143, 5.089], + [112.079, 52.355, 53.691], + [57.159, 0.382, 0.326], + [null, null, null], + [null, null, null], + [null, null, null], + [null, null, null], + [57.604, 0.577, 0.581], + [null, null, null], + [61.807, 4.925, 4.442], + [59.267, 2.134, 2.08], + [null, null, null], + [59.457, 2.794, 2.899], + [66.703, 9.836, 9.601], + [162.965, 107.044, 106.46], + [163.732, 108.041, 107.67], + [66.84, 9.45, 9.142], + [66.628, 9.691, 9.656], + [70.519, 13.682, 13.813], + [58.274, 1.192, 1.231], + [57.728, 0.773, 0.794], + [57.567, 0.571, 0.59], + [59.564, 2.546, 2.64], + [57.742, 0.658, 0.658], + [68.956, 0.713, 0.62], + [57.639, 0.586, 0.64] +] + } + \ No newline at end of file