This repository contains automated HammerDB benchmark scripts for running TPC-C (TPROC-C) and TPC-H (TPROC-H) workloads against Microsoft SQL Server using Docker containers.
Full blog post here: https://www.nocentino.com/posts/2026-09-12-hammerdb-containers/
Currently built and tested against HammerDB 6.0 and SQL Server 2025 CU8.
HammerDB 6.0 update
- JSON reports and charts for every run. The parse phase writes HammerDB's own report plus a compact one, and HTML charts, instead of only printing to the console. See JSON Reports and Charts.
- Compare two configurations directly. Tag runs with
PROFILE_IDand diff them withRUN_MODE=compare, or use./compare.shto run both across a range of virtual user counts. See Comparing Runs. - CPU, I/O, and storage detail.
METRICS_ENABLED=truerecords the database host's hardware and software alongside the result. See CPU and I/O Metrics. - Richer response times. The xtprof profiler now reports p99/p95/p75/p50/p25 per stored procedure, not just min, average, and max.
- TPC-H gets the same reporting as TPC-C, alongside its original text report.
- CI on every push and pull request. A smoke test builds the image and runs build, load, parse, and a comparison against a real SQL Server.
If you are upgrading from HammerDB 5.0, read
Upgrading from HammerDB 5.0 first. An existing
output/hammer.DB will make runs fail after printing a plausible result.
Fixed along the way
loadtest.shreads its credentials and port from the env file rather than duplicating them, and only manages a local container whenSQL_SERVER_HOSTpoints at localhost.jobs <jobid> saveis broken in the shipped HammerDB 6.0 binary; scripts/hammerdb6_compat.tcl restores it.- A failed build or load no longer looks like a success. Bare
docker compose upreturns 0 whatever the container did, soloadtest.shnow takes its exit code from the service, validates that the report holds a result, and exits non-zero when a run produces nothing. Everydocker compose upin this README carries--abort-on-container-exitfor the same reason. - The parse phase no longer announces a native report that
jobs savefailed to write, which it did by printing its errors instead of raising them. - The SA password no longer reaches the logs. HammerDB echoes every value passed to
disetand the build scripts calledprint dict, so it appeared in every run log and in any CI artifact built from one. It is still stored inhammer.DBby HammerDB itself, so share the JSON reports and charts rather than all ofoutput/. - Cleanup runs on every exit path. A leaked SQL Server volume used to make later runs
fail their readiness probe for minutes with no stated cause, because SQL Server only
applies
MSSQL_SA_PASSWORDto an empty data directory. - Results are identified by the run's job id rather than by file timestamp, so a parse that wrote nothing can no longer let an earlier report stand in as this run's.
- The parse scripts escape what they put in JSON, detect both forms of HammerDB's no-data message, and exit non-zero when no result was parsed.
MSSQLS_TCPandMSSQLS_AUTHENTICATIONwere documented and read by nothing. They are honored now. The metrics agent reads the env file, soMETRICS_AGENT_IDreaches it. Comparing a profile against itself is refused instead of reported as no change.test.shis nowcompare.sh. The old name read like a test suite.- The README documented
TMPDIRwhile the scripts readTMP, anddocker composefailed on a fresh clone without a local.env.
The scripts provide a streamlined way to:
- Automatically set up SQL Server 2025 container
- Build TPC-C and TPC-H schemas
- Run benchmark tests with configurable parameters
- Extract and format test results
All configuration is managed through environment variables, making it easy to adjust parameters without modifying the scripts.
- Docker and Docker Compose installed
- Sufficient disk space for SQL Server containers and test databases
sqlcmdon your PATH, used byloadtest.shto wait for the local SQL Server container. Not needed when running the phases through Docker Compose yourself, or when targeting a remote server.
hammerdb/
├── hammerdb.env # Environment configuration file (not committed)
├── hammerdb.env.example # Template to copy to hammerdb.env
├── docker-compose.yaml # Docker Compose configuration
├── dockerfile # HammerDB 6.0 + mssql-tools18 image
├── entrypoint.sh # Dispatches to a Tcl script by RUN_MODE + BENCHMARK
├── loadtest.sh # Main execution script (TPC-C end to end)
├── compare.sh # Compare two configurations with jobs diff
├── cleanup.sql # Ad-hoc drop/backup/restore helpers
├── scripts/
│ ├── build_schema_tprocc.tcl # Build TPC-C schema
│ ├── build_schema_tproch.tcl # Build TPC-H schema
│ ├── load_test_tprocc.tcl # Run TPC-C benchmark
│ ├── load_test_tproch.tcl # Run TPC-H benchmark
│ ├── parse_output_tprocc.tcl # Extract TPC-C results, JSON report, charts
│ ├── parse_output_tproch.tcl # Extract TPC-H results
│ ├── compare_profiles.tcl # Compare two TPC-C performance profiles
│ └── hammerdb6_compat.tcl # Restores the broken 6.0 "jobs save" command
├── output/ # Test results directory (mounted as /tmp in container)
└── README.md # This file
The output/ directory is mounted into the container as /tmp. It holds HammerDB's
job repository (hammer.DB), the time profiler log (hdbxtprofile.log), and BCP
intermediate CSVs from schema builds. It is gitignored and safe to delete between
runs — everything in it is regenerated, though deleting hammer.DB also discards
the history of previous benchmark jobs.
Everything below runs in about five minutes on a laptop and needs nothing installed except Docker.
1. Clone and configure
git clone https://github.com/nocentino/hammerdb.git
cd hammerdb
cp hammerdb.env.example hammerdb.envThe example file is deliberately tiny — one warehouse, one virtual user, a one minute test. It is meant to prove the plumbing works, not to produce a real number.
2. Run the whole thing
./loadtest.shThis starts a SQL Server 2025 container on port 4001, builds the TPC-C schema, runs the timed test, parses the results, and tears the container down. TPC-H is not part of this script — see Run HammerDB Tests with Docker Compose.
3. Read the results
The run ends with your headline number:
TEST RESULT : System achieved 26018 NOPM from 60408 SQL Server TPM
and leaves these in output/:
| File | What it is |
|---|---|
hdb_<jobid>.json |
HammerDB's own report: config, result, response times, metrics |
tprocc_<jobid>.json |
Compact report: result, transaction counts, percentiles, system data |
tprocc_<jobid>_result.html |
NOPM/TPM chart — open it in a browser |
tprocc_<jobid>_timing.html |
Response time distribution |
tprocc_<jobid>_tcount.html |
Transactions over the run |
If a phase fails, loadtest.sh stops there, names the likely cause, tears down, and
exits non-zero. It also checks that the report actually contains a result before
pointing you at it, so a run that produced nothing tells you so instead of handing
you a file full of null.
4. Point it at a real server
Edit hammerdb.env and change SQL_SERVER_HOST to any SQL Server on your network,
then size the workload to the hardware using one of the
recommended configurations.
loadtest.sh reads its connection settings from that file, so it only starts and
removes a local SQL Server container when SQL_SERVER_HOST points at localhost.
Against any other host it runs the benchmark directly and leaves the server alone.
You can also drive the phases yourself:
RUN_MODE=build BENCHMARK=tprocc docker compose up --abort-on-container-exit # once per schema size
RUN_MODE=load BENCHMARK=tprocc docker compose up --abort-on-container-exit # repeat as you tune
docker compose run --rm --no-TTY -e RUN_MODE=parse -e BENCHMARK=tprocc hammerdbBuilding the schema is a one time cost per WAREHOUSES value — once it exists, loop
on load and parse while you tune.
Where to go next
- Comparing Runs — tag runs with
PROFILE_IDand diff two configurations - CPU and I/O Metrics — capture CPU, I/O, and storage detail alongside the result
- Configuration — every environment variable
- Upgrading from HammerDB 5.0 — read this first if you have an existing
output/hammer.DB
This environment consists of two main components: a 2025 test container, and a containerized HammerDB implementation. The commands below drive each phase individually, which is what you want once you are past the Quick Start and are iterating on a configuration. You can modify hammerdb.env to target any SQL Server instance on your network by changing the SQL_SERVER_HOST environment variable and execute load tests against production or staging systems. Be sure to adjust the configuration parameters as documented in the Configuration section below.
SQL Server 2025 CU8 on port 4001
docker run \
--env 'ACCEPT_EULA=Y' \
--env 'MSSQL_SA_PASSWORD=S0methingS@Str0ng!' \
--name 'sql_2025' \
--volume sqldata_2025:/var/opt/mssql \
--publish 4001:1433 \
--platform=linux/amd64 \
--detach mcr.microsoft.com/mssql/server:2025-CU8-ubuntu-24.04
The HammerDB test execution is orchestrated through Docker Compose using environment variables to control the test mode and benchmark type. Each benchmark follows a three-phase process: schema building, load testing, and results parsing. The RUN_MODE variable determines which phase to execute (build, load, or parse), while the BENCHMARK variable specifies whether to run TPC-C (tprocc) or TPC-H (tproch) workloads. This modular approach allows you to run specific test phases independently or chain them together for complete benchmark execution and testing multiple configurations iteratively.
Note: Schema building is a one-time operation per benchmark configuration and test size dimension. Once built, you can execute multiple load tests and parse results without rebuilding the schema, making iterative testing and configuration tuning more efficient.
# TPC-C Schema Build
RUN_MODE=build BENCHMARK=tprocc docker compose up --abort-on-container-exit
# TPC-C Load Test
RUN_MODE=load BENCHMARK=tprocc docker compose up --abort-on-container-exit
# TPC-C Results Parsing (use --no-TTY flag if output is getting truncated)
docker compose run --rm --no-TTY -e RUN_MODE=parse -e BENCHMARK=tprocc hammerdb
# TPC-H Schema Build
RUN_MODE=build BENCHMARK=tproch docker compose up --abort-on-container-exit
# TPC-H Load Test
RUN_MODE=load BENCHMARK=tproch docker compose up --abort-on-container-exit
# TPC-H Results Parsing
docker compose run --rm --no-TTY -e RUN_MODE=parse -e BENCHMARK=tproch hammerdb
# Compare two tagged TPC-C profiles, see "Comparing Runs" below
docker compose run --rm --no-TTY -e RUN_MODE=compare -e BENCHMARK=tprocc \
-e BASE_PROFILE_ID=1 -e COMP_PROFILE_ID=2 hammerdbNote: The Tcl scripts in
scripts/are volume-mounted, so edits take effect immediately.entrypoint.shis copied into the image, so changing it (for example to add aRUN_MODE) requiresdocker compose build.
Tip: If you're experiencing truncated output during the parse phase, use the
--no-TTYflag to disable pseudo-TTY allocation, which provides raw unbuffered output.
These commands read hammerdb.env by default. To keep several configurations side by
side and switch between them, set HAMMERDB_ENV_FILE:
HAMMERDB_ENV_FILE=hammerdb-2022.env RUN_MODE=load BENCHMARK=tprocc docker compose up --abort-on-container-exit
HAMMERDB_ENV_FILE=hammerdb-2025.env RUN_MODE=load BENCHMARK=tprocc docker compose up --abort-on-container-exitBefore running a real benchmark, confirm the whole pipeline works end to end. Use the minimal configuration from The smallest test you can run, which builds a single warehouse and runs for one minute.
# 1. Start a local SQL Server to test against
docker run \
--env 'ACCEPT_EULA=Y' \
--env 'MSSQL_SA_PASSWORD=S0methingS@Str0ng!' \
--name 'sql2025' \
--publish 4001:1433 \
--platform=linux/amd64 \
--detach mcr.microsoft.com/mssql/server:2025-CU8-ubuntu-24.04
# 2. Build the HammerDB image
docker compose build
# 3. Run all three phases
RUN_MODE=build BENCHMARK=tprocc docker compose up --abort-on-container-exit
RUN_MODE=load BENCHMARK=tprocc docker compose up --abort-on-container-exit
docker compose run --rm --no-TTY -e RUN_MODE=parse -e BENCHMARK=tprocc hammerdb
# 4. Clean up
docker compose down && docker rm -f sql2025What a healthy run looks like:
- build ends with
TPCC SCHEMA COMPLETE,FINISHED SUCCESS, andTPROC-C SCHEMA BUILD COMPLETE - load ends with a
TEST RESULT : System achieved <N> NOPM from <N> SQL Server TPMline, every virtual user reportingFINISHED SUCCESS, and a job ID written tooutput/mssqls_tprocc - parse prints
TRANSACTION RESPONSE TIMES,TRANSACTION COUNT, andHAMMERDB RESULTas JSON
Watch for a virtual user reporting FINISHED FAILED after a plausible TEST RESULT
line — the benchmark numbers can look fine while result recording failed. See
Upgrading from HammerDB 5.0 for the most common cause.
Note: On Apple Silicon both images run under emulation (
linux/amd64), so throughput numbers from a smoke test on a Mac are not meaningful for comparison — you are only checking that the plumbing works.
.github/workflows/smoke-test.yml runs this same
smoke test on every push and pull request, against a real SQL Server 2025 CU8. GitHub's
Linux runners are x86_64, so it runs natively rather than under emulation.
It builds the image, checks the HammerDB layout, then runs build, load, parse, and a
profile comparison, failing if any virtual user reports FINISHED FAILED, if the parse
phase emits a warning, or if the JSON report and charts are not produced. The reports
and charts are uploaded as a build artifact.
All configuration is managed through the hammerdb.env file. Below are the expose configration environment variables.
Note: Set these in the env file rather than in the Docker Compose
environment:list. Compose passes a variable listed there as an empty value when it is unset in your shell, which silently overrides the env file. To change one for a single run, usedocker compose run -e VAR=value ...instead.
USERNAME: SQL Server username (default: sa)PASSWORD: SQL Server password (default: S0methingS@Str0ng!)SQL_SERVER_HOST: SQL Server host and port (default: localhost,4001)
USE_BCP: Enable BCP for faster data loading (true/false)TMP: Directory for temporary files and output (default: /tmp)MSSQLS_TCP: Use TCP connection (default: true)MSSQLS_AUTHENTICATION: Authentication type (default: sql)
Schema Build Settings:
TPROCC_DATABASE_NAME: Database name for TPC-C (default: tpcc)TPROCC_DRIVER: Database driver (default: mssqls)TPROCC_BUILD_VIRTUAL_USERS: Virtual users for schema buildWAREHOUSES: Number of warehousesTPROCC_DRIVER_TYPE: Driver type (timed/test). Read at schema build time only; the load phase always runs the timed driverTPROCC_ALLWAREHOUSE: Use all warehouses in test (true/false)
Test Settings:
VIRTUAL_USERS: Virtual users for test executionRAMPUP: Ramp-up time in minutesDURATION: Test duration in minutesTOTAL_ITERATIONS: Total iterations to runTPROCC_LOG_TO_TEMP: Log output to temp directory (0/1)TPROCC_USE_TRANSACTION_COUNTER: Enable transaction counter (true/false)TPROCC_CHECKPOINT: Enable checkpoint during test (true/false)TPROCC_TIMEPROFILE: Enable time profiling (true/false)
Reporting Settings (HammerDB 6.0):
Read by loadtest.sh only, and only when SQL_SERVER_HOST points at localhost.
SQL_IMAGE: SQL Server image to run (default:mcr.microsoft.com/mssql/server:2025-CU8-ubuntu-24.04)SQL_CONTAINER: container name (default:sql2025)SQL_VOLUME: data volume name (default:sqldata_<container name>, so two instances never share one data directory)
PROFILE_ID: Tags the run so it can be compared later withRUN_MODE=compare.0means untagged (default: 0)TPROCC_XT_RESERVOIR: Reservoir size backing the xtprof percentiles (default: 10000)REPORT_JSON: Write a combined JSON report per job to the output directory (default: true)SAVE_CHARTS: Write HTML charts per job to the output directory (default: true)
Metrics Settings (HammerDB 6.0):
METRICS_ENABLED: Collect CPU/IO metrics and system data (default: false). Requires the agent, see CPU and I/O MetricsMETRICS_AGENT_HOSTNAME: Host running the HammerDB agent (default: localhost)METRICS_AGENT_ID: Agent port (default: 10000)
Profile Comparison Settings (used by RUN_MODE=compare):
BASE_PROFILE_ID: Baseline profile idCOMP_PROFILE_ID: Profile compared against the baselineWEIGHTED_COMPARE: Use weighted compare mode (true/false, default: false)
Schema Build Settings:
TPROCH_DATABASE_NAME: Database name for TPC-H (default: tpch)TPROCH_DRIVER: Database driver (default: mssqls)TPROCH_SCALE_FACTOR: Scale factor for data generationTPROCH_BUILD_THREADS: Number of threads for schema buildTPROCH_USE_CLUSTERED_COLUMNSTORE: Use clustered columnstore indexes (true/false)TPROCH_MAXDOP: Maximum degree of parallelism (default: 2). Applied at schema build time, so changing it requires a rebuild to take effect
Test Settings:
TPROCH_VIRTUAL_USERS: Virtual users for test executionTPROCH_TOTAL_QUERYSETS: Number of query sets to runTPROCH_LOG_TO_TEMP: Log output to temp directory (0/1)
TPC-H honours REPORT_JSON, SAVE_CHARTS, and the METRICS_* variables, writing
tproch_<jobid>.json and charts alongside the original .out text report.
PROFILE_ID does not apply to TPC-H. HammerDB performance profiles are a
TPROC-C only feature — a profile id set during a TPC-H run is not recorded against
the job — so Comparing Runs is TPC-C only. TPC-H also produces no
xtprof timing data, so the timing section of its JSON report is null.
Each configuration below is a complete hammerdb.env file tailored for different
hardware specifications. Copy one in full and adjust from there.
Each includes the HammerDB 6.0 reporting and metrics settings. To compare two
configurations, give each env file a different PROFILE_ID — see
Comparing Runs. To capture CPU, I/O, and storage detail, set
METRICS_ENABLED=true and start the agent — see
CPU and I/O Metrics.
Below is a configuration that provides the smallest possible test setup for quickly validating your environment. Use this when you want to verify everything is working correctly without waiting for lengthy database builds or extended test runs. This minimal setup creates a 100MB database and completes testing in under 5 minutes. This is what's included in the repository. More realistic examples are below in the readme. Update your hammerdb.env with these examples and modify them for your hardware.
# Database Connection
USERNAME=sa
PASSWORD=S0methingS@Str0ng!
SQL_SERVER_HOST=localhost,4001
# Common settings for all benchmarks
USE_BCP=true
TMP=/tmp
# Connection settings
MSSQLS_TCP=true
MSSQLS_AUTHENTICATION=sql
# TPROC-C Configuration
TPROCC_DATABASE_NAME=tpcc
TPROCC_DRIVER=mssqls
# TPROC-C Build settings
TPROCC_BUILD_VIRTUAL_USERS=1
WAREHOUSES=1
TPROCC_DRIVER_TYPE=timed
TPROCC_ALLWAREHOUSE=true
# TPROC-C Test settings
VIRTUAL_USERS=1
RAMPUP=0
DURATION=1
TOTAL_ITERATIONS=10000000
TPROCC_LOG_TO_TEMP=0
TPROCC_USE_TRANSACTION_COUNTER=true
TPROCC_CHECKPOINT=false
TPROCC_TIMEPROFILE=true
# Reporting (HammerDB 6.0)
PROFILE_ID=0 # Tag runs to compare them later, 0 = untagged
TPROCC_XT_RESERVOIR=10000 # Reservoir behind the xtprof percentiles
REPORT_JSON=true # Write output/tprocc_<jobid>.json
SAVE_CHARTS=true # Write output/tprocc_<jobid>_*.html
# CPU/IO metrics (HammerDB 6.0), needs the agent on the database host
METRICS_ENABLED=false
METRICS_AGENT_HOSTNAME=localhost
METRICS_AGENT_ID=10000
# Profile comparison, used by RUN_MODE=compare
BASE_PROFILE_ID=1
COMP_PROFILE_ID=2
WEIGHTED_COMPARE=false
# TPROC-H Configuration
TPROCH_DATABASE_NAME=tpch
TPROCH_SCALE_FACTOR=1
TPROCH_DRIVER=mssqls
TPROCH_BUILD_THREADS=1
TPROCH_USE_CLUSTERED_COLUMNSTORE=true
# TPROC-H specific test settings
TPROCH_VIRTUAL_USERS=1
TPROCH_TOTAL_QUERYSETS=1
TPROCH_MAXDOP=2 # Build setting, rebuild to change
TPROCH_LOG_TO_TEMP=1This configuration is optimized for a typical development/test workstation:
# Database Connection
USERNAME=sa
PASSWORD=S0methingS@Str0ng!
SQL_SERVER_HOST=localhost,4001
# Common settings for all benchmarks
USE_BCP=true
TMP=/tmp
# Connection settings
MSSQLS_TCP=true
MSSQLS_AUTHENTICATION=sql
# TPROC-C Configuration
TPROCC_DATABASE_NAME=tpcc
TPROCC_DRIVER=mssqls
# TPROC-C Build settings
TPROCC_BUILD_VIRTUAL_USERS=4 # Half your cores for parallel loading
WAREHOUSES=50
TPROCC_DRIVER_TYPE=timed
TPROCC_ALLWAREHOUSE=true
# TPROC-C Test settings
VIRTUAL_USERS=16 # 2x cores
RAMPUP=2 # 2 minutes to stabilize
DURATION=10 # 10 minutes for meaningful results
TOTAL_ITERATIONS=10000000
TPROCC_LOG_TO_TEMP=0
TPROCC_USE_TRANSACTION_COUNTER=true
TPROCC_CHECKPOINT=false
TPROCC_TIMEPROFILE=true
# Reporting (HammerDB 6.0)
PROFILE_ID=0 # Tag runs to compare them later, 0 = untagged
TPROCC_XT_RESERVOIR=10000 # Reservoir behind the xtprof percentiles
REPORT_JSON=true # Write output/tprocc_<jobid>.json
SAVE_CHARTS=true # Write output/tprocc_<jobid>_*.html
# CPU/IO metrics (HammerDB 6.0), needs the agent on the database host
METRICS_ENABLED=false
METRICS_AGENT_HOSTNAME=localhost
METRICS_AGENT_ID=10000
# Profile comparison, used by RUN_MODE=compare
BASE_PROFILE_ID=1
COMP_PROFILE_ID=2
WEIGHTED_COMPARE=false
# TPROC-H Configuration
TPROCH_DATABASE_NAME=tpch
TPROCH_DRIVER=mssqls
TPROCH_SCALE_FACTOR=10 # 10GB dataset
TPROCH_BUILD_THREADS=4 # Half your cores
TPROCH_USE_CLUSTERED_COLUMNSTORE=true
# TPROC-H Test settings
TPROCH_VIRTUAL_USERS=4 # Lower for CPU-intensive queries
TPROCH_TOTAL_QUERYSETS=1 # One complete run
TPROCH_MAXDOP=8 # Build setting, rebuild to change
TPROCH_LOG_TO_TEMP=1Optimized for smaller development systems or cloud instances:
# Database Connection
USERNAME=sa
PASSWORD=S0methingS@Str0ng!
SQL_SERVER_HOST=localhost,4001
# Common settings for all benchmarks
USE_BCP=true
TMP=/tmp
# Connection settings
MSSQLS_TCP=true
MSSQLS_AUTHENTICATION=sql
# TPROC-C Configuration
TPROCC_DATABASE_NAME=tpcc
TPROCC_DRIVER=mssqls
# TPROC-C Build settings
TPROCC_BUILD_VIRTUAL_USERS=2 # Half your cores
WAREHOUSES=30 # ~3GB database
TPROCC_DRIVER_TYPE=timed
TPROCC_ALLWAREHOUSE=true
# TPROC-C Test settings
VIRTUAL_USERS=8 # 2x cores
RAMPUP=2 # 2 minutes to stabilize
DURATION=10 # 10 minutes for testing
TOTAL_ITERATIONS=10000000
TPROCC_LOG_TO_TEMP=0
TPROCC_USE_TRANSACTION_COUNTER=true
TPROCC_CHECKPOINT=false
TPROCC_TIMEPROFILE=true
# Reporting (HammerDB 6.0)
PROFILE_ID=0 # Tag runs to compare them later, 0 = untagged
TPROCC_XT_RESERVOIR=10000 # Reservoir behind the xtprof percentiles
REPORT_JSON=true # Write output/tprocc_<jobid>.json
SAVE_CHARTS=true # Write output/tprocc_<jobid>_*.html
# CPU/IO metrics (HammerDB 6.0), needs the agent on the database host
METRICS_ENABLED=false
METRICS_AGENT_HOSTNAME=localhost
METRICS_AGENT_ID=10000
# Profile comparison, used by RUN_MODE=compare
BASE_PROFILE_ID=1
COMP_PROFILE_ID=2
WEIGHTED_COMPARE=false
# TPROC-H Configuration
TPROCH_DATABASE_NAME=tpch
TPROCH_DRIVER=mssqls
TPROCH_SCALE_FACTOR=5 # 5GB dataset
TPROCH_BUILD_THREADS=2 # Half your cores
TPROCH_USE_CLUSTERED_COLUMNSTORE=true
# TPROC-H Test settings
TPROCH_VIRTUAL_USERS=2 # Conservative for small systems
TPROCH_TOTAL_QUERYSETS=1 # One complete run
TPROCH_MAXDOP=4 # Build setting, rebuild to change
TPROCH_LOG_TO_TEMP=1Configuration for production-grade servers or high-performance workstations:
# Database Connection
USERNAME=sa
PASSWORD=S0methingS@Str0ng!
SQL_SERVER_HOST=localhost,4001
# Common settings for all benchmarks
USE_BCP=true
TMP=/tmp
# Connection settings
MSSQLS_TCP=true
MSSQLS_AUTHENTICATION=sql
# TPROC-C Configuration
TPROCC_DATABASE_NAME=tpcc
TPROCC_DRIVER=mssqls
# TPROC-C Build settings
TPROCC_BUILD_VIRTUAL_USERS=8 # Half your cores
WAREHOUSES=200 # ~20GB database
TPROCC_DRIVER_TYPE=timed
TPROCC_ALLWAREHOUSE=true
# TPROC-C Test settings
VIRTUAL_USERS=32 # Start with 2x cores
RAMPUP=3 # 3 minutes for larger scale
DURATION=15 # 15 minutes for stable results
TOTAL_ITERATIONS=10000000 # Effectively unlimited
TPROCC_LOG_TO_TEMP=0
TPROCC_USE_TRANSACTION_COUNTER=true
TPROCC_CHECKPOINT=false
TPROCC_TIMEPROFILE=true
# Reporting (HammerDB 6.0)
PROFILE_ID=0 # Tag runs to compare them later, 0 = untagged
TPROCC_XT_RESERVOIR=10000 # Reservoir behind the xtprof percentiles
REPORT_JSON=true # Write output/tprocc_<jobid>.json
SAVE_CHARTS=true # Write output/tprocc_<jobid>_*.html
# CPU/IO metrics (HammerDB 6.0), needs the agent on the database host
METRICS_ENABLED=false
METRICS_AGENT_HOSTNAME=localhost
METRICS_AGENT_ID=10000
# Profile comparison, used by RUN_MODE=compare
BASE_PROFILE_ID=1
COMP_PROFILE_ID=2
WEIGHTED_COMPARE=false
# TPROC-H Configuration
TPROCH_DATABASE_NAME=tpch
TPROCH_DRIVER=mssqls
TPROCH_SCALE_FACTOR=30 # 30GB dataset
TPROCH_BUILD_THREADS=8 # Half your cores
TPROCH_USE_CLUSTERED_COLUMNSTORE=true
# TPROC-H Test settings
TPROCH_VIRTUAL_USERS=8 # More parallelism
TPROCH_TOTAL_QUERYSETS=1 # One complete run
TPROCH_MAXDOP=8 # Build setting, rebuild to change
TPROCH_LOG_TO_TEMP=1The framework automatically extracts key metrics, including:
TPC-C Output:
- Transactions Per Minute (TPM)
- New Orders Per Minute (NOPM)
- Per-transaction response times, reported by the xtprof time profiler as
p99_ms/p95_ms/p75_ms/p50_ms/p25_ms, plus min/avg/max, standard deviation, and call counts for each stored procedure (NEWORD, PAYMENT, DELIVERY, SLEV, OSTAT)
TPC-H Output:
- Individual query execution times
- Total runtime for all 22 queries
- Query-specific metrics
Results are saved in both raw format (logs) and parsed format in the output/ directory.
A parsed TPC-C run looks like this:
HAMMERDB RESULT
[
"6A7E2CDBC7F603E293439383",
"2026-08-13 20:45:15",
"1 Active Virtual Users configured",
"TEST RESULT : System achieved 26018 NOPM from 60408 SQL Server TPM"
]
Alongside the console output, the parse phase writes the following to output/:
| File | Contents |
|---|---|
hdb_<jobid>.json |
HammerDB's own report: benchmark config, result, response times, metrics, system data |
tprocc_<jobid>.json |
Compact report: result, transaction count, xtprof timings, system data, profile id |
tprocc_<jobid>_result.html |
NOPM/TPM bar chart |
tprocc_<jobid>_timing.html |
Response time distribution |
tprocc_<jobid>_tcount.html |
Transaction count over the run |
TPC-H writes the same shape, alongside the original text report:
| File | Contents |
|---|---|
hdb_<jobid>.json |
HammerDB's own report |
tproch_<jobid>.json |
Compact report: result, query timings, and system data |
tproch_<jobid>_result.html |
Query result chart |
tproch_<jobid>_timing.html |
Query timing chart |
mssqls_tproch_<jobid>.out |
Original plain text report, kept for compatibility |
Each chart is a single HTML file that opens directly in a browser, but it loads
the ECharts library from a CDN rather than embedding it, so rendering needs
internet access. On an isolated network the page opens and stays blank. Turn
either output off with REPORT_JSON=false or SAVE_CHARTS=false.
A section with no data is reported as null rather than as empty fields. TPC-H
produces no xtprof timing data, so timing is normally null there, and system
is null unless metrics were enabled.
Two JSON reports are written because they come from different places.
hdb_<jobid>.json is HammerDB's own richer format, produced by its
jobs <jobid> save command. tprocc_<jobid>.json is assembled by the parse
script from the individual job subcommands, is guaranteed to be produced even if
the native path breaks again, and is the only one carrying the profile id. Drop
either by editing the parse script if you only want one.
Note:
jobs <jobid> saveis broken in the shipped HammerDB 6.0 Linux binary. Itsjobs_save_jsoncalls two helpers that exist in the v6.0 source but not in the released starpack, so it fails withinvalid command name "jobs_summary_public_config"and leaves a zero-byte file. scripts/hammerdb6_compat.tcl defines those two helpers at runtime, taken verbatim from the v6.0 source, which makes the command work. Each definition is guarded, so the shim becomes a no-op once a HammerDB release ships them. The released binary keeps its modules in a password protected VFS that takes precedence over/opt/HammerDB-6.0/modules, so dropping the newer module on disk does not override it — the helpers have to be defined at runtime.
Tag each run with a PROFILE_ID during the load phase, then compare two profiles.
This is the intended way to test configurations against each other — SQL Server
versions, storage, instance sizes — instead of eyeballing two result blobs.
A profile is a set of runs, normally the same workload at increasing virtual user
counts. Give every run in one configuration the same PROFILE_ID.
Warning: profile ids live in the job repository, not in a session. Every earlier run tagged with the same id is still in that profile, including an ad-hoc
./loadtest.shrun that pickedPROFILE_IDup fromhammerdb.env. When a profile holds more than one run at the same virtual user count, HammerDB uses one of them and ignores the rest without saying so, and the summary then answers a slightly different question than the one you asked. KeepPROFILE_ID=0inhammerdb.envso casual runs stay untagged, use a fresh pair of ids per comparison, and read the profile listing the comparison prints before trusting the numbers under it.
# Baseline configuration, tagged as profile 1
HAMMERDB_ENV_FILE=hammerdb-2022.env RUN_MODE=load BENCHMARK=tprocc docker compose up --abort-on-container-exit
# Configuration under test, tagged as profile 2
HAMMERDB_ENV_FILE=hammerdb-2025.env RUN_MODE=load BENCHMARK=tprocc docker compose up --abort-on-container-exit
# Compare them
docker compose run --rm --no-TTY \
-e RUN_MODE=compare -e BENCHMARK=tprocc \
-e BASE_PROFILE_ID=1 -e COMP_PROFILE_ID=2 hammerdbSet PROFILE_ID in each env file, or override per run with
docker compose run -e PROFILE_ID=2 ....
compare.sh wraps this up. It runs the load phase against each configuration, tags
each with its own profile id, and runs the comparison:
./compare.sh hammerdb-2022.env hammerdb-2025.env
# Compare a curve rather than a single point by running several VU counts
VU_COUNTS="4 8 16 32" ./compare.sh hammerdb-2022.env hammerdb-2025.envThe schema must already exist on both instances. compare.sh only runs load, compare,
and the reporting — it does not build.
The comparison prints each profile's runs and a summary, and writes
output/tprocc_profile_<base>_vs_<comp>.json plus an HTML comparison chart:
PROFILE DIFF 1 VS 2
Profiles compared (unweighted): matched=2, avg_base=32759, avg_comp=31370
WEIGHTED_COMPARE=true additionally reports a core-count weighted comparison.
Note:
matchedcounts runs paired by virtual user count across the two profiles. Profile charts need at least two runs per profile at different virtual user counts — with a single run each, the comparison summary still works but the chart is empty and is skipped with a warning.
Setting METRICS_ENABLED=true collects CPU and I/O metrics during the run and
populates the JOBSYSTEM hardware and software fields, which then appear in the
system block of the JSON report:
"system": {
"cpumodel": "VirtualApple @ 2.50GHz",
"cpucount": "12",
"os_name": "Ubuntu 24.04.4 LTS",
"memory": "19.5 GB",
"storage": "vda (256 GB); vdb (1 GB)",
"nic": "eth0 (10 Gbps)"
}This requires the HammerDB metric agent to be running on the database host, and
the agent requires the sysstat package. Without a reachable agent the load phase
prints a warning and continues, so the benchmark itself never fails because metrics
are unavailable.
Local SQL Server (the container started by loadtest.sh, where the database and
Docker host are the same machine):
docker compose --profile metrics up -d agentRemote SQL Server — run the agent on the database server itself:
# On the SQL Server host
sudo apt-get install -y sysstat # or: dnf install sysstat
/opt/HammerDB-6.0/agent/agent 10000Then point the load phase at it:
METRICS_ENABLED=true
METRICS_AGENT_HOSTNAME=sqlserver.example.com
METRICS_AGENT_ID=10000Note: On a Mac the agent reports the Docker VM, not macOS, and the figures are distorted by emulation. Metrics are only meaningful when the agent runs on a real Linux database host.
HammerDB 6.0 extended its job repository schema. JOBSYSTEM gained hardware and
software detail fields and is migrated automatically on open (ALTER TABLE ADD COLUMN), and a new JOBCI table is created. JOBTIMING has no such migration
path, so a 5.0 hammer.DB is missing the new percentile columns. If you carry an
old one over in output/, the benchmark itself still runs and reports its TPM/NOPM,
but recording the timing data fails at the end of the run:
Vuser 1:TEST RESULT : System achieved 23275 NOPM from 54230 SQL Server TPM
Error in Virtual User 1: table JOBTIMING has no column named p75_ms
Vuser 1:FINISHED FAILED
Note that the run reports FINISHED FAILED only after printing a plausible
result, so this is easy to miss. Delete or archive output/hammer.DB before your
first 6.0 run and HammerDB will create a fresh repository with the new schema:
mv output/hammer.DB output/hammer.DB.hammerdb5.bak # or just delete itOld jobs in an archived 5.0 repository are still readable by HammerDB 5.0; there is no in-place upgrade path, so keep the file if you need that history.
The Tcl scripts in scripts/ needed no changes for 6.0 — every mssqls_* parameter
they set still exists in 6.0's config/mssqlserver.xml.
To start the container in interactive mode, useful for debugging tests.
# if the container isn't built yet
docker compose build
# this will enter an interactive terminal inside the hammerdb container
docker run -it --network host \
--env-file hammerdb.env \
--env RUN_MODE=parse \
--env BENCHMARK=tprocc \
--env TMP=/tmp \
-v $(pwd)/scripts:/opt/HammerDB-6.0/scripts \
-v $(pwd)/output:/tmp \
--entrypoint /bin/bash \
hammerdb-hammerdb:latest