Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions spiceai-cayenne/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Spice.ai OSS (Cayenne)

[Spice.ai OSS](https://github.com/spiceai/spiceai) is a portable, single-binary
runtime built on Apache DataFusion that federates and accelerates SQL queries
across databases, data warehouses, and data lakes. This entry benchmarks its
native Cayenne acceleration engine: `./load` ingests `hits.parquet` into a
disk-backed Cayenne table (`acceleration: {engine: cayenne, mode: file}`), and
queries are served from that acceleration through the runtime's HTTP API.

Notes:

- `spicepod.yaml` takes the place of `create.sql`: it defines the dataset and
its acceleration. The dataset keeps the source parquet schema, where
`EventDate` is an integer (days since epoch); the queries referencing
`EventDate` wrap it as `to_timestamp("EventDate" * 86400)`, matching how
Spice's own benchmark suite runs ClickBench. Everything else in
`queries.sql` is identical to the `datafusion` entry's.
- The SQL results cache (enabled by default in Spice) is disabled in
`spicepod.yaml` per the benchmark caching rules.
- The acceleration persists across restarts and the runtime skips re-ingestion
when the on-disk data is current, so the true-cold-run restart cycle serves
disk-resident data like other durable systems.

Run `./benchmark.sh` on a fresh Ubuntu VM (see `lib/benchmark-common.sh` for
the flow), or use the repo's `run-benchmark.sh` automation.
4 changes: 4 additions & 0 deletions spiceai-cayenne/benchmark.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
# Thin shim — actual flow is in lib/benchmark-common.sh.
export BENCH_DOWNLOAD_SCRIPT="download-hits-parquet-single"
exec ../lib/benchmark-common.sh
13 changes: 13 additions & 0 deletions spiceai-cayenne/check
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
set -e

# The driver starts the system before the dataset is downloaded; until
# hits.parquet exists the dataset cannot load (the runtime stays up and
# retries), so gate on process liveness only. Afterwards gate on
# /v1/ready, which returns 200 only once the accelerated dataset is
# ready to serve queries.
if [ -e hits.parquet ]; then
curl -fsS http://localhost:8090/v1/ready >/dev/null
else
curl -fsS http://localhost:8090/health >/dev/null
fi
6 changes: 6 additions & 0 deletions spiceai-cayenne/data-size
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
set -e

# Cayenne file-mode acceleration data (segments + metastore), written by
# the runtime under ./.spice.
du -sb .spice | cut -f1
18 changes: 18 additions & 0 deletions spiceai-cayenne/install
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
set -e

# install.spiceai.org ships the CLI only; `spice install` then downloads
# the runtime binary (spiced) into ~/.spice/bin.
if [ ! -x "$HOME/.spice/bin/spice" ]; then
curl https://install.spiceai.org | /bin/bash
fi
if [ ! -x "$HOME/.spice/bin/spiced" ]; then
"$HOME/.spice/bin/spice" install >/dev/null 2>&1
fi

# Symlink into /usr/local/bin so sibling scripts (check, load, query,
# start) find them unconditionally.
sudo ln -sf "$HOME/.spice/bin/spiced" /usr/local/bin/spiced
sudo ln -sf "$HOME/.spice/bin/spice" /usr/local/bin/spice

spiced --version
21 changes: 21 additions & 0 deletions spiceai-cayenne/load
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash
set -e

# Idempotent: drop any prior acceleration state and restart the runtime so
# the Cayenne acceleration performs a full ingest of hits.parquet.
./stop >/dev/null 2>&1 || true
rm -rf .spice spiced.log
./start

# /v1/ready returns 200 once the initial refresh has completed and the
# dataset is queryable from the acceleration.
for _ in $(seq 1 7200); do
if curl -fsS http://localhost:8090/v1/ready >/dev/null 2>&1; then
exit 0
fi
sleep 1
done

echo "load: dataset did not become ready within 7200s" >&2
tail -n 50 spiced.log >&2
exit 1
59 changes: 59 additions & 0 deletions spiceai-cayenne/make-json.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/bash
# Converts the raw `result.csv` from `benchmark.sh` plus the `Load time:` /
# `Data size:` lines in `log` into the final json format used by the
# benchmark dashboard.
#
# usage: ./make-json.sh <machine> # saves results/<YYYYMMDD>/<machine>.json
set -e

MACHINE=$1
[ -n "$MACHINE" ] || { echo "usage: $0 <machine>" >&2; exit 1; }

DATE=$(date -u +%Y-%m-%d)
YYYYMMDD=${DATE//-/}
mkdir -p "results/${YYYYMMDD}"

python3 - "$MACHINE" "$DATE" > "results/${YYYYMMDD}/${MACHINE}.json" <<'PY'
import csv, json, re, sys
machine, date = sys.argv[1], sys.argv[2]
runs = {}
with open("result.csv") as f:
for query, _try, timing in csv.reader(f):
runs.setdefault(int(query), []).append(
None if timing == "null" else float(timing))
load_time = data_size = None
qps = err_ratio = None
with open("log") as f:
for line in f:
if m := re.match(r"Load time: ([\d.]+)", line):
load_time = float(m.group(1))
elif m := re.match(r"Data size: (\d+)", line):
data_size = int(m.group(1))
elif m := re.match(r"Concurrent QPS: ([\d.]+)", line):
qps = float(m.group(1))
elif m := re.match(r"Concurrent error ratio: ([\d.]+)", line):
err_ratio = float(m.group(1))
template = json.load(open("template.json"))
result = {
"system": template["system"],
"date": date,
"machine": machine,
"cluster_size": 1,
"proprietary": template["proprietary"],
"hardware": template["hardware"],
"tuned": template["tuned"],
"tags": template["tags"],
"load_time": load_time,
"data_size": data_size,
"concurrent_qps": qps,
"concurrent_error_ratio": err_ratio,
"result": [runs[q] for q in sorted(runs)],
}
print(json.dumps(result, indent=4))
PY

echo "results/${YYYYMMDD}/${MACHINE}.json"
43 changes: 43 additions & 0 deletions spiceai-cayenne/queries.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
SELECT COUNT(*) FROM hits;
SELECT COUNT(*) FROM hits WHERE "AdvEngineID" <> 0;
SELECT SUM("AdvEngineID"), COUNT(*), AVG("ResolutionWidth") FROM hits;
SELECT AVG("UserID") FROM hits;
SELECT COUNT(DISTINCT "UserID") FROM hits;
SELECT COUNT(DISTINCT "SearchPhrase") FROM hits;
SELECT MIN(to_timestamp("EventDate" * 86400)), MAX(to_timestamp("EventDate" * 86400)) FROM hits;
SELECT "AdvEngineID", COUNT(*) FROM hits WHERE "AdvEngineID" <> 0 GROUP BY "AdvEngineID" ORDER BY COUNT(*) DESC;
SELECT "RegionID", COUNT(DISTINCT "UserID") AS u FROM hits GROUP BY "RegionID" ORDER BY u DESC LIMIT 10;
SELECT "RegionID", SUM("AdvEngineID"), COUNT(*) AS c, AVG("ResolutionWidth"), COUNT(DISTINCT "UserID") FROM hits GROUP BY "RegionID" ORDER BY c DESC LIMIT 10;
SELECT "MobilePhoneModel", COUNT(DISTINCT "UserID") AS u FROM hits WHERE "MobilePhoneModel" <> '' GROUP BY "MobilePhoneModel" ORDER BY u DESC LIMIT 10;
SELECT "MobilePhone", "MobilePhoneModel", COUNT(DISTINCT "UserID") AS u FROM hits WHERE "MobilePhoneModel" <> '' GROUP BY "MobilePhone", "MobilePhoneModel" ORDER BY u DESC LIMIT 10;
SELECT "SearchPhrase", COUNT(*) AS c FROM hits WHERE "SearchPhrase" <> '' GROUP BY "SearchPhrase" ORDER BY c DESC LIMIT 10;
SELECT "SearchPhrase", COUNT(DISTINCT "UserID") AS u FROM hits WHERE "SearchPhrase" <> '' GROUP BY "SearchPhrase" ORDER BY u DESC LIMIT 10;
SELECT "SearchEngineID", "SearchPhrase", COUNT(*) AS c FROM hits WHERE "SearchPhrase" <> '' GROUP BY "SearchEngineID", "SearchPhrase" ORDER BY c DESC LIMIT 10;
SELECT "UserID", COUNT(*) FROM hits GROUP BY "UserID" ORDER BY COUNT(*) DESC LIMIT 10;
SELECT "UserID", "SearchPhrase", COUNT(*) FROM hits GROUP BY "UserID", "SearchPhrase" ORDER BY COUNT(*) DESC LIMIT 10;
SELECT "UserID", "SearchPhrase", COUNT(*) FROM hits GROUP BY "UserID", "SearchPhrase" LIMIT 10;
SELECT "UserID", extract(minute FROM to_timestamp_seconds("EventTime")) AS m, "SearchPhrase", COUNT(*) FROM hits GROUP BY "UserID", m, "SearchPhrase" ORDER BY COUNT(*) DESC LIMIT 10;
SELECT "UserID" FROM hits WHERE "UserID" = 435090932899640449;
SELECT COUNT(*) FROM hits WHERE "URL" LIKE '%google%';
SELECT "SearchPhrase", MIN("URL"), COUNT(*) AS c FROM hits WHERE "URL" LIKE '%google%' AND "SearchPhrase" <> '' GROUP BY "SearchPhrase" ORDER BY c DESC LIMIT 10;
SELECT "SearchPhrase", MIN("URL"), MIN("Title"), COUNT(*) AS c, COUNT(DISTINCT "UserID") FROM hits WHERE "Title" LIKE '%Google%' AND "URL" NOT LIKE '%.google.%' AND "SearchPhrase" <> '' GROUP BY "SearchPhrase" ORDER BY c DESC LIMIT 10;
SELECT * FROM hits WHERE "URL" LIKE '%google%' ORDER BY "EventTime" LIMIT 10;
SELECT "SearchPhrase" FROM hits WHERE "SearchPhrase" <> '' ORDER BY "EventTime" LIMIT 10;
SELECT "SearchPhrase" FROM hits WHERE "SearchPhrase" <> '' ORDER BY "SearchPhrase" LIMIT 10;
SELECT "SearchPhrase" FROM hits WHERE "SearchPhrase" <> '' ORDER BY "EventTime", "SearchPhrase" LIMIT 10;
SELECT "CounterID", AVG(length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;
SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;
SELECT SUM("ResolutionWidth"), SUM("ResolutionWidth" + 1), SUM("ResolutionWidth" + 2), SUM("ResolutionWidth" + 3), SUM("ResolutionWidth" + 4), SUM("ResolutionWidth" + 5), SUM("ResolutionWidth" + 6), SUM("ResolutionWidth" + 7), SUM("ResolutionWidth" + 8), SUM("ResolutionWidth" + 9), SUM("ResolutionWidth" + 10), SUM("ResolutionWidth" + 11), SUM("ResolutionWidth" + 12), SUM("ResolutionWidth" + 13), SUM("ResolutionWidth" + 14), SUM("ResolutionWidth" + 15), SUM("ResolutionWidth" + 16), SUM("ResolutionWidth" + 17), SUM("ResolutionWidth" + 18), SUM("ResolutionWidth" + 19), SUM("ResolutionWidth" + 20), SUM("ResolutionWidth" + 21), SUM("ResolutionWidth" + 22), SUM("ResolutionWidth" + 23), SUM("ResolutionWidth" + 24), SUM("ResolutionWidth" + 25), SUM("ResolutionWidth" + 26), SUM("ResolutionWidth" + 27), SUM("ResolutionWidth" + 28), SUM("ResolutionWidth" + 29), SUM("ResolutionWidth" + 30), SUM("ResolutionWidth" + 31), SUM("ResolutionWidth" + 32), SUM("ResolutionWidth" + 33), SUM("ResolutionWidth" + 34), SUM("ResolutionWidth" + 35), SUM("ResolutionWidth" + 36), SUM("ResolutionWidth" + 37), SUM("ResolutionWidth" + 38), SUM("ResolutionWidth" + 39), SUM("ResolutionWidth" + 40), SUM("ResolutionWidth" + 41), SUM("ResolutionWidth" + 42), SUM("ResolutionWidth" + 43), SUM("ResolutionWidth" + 44), SUM("ResolutionWidth" + 45), SUM("ResolutionWidth" + 46), SUM("ResolutionWidth" + 47), SUM("ResolutionWidth" + 48), SUM("ResolutionWidth" + 49), SUM("ResolutionWidth" + 50), SUM("ResolutionWidth" + 51), SUM("ResolutionWidth" + 52), SUM("ResolutionWidth" + 53), SUM("ResolutionWidth" + 54), SUM("ResolutionWidth" + 55), SUM("ResolutionWidth" + 56), SUM("ResolutionWidth" + 57), SUM("ResolutionWidth" + 58), SUM("ResolutionWidth" + 59), SUM("ResolutionWidth" + 60), SUM("ResolutionWidth" + 61), SUM("ResolutionWidth" + 62), SUM("ResolutionWidth" + 63), SUM("ResolutionWidth" + 64), SUM("ResolutionWidth" + 65), SUM("ResolutionWidth" + 66), SUM("ResolutionWidth" + 67), SUM("ResolutionWidth" + 68), SUM("ResolutionWidth" + 69), SUM("ResolutionWidth" + 70), SUM("ResolutionWidth" + 71), SUM("ResolutionWidth" + 72), SUM("ResolutionWidth" + 73), SUM("ResolutionWidth" + 74), SUM("ResolutionWidth" + 75), SUM("ResolutionWidth" + 76), SUM("ResolutionWidth" + 77), SUM("ResolutionWidth" + 78), SUM("ResolutionWidth" + 79), SUM("ResolutionWidth" + 80), SUM("ResolutionWidth" + 81), SUM("ResolutionWidth" + 82), SUM("ResolutionWidth" + 83), SUM("ResolutionWidth" + 84), SUM("ResolutionWidth" + 85), SUM("ResolutionWidth" + 86), SUM("ResolutionWidth" + 87), SUM("ResolutionWidth" + 88), SUM("ResolutionWidth" + 89) FROM hits;
SELECT "SearchEngineID", "ClientIP", COUNT(*) AS c, SUM("IsRefresh"), AVG("ResolutionWidth") FROM hits WHERE "SearchPhrase" <> '' GROUP BY "SearchEngineID", "ClientIP" ORDER BY c DESC LIMIT 10;
SELECT "WatchID", "ClientIP", COUNT(*) AS c, SUM("IsRefresh"), AVG("ResolutionWidth") FROM hits WHERE "SearchPhrase" <> '' GROUP BY "WatchID", "ClientIP" ORDER BY c DESC LIMIT 10;
SELECT "WatchID", "ClientIP", COUNT(*) AS c, SUM("IsRefresh"), AVG("ResolutionWidth") FROM hits GROUP BY "WatchID", "ClientIP" ORDER BY c DESC LIMIT 10;
SELECT "URL", COUNT(*) AS c FROM hits GROUP BY "URL" ORDER BY c DESC LIMIT 10;
SELECT 1, "URL", COUNT(*) AS c FROM hits GROUP BY 1, "URL" ORDER BY c DESC LIMIT 10;
SELECT "ClientIP", "ClientIP" - 1, "ClientIP" - 2, "ClientIP" - 3, COUNT(*) AS c FROM hits GROUP BY "ClientIP", "ClientIP" - 1, "ClientIP" - 2, "ClientIP" - 3 ORDER BY c DESC LIMIT 10;
SELECT "URL", COUNT(*) AS PageViews FROM hits WHERE "CounterID" = 62 AND to_timestamp("EventDate" * 86400) >= '2013-07-01' AND to_timestamp("EventDate" * 86400) <= '2013-07-31' AND "DontCountHits" = 0 AND "IsRefresh" = 0 AND "URL" <> '' GROUP BY "URL" ORDER BY PageViews DESC LIMIT 10;
SELECT "Title", COUNT(*) AS PageViews FROM hits WHERE "CounterID" = 62 AND to_timestamp("EventDate" * 86400) >= '2013-07-01' AND to_timestamp("EventDate" * 86400) <= '2013-07-31' AND "DontCountHits" = 0 AND "IsRefresh" = 0 AND "Title" <> '' GROUP BY "Title" ORDER BY PageViews DESC LIMIT 10;
SELECT "URL", COUNT(*) AS PageViews FROM hits WHERE "CounterID" = 62 AND to_timestamp("EventDate" * 86400) >= '2013-07-01' AND to_timestamp("EventDate" * 86400) <= '2013-07-31' AND "IsRefresh" = 0 AND "IsLink" <> 0 AND "IsDownload" = 0 GROUP BY "URL" ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;
SELECT "TraficSourceID", "SearchEngineID", "AdvEngineID", CASE WHEN ("SearchEngineID" = 0 AND "AdvEngineID" = 0) THEN "Referer" ELSE '' END AS Src, "URL" AS Dst, COUNT(*) AS PageViews FROM hits WHERE "CounterID" = 62 AND to_timestamp("EventDate" * 86400) >= '2013-07-01' AND to_timestamp("EventDate" * 86400) <= '2013-07-31' AND "IsRefresh" = 0 GROUP BY "TraficSourceID", "SearchEngineID", "AdvEngineID", Src, Dst ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;
SELECT "URLHash", to_timestamp("EventDate" * 86400), COUNT(*) AS PageViews FROM hits WHERE "CounterID" = 62 AND to_timestamp("EventDate" * 86400) >= '2013-07-01' AND to_timestamp("EventDate" * 86400) <= '2013-07-31' AND "IsRefresh" = 0 AND "TraficSourceID" IN (-1, 6) AND "RefererHash" = 3594120000172545465 GROUP BY "URLHash", to_timestamp("EventDate" * 86400) ORDER BY PageViews DESC LIMIT 10 OFFSET 100;
SELECT "WindowClientWidth", "WindowClientHeight", COUNT(*) AS PageViews FROM hits WHERE "CounterID" = 62 AND to_timestamp("EventDate" * 86400) >= '2013-07-01' AND to_timestamp("EventDate" * 86400) <= '2013-07-31' AND "IsRefresh" = 0 AND "DontCountHits" = 0 AND "URLHash" = 2868770270353813622 GROUP BY "WindowClientWidth", "WindowClientHeight" ORDER BY PageViews DESC LIMIT 10 OFFSET 10000;
SELECT DATE_TRUNC('minute', to_timestamp("EventTime")::timestamp) AS M, COUNT(*) AS PageViews FROM hits WHERE "CounterID" = 62 AND to_timestamp("EventDate" * 86400) >= '2013-07-14' AND to_timestamp("EventDate" * 86400) <= '2013-07-15' AND "IsRefresh" = 0 AND "DontCountHits" = 0 GROUP BY DATE_TRUNC('minute', to_timestamp("EventTime")::timestamp) ORDER BY DATE_TRUNC('minute', M) LIMIT 10 OFFSET 1000;
24 changes: 24 additions & 0 deletions spiceai-cayenne/query
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# Reads a SQL query from stdin, runs it via the Spice HTTP API.
# Stdout: query result (JSON).
# Stderr: query runtime in fractional seconds on the last line.
# Exit non-zero on error.
set -e

resp=$(mktemp /tmp/spiceai-query.XXXXXX)
trap 'rm -f "$resp"' EXIT

metrics=$(curl -sS -o "$resp" -w '%{http_code} %{time_total}' \
-H 'Content-Type: text/plain' --data-binary @- \
'http://localhost:8090/v1/sql')
code=${metrics% *}
time_total=${metrics#* }

if [ "$code" != "200" ]; then
cat "$resp" >&2
echo >&2
exit 1
fi

cat "$resp"
printf '%s\n' "$time_total" >&2
Loading