Skip to content

Commit a01774f

Browse files
andygroveclaude
andauthored
chore: Add Docker Compose support for TPC benchmarks (apache#3576)
* Add Docker Compose support for TPC benchmarks Add Docker Compose setup for running TPC-H/TPC-DS benchmarks in an isolated Spark standalone cluster with two workers. Bundle TPC query SQL files in the repository, removing the need for external TPCH_QUERIES/TPCDS_QUERIES environment variables. Add Dockerfile.build-comet for cross-compiling Comet JARs with Linux native libraries on macOS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add lightweight Docker Compose file for laptop benchmarking Add docker-compose-laptop.yml with a single worker (~12 GB total) for SF1-SF10 testing on laptops, replacing the --scale/env var workaround. Update README to document both compose files side by side. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * revert a docs change * Fix undefined variable warning in Dockerfile.build-comet Remove self-reference to LD_LIBRARY_PATH since it is not defined earlier in the Dockerfile, causing a Docker build warning. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix * prettier * fix * prettier --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8a26dc3 commit a01774f

8 files changed

Lines changed: 550 additions & 20 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ filtered_rat.txt
1717
dev/dist
1818
apache-rat-*.jar
1919
venv
20+
.venv
2021
dev/release/comet-rm/workdir
2122
spark/benchmarks
2223
.DS_Store
2324
comet-event-trace.json
2425
__pycache__
26+
output

benchmarks/tpc/README.md

Lines changed: 173 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ For full instructions on running these benchmarks on an EC2 instance, see the [C
2626

2727
[Comet Benchmarking on EC2 Guide]: https://datafusion.apache.org/comet/contributor-guide/benchmarking_aws_ec2.html
2828

29+
## Setup
30+
31+
TPC queries are bundled in `benchmarks/tpc/queries/` (derived from TPC-H/DS under the TPC Fair Use Policy).
32+
2933
## Usage
3034

3135
All benchmarks are run via `run.py`:
@@ -55,10 +59,9 @@ export SPARK_HOME=/opt/spark-3.5.3-bin-hadoop3/
5559
export SPARK_MASTER=spark://yourhostname:7077
5660
```
5761

58-
Set path to queries and data:
62+
Set path to data (TPC queries are bundled in `benchmarks/tpc/queries/`):
5963

6064
```shell
61-
export TPCH_QUERIES=/mnt/bigdata/tpch/queries/
6265
export TPCH_DATA=/mnt/bigdata/tpch/sf100/
6366
```
6467

@@ -135,9 +138,9 @@ $SPARK_HOME/bin/spark-submit \
135138
--master $SPARK_MASTER \
136139
--packages org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:1.8.1 \
137140
--conf spark.driver.memory=8G \
138-
--conf spark.executor.instances=1 \
141+
--conf spark.executor.instances=2 \
139142
--conf spark.executor.cores=8 \
140-
--conf spark.cores.max=8 \
143+
--conf spark.cores.max=16 \
141144
--conf spark.executor.memory=16g \
142145
create-iceberg-tables.py \
143146
--benchmark tpch \
@@ -166,7 +169,6 @@ export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
166169
export COMET_JAR=/opt/comet/comet-spark-spark3.5_2.12-0.10.0.jar
167170
export ICEBERG_JAR=/path/to/iceberg-spark-runtime-3.5_2.12-1.8.1.jar
168171
export ICEBERG_WAREHOUSE=/mnt/bigdata/iceberg-warehouse
169-
export TPCH_QUERIES=/mnt/bigdata/tpch/queries/
170172
sudo ./drop-caches.sh
171173
python3 run.py --engine comet-iceberg --benchmark tpch
172174
```
@@ -185,6 +187,172 @@ physical plan output.
185187
| `--catalog` | No | `local` | Iceberg catalog name |
186188
| `--database` | No | benchmark name | Database name for the tables |
187189

190+
## Running with Docker
191+
192+
A Docker Compose setup is provided in `infra/docker/` for running benchmarks in an isolated
193+
Spark standalone cluster. The Docker image supports both **Linux (amd64)** and **macOS (arm64)**
194+
via architecture-agnostic Java symlinks created at build time.
195+
196+
### Build the image
197+
198+
The image must be built for the correct platform to match the native libraries in the
199+
engine JARs (e.g. Comet bundles `libcomet.so` for a specific OS/arch).
200+
201+
```shell
202+
docker build -t comet-bench -f benchmarks/tpc/infra/docker/Dockerfile .
203+
```
204+
205+
### Building a compatible Comet JAR
206+
207+
The Comet JAR contains platform-specific native libraries (`libcomet.so` / `libcomet.dylib`).
208+
A JAR built on the host may not work inside the Docker container due to OS, architecture,
209+
or glibc version mismatches. Use `Dockerfile.build-comet` to build a JAR with compatible
210+
native libraries:
211+
212+
- **macOS (Apple Silicon):** The host JAR contains `darwin/aarch64` libraries which
213+
won't work in Linux containers. You **must** use the build Dockerfile.
214+
- **Linux:** If your host glibc version differs from the container's, the native library
215+
will fail to load with a `GLIBC_x.xx not found` error. The build Dockerfile uses
216+
Ubuntu 20.04 (glibc 2.31) for broad compatibility. Use it if you see
217+
`UnsatisfiedLinkError` mentioning glibc when running benchmarks.
218+
219+
```shell
220+
mkdir -p output
221+
docker build -t comet-builder \
222+
-f benchmarks/tpc/infra/docker/Dockerfile.build-comet .
223+
docker run --rm -v $(pwd)/output:/output comet-builder
224+
export COMET_JAR=$(pwd)/output/comet-spark-spark3.5_2.12-*.jar
225+
```
226+
227+
### Platform notes
228+
229+
**macOS (Apple Silicon):** Docker Desktop is required.
230+
231+
- **Memory:** Docker Desktop defaults to a small memory allocation (often 8 GB) which
232+
is not enough for Spark benchmarks. Go to **Docker Desktop > Settings > Resources >
233+
Memory** and increase it to at least 48 GB (each worker requests 16 GB for its executor
234+
plus overhead, and the driver needs 8 GB). Without enough memory, executors will be
235+
OOM-killed (exit code 137).
236+
- **File Sharing:** You may need to add your data directory (e.g. `/opt`) to
237+
**Docker Desktop > Settings > Resources > File Sharing** before mounting host volumes.
238+
239+
**Linux (amd64):** Docker uses cgroup memory limits directly without a VM layer. No
240+
special Docker configuration is needed, but you may still need to build the Comet JAR
241+
using `Dockerfile.build-comet` (see above) if your host glibc version doesn't match
242+
the container's.
243+
244+
The Docker image auto-detects the container architecture (amd64/arm64) and sets up
245+
arch-agnostic Java symlinks. The compose file uses `BENCH_JAVA_HOME` (not `JAVA_HOME`)
246+
to avoid inheriting the host's Java path into the container.
247+
248+
### Start the cluster
249+
250+
Set environment variables pointing to your host paths, then start the Spark master and
251+
two workers:
252+
253+
```shell
254+
export DATA_DIR=/mnt/bigdata/tpch/sf100
255+
export RESULTS_DIR=/tmp/bench-results
256+
export COMET_JAR=/opt/comet/comet-spark-spark3.5_2.12-0.10.0.jar
257+
258+
mkdir -p $RESULTS_DIR/spark-events
259+
docker compose -f benchmarks/tpc/infra/docker/docker-compose.yml up -d
260+
```
261+
262+
Set `COMET_JAR`, `GLUTEN_JAR`, or `ICEBERG_JAR` to the host path of the engine JAR you
263+
want to use. Each JAR is mounted individually into the container, so you can easily switch
264+
between versions by changing the path and restarting.
265+
266+
### Run benchmarks
267+
268+
Use `docker compose run --rm` to execute benchmarks. The `--rm` flag removes the
269+
container when it exits, preventing port conflicts on subsequent runs. Pass
270+
`--no-restart` since the cluster is already managed by Compose, and `--output /results`
271+
so that output files land in the mounted results directory:
272+
273+
```shell
274+
docker compose -f benchmarks/tpc/infra/docker/docker-compose.yml \
275+
run --rm -p 4040:4040 bench \
276+
python3 /opt/benchmarks/run.py \
277+
--engine comet --benchmark tpch --output /results --no-restart
278+
```
279+
280+
The `-p 4040:4040` flag exposes the Spark Application UI on the host. The following
281+
UIs are available during a benchmark run:
282+
283+
| UI | URL |
284+
| ----------------- | ---------------------- |
285+
| Spark Master | http://localhost:8080 |
286+
| Worker 1 | http://localhost:8081 |
287+
| Worker 2 | http://localhost:8082 |
288+
| Spark Application | http://localhost:4040 |
289+
| History Server | http://localhost:18080 |
290+
291+
> **Note:** The Master UI links to the Application UI using the container's internal
292+
> hostname, which is not reachable from the host. Use `http://localhost:4040` directly
293+
> to access the Application UI.
294+
295+
The Spark Application UI is only available while a benchmark is running. To inspect
296+
completed runs, uncomment the `history-server` service in `docker-compose.yml` and
297+
restart the cluster. The History Server reads event logs from `$RESULTS_DIR/spark-events`.
298+
299+
For Gluten (requires Java 8), you must restart the **entire cluster** with `JAVA_HOME`
300+
set so that all services (master, workers, and bench) use Java 8:
301+
302+
```shell
303+
export BENCH_JAVA_HOME=/usr/lib/jvm/java-8-openjdk
304+
docker compose -f benchmarks/tpc/infra/docker/docker-compose.yml down
305+
docker compose -f benchmarks/tpc/infra/docker/docker-compose.yml up -d
306+
307+
docker compose -f benchmarks/tpc/infra/docker/docker-compose.yml \
308+
run --rm bench \
309+
python3 /opt/benchmarks/run.py \
310+
--engine gluten --benchmark tpch --output /results --no-restart
311+
```
312+
313+
> **Important:** Only passing `-e JAVA_HOME=...` to the `bench` container is not
314+
> sufficient -- the workers also need Java 8 or Gluten will fail at runtime with
315+
> `sun.misc.Unsafe` errors. Unset `BENCH_JAVA_HOME` (or switch it back to Java 17)
316+
> and restart the cluster before running Comet or Spark benchmarks.
317+
318+
### Memory limits
319+
320+
Two compose files are provided for different hardware profiles:
321+
322+
| File | Workers | Total memory | Use case |
323+
| --------------------------- | ------- | ------------ | ------------------------------ |
324+
| `docker-compose.yml` | 2 | ~74 GB | SF100+ on a workstation/server |
325+
| `docker-compose-laptop.yml` | 1 | ~12 GB | SF1–SF10 on a laptop |
326+
327+
**`docker-compose.yml`** (workstation default):
328+
329+
| Container | Container limit (`mem_limit`) | Spark JVM allocation |
330+
| -------------- | ----------------------------- | ------------------------- |
331+
| spark-worker-1 | 32 GB | 16 GB executor + overhead |
332+
| spark-worker-2 | 32 GB | 16 GB executor + overhead |
333+
| bench (driver) | 10 GB | 8 GB driver |
334+
| **Total** | **74 GB** | |
335+
336+
Configure via environment variables: `WORKER_MEM_LIMIT` (default: 32g per worker),
337+
`BENCH_MEM_LIMIT` (default: 10g), `WORKER_MEMORY` (default: 16g, Spark executor memory),
338+
`WORKER_CORES` (default: 8).
339+
340+
### Running on a laptop with small scale factors
341+
342+
For local development or testing with small scale factors (e.g. SF1 or SF10), use the
343+
laptop compose file which runs a single worker with reduced memory:
344+
345+
```shell
346+
docker compose -f benchmarks/tpc/infra/docker/docker-compose-laptop.yml up -d
347+
```
348+
349+
This starts one worker (4 GB executor inside an 8 GB container) and a 4 GB bench
350+
container, totaling approximately **12 GB** of memory.
351+
352+
The benchmark scripts request 2 executor instances and 16 max cores by default
353+
(`run.py`). Spark will simply use whatever resources are available on the single worker,
354+
so no script changes are needed.
355+
188356
### Comparing Parquet vs Iceberg performance
189357

190358
Run both benchmarks and compare:
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# Benchmark image for running TPC-H and TPC-DS benchmarks across engines
17+
# (Spark, Comet, Gluten).
18+
#
19+
# Build (from repository root):
20+
# docker build -t comet-bench -f benchmarks/tpc/infra/docker/Dockerfile .
21+
22+
ARG SPARK_IMAGE=apache/spark:3.5.2-python3
23+
FROM ${SPARK_IMAGE}
24+
25+
USER root
26+
27+
# Install Java 8 (Gluten) and Java 17 (Comet) plus Python 3.
28+
RUN apt-get update \
29+
&& apt-get install -y --no-install-recommends \
30+
openjdk-8-jdk-headless \
31+
openjdk-17-jdk-headless \
32+
python3 python3-pip procps \
33+
&& apt-get clean \
34+
&& rm -rf /var/lib/apt/lists/*
35+
36+
# Default to Java 17 (override with JAVA_HOME at runtime for Gluten).
37+
# Detect architecture (amd64 or arm64) so the image works on both Linux and macOS.
38+
ARG TARGETARCH
39+
RUN ln -s /usr/lib/jvm/java-17-openjdk-${TARGETARCH} /usr/lib/jvm/java-17-openjdk && \
40+
ln -s /usr/lib/jvm/java-8-openjdk-${TARGETARCH} /usr/lib/jvm/java-8-openjdk
41+
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk
42+
43+
# Copy the benchmark scripts into the image.
44+
COPY benchmarks/tpc/run.py /opt/benchmarks/run.py
45+
COPY benchmarks/tpc/tpcbench.py /opt/benchmarks/tpcbench.py
46+
COPY benchmarks/tpc/engines /opt/benchmarks/engines
47+
COPY benchmarks/tpc/queries /opt/benchmarks/queries
48+
COPY benchmarks/tpc/create-iceberg-tables.py /opt/benchmarks/create-iceberg-tables.py
49+
COPY benchmarks/tpc/generate-comparison.py /opt/benchmarks/generate-comparison.py
50+
51+
# Engine JARs are bind-mounted or copied in at runtime via --jars.
52+
# Data and query paths are also bind-mounted.
53+
54+
WORKDIR /opt/benchmarks
55+
56+
# Defined in the base apache/spark image.
57+
ARG spark_uid
58+
USER ${spark_uid}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# Build a Comet JAR with native libraries for the current platform.
17+
#
18+
# This is useful on macOS (Apple Silicon) where the host-built JAR contains
19+
# darwin/aarch64 native libraries but Docker containers need linux/aarch64.
20+
#
21+
# Usage (from repository root):
22+
# docker build -t comet-builder -f benchmarks/tpc/infra/docker/Dockerfile.build-comet .
23+
# docker run --rm -v $(pwd)/output:/output comet-builder
24+
#
25+
# The JAR is copied to ./output/ on the host.
26+
27+
# Use Ubuntu 20.04 to match the GLIBC version (2.31) in apache/spark images.
28+
FROM ubuntu:20.04 AS builder
29+
30+
ARG TARGETARCH
31+
ENV DEBIAN_FRONTEND=noninteractive
32+
33+
# Install build dependencies: Java 17, Maven wrapper prerequisites, GCC 11.
34+
# Ubuntu 20.04's default GCC 9 has a memcmp bug (GCC #95189) that breaks aws-lc-sys.
35+
RUN apt-get update && apt-get install -y --no-install-recommends \
36+
openjdk-17-jdk-headless \
37+
curl ca-certificates git pkg-config \
38+
libssl-dev unzip software-properties-common \
39+
&& add-apt-repository -y ppa:ubuntu-toolchain-r/test \
40+
&& apt-get update \
41+
&& apt-get install -y --no-install-recommends gcc-11 g++-11 make \
42+
&& update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110 \
43+
&& update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 110 \
44+
&& update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-11 110 \
45+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
46+
47+
# Install protoc 25.x (Ubuntu 22.04's protoc is too old for proto3 optional fields).
48+
ARG PROTOC_VERSION=25.6
49+
RUN ARCH=$(uname -m) && \
50+
if [ "$ARCH" = "aarch64" ]; then PROTOC_ARCH="linux-aarch_64"; \
51+
else PROTOC_ARCH="linux-x86_64"; fi && \
52+
curl -sLO "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${PROTOC_ARCH}.zip" && \
53+
unzip -o "protoc-${PROTOC_VERSION}-${PROTOC_ARCH}.zip" -d /usr/local bin/protoc && \
54+
rm "protoc-${PROTOC_VERSION}-${PROTOC_ARCH}.zip" && \
55+
protoc --version
56+
57+
# Set JAVA_HOME and LD_LIBRARY_PATH so the Rust build can find libjvm.
58+
RUN ln -s /usr/lib/jvm/java-17-openjdk-${TARGETARCH} /usr/lib/jvm/java-17-openjdk
59+
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk
60+
ENV LD_LIBRARY_PATH=${JAVA_HOME}/lib/server
61+
62+
# Install Rust.
63+
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
64+
ENV PATH="/root/.cargo/bin:${PATH}"
65+
66+
WORKDIR /build
67+
68+
# Copy the full source tree.
69+
COPY . .
70+
71+
# Build native code + package the JAR (skip tests).
72+
RUN make release-nogit
73+
74+
# The entrypoint copies the built JAR to /output (bind-mounted from host).
75+
RUN mkdir -p /output
76+
CMD ["sh", "-c", "cp spark/target/comet-spark-spark3.5_2.12-*-SNAPSHOT.jar /output/ && echo 'Comet JAR copied to /output/' && ls -lh /output/*.jar"]

0 commit comments

Comments
 (0)