Infrastructure-as-code benchmarking tool for LavinMQ, using Terraform to
provision AWS resources and lavinmqperf to run
performance tests.
- Terraform >= 1.3.0 — installation guide
- AWS credentials with permission to create EC2 and VPC resources
- An SSH key pair for instance access
Provisions a broker and one or more load generator instances, then runs a single user-supplied
lavinmqperf command. Useful for ad-hoc throughput or latency measurements where you control the
exact test parameters.
Runs sequential AMQP throughput tests across a configurable set of message sizes, repeated N times.
Produces a CSV file of raw per-run results and a JSON config file on the load generator. Aggregated
(median) summaries are written to results/v{version}/throughput.md by scripts/aggregate_results.py.
Runs sequential AMQP latency tests across a configurable set of message sizes and rate limits,
repeated N times. Produces a CSV file of raw per-run results and a JSON config file on the load
generator. Aggregated (median) summaries are written to results/v{version}/latency_P95.md and
latency_P99.md by scripts/aggregate_results.py.
Runs sequential MQTT throughput tests across a configurable set of message sizes, repeated N times.
Installs emqtt-bench and mqttloader on the load generator. Produces a CSV file of raw per-run
results and a JSON config file on the load generator. Aggregated (median) summaries are written to
results/v{version}/mqtt_throughput.md by scripts/aggregate_results.py.
Variables can be supplied in three ways:
terraform.auto.tfvars — loaded automatically by Terraform:
terraform applyterraform.tfvars — loaded explicitly:
terraform apply -var-file="terraform.tfvars".env file via dotenv — environment variables prefixed
with TF_VAR_:
dotenv terraform applyUse the templates in modules/providers/aws/variables_template/ as a starting point:
terraform_tfvars.txt— for.tfvarsfilesenv.txt— for.envfiles
AWS credentials must be set as environment variables regardless of the method used:
export AWS_ACCESS_KEY=***
export AWS_SECRET_KEY=***The Benchmark GitHub Actions workflow provisions
infrastructure, runs benchmarks across all supported instance types in parallel, aggregates the
results into markdown summary files, and opens a pull request against main with the results
committed to a results/v{version} branch.
See docs/pipeline.md for diagrams of the AWS infrastructure and the CI workflow, from release trigger to published results.
Via the GitHub UI: go to Actions → Benchmark → Run workflow, fill in the version and pick a scenario.
Via the GitHub CLI:
# Run all benchmarks (AMQP throughput, AMQP latency, MQTT throughput) for all instance types
gh workflow run benchmark.yml \
-f broker_version=2.7.0 \
-f scenarios=all
# Latency only
gh workflow run benchmark.yml \
-f broker_version=2.7.0 \
-f scenarios=latency
# AMQP throughput only
gh workflow run benchmark.yml \
-f broker_version=2.7.0 \
-f scenarios=throughput
# MQTT throughput only
gh workflow run benchmark.yml \
-f broker_version=2.7.0 \
-f scenarios=mqtt-throughput
# Pin a specific load generator version (leave empty to use latest stable)
gh workflow run benchmark.yml \
-f broker_version=2.7.0 \
-f load_generator_version=2.7.0 \
-f scenarios=all
# Run from a specific branch (e.g. when testing workflow changes)
gh workflow run benchmark.yml \
-r my-branch \
-f broker_version=2.7.0 \
-f scenarios=allResults are committed to results/v{version}/ and a pull request is created (or updated if one
already exists for that version).
All three workflows accept an optional brokers input — a comma-separated list of broker instance
types to run. When omitted or empty, all instance types are benchmarked. This is useful for
re-running a single instance type that failed without triggering the full suite.
# Re-run latency benchmark for a single instance type
gh workflow run benchmark.yml \
-f broker_version=2.7.0 \
-f scenarios=latency \
-f brokers="c8g.large"
# Re-run latency benchmark for multiple specific instance types
gh workflow run benchmark.yml \
-f broker_version=2.7.0 \
-f scenarios=latency \
-f brokers="c8g.large,t4g.medium"The individual benchmark-latency.yml, benchmark-throughput.yml, and benchmark-mqtt-throughput.yml
workflows can also be triggered directly in the same way if you want to skip the aggregate/PR step.
Each job runs terraform destroy with if: always(), but if a job is cancelled, times out, or the
destroy step itself fails, the AWS resources it created (VPC, subnet, internet gateway, EC2
instances, ENIs, key pair) are left running. Since the Terraform state lives
only on the ephemeral runner, it's gone too, so cleanup has to be done directly via the AWS
console/CLI rather than by re-running terraform destroy.
Leftover resources are tagged CreatedBy = github-actions. Delete them in this order, since later
resources depend on earlier ones being gone:
- Terminate the 2 EC2 instances (broker + load generator). Their network interfaces are attached to the subnet/VPC and block deletion otherwise.
- Wait until both show
terminated. - Detach the Internet Gateway from the VPC.
- Delete the Internet Gateway.
- Delete the network interfaces (ENIs) belonging to the two terminated instances, these aren't always cleaned up automatically on termination and will block subnet/VPC deletion if left.
- Delete the subnet.
- Delete the VPC, this implicitly removes the default route table entries, default security
group rules, and the main route table association created in
network.tf. No separate delete needed for those. - Delete the SSH key pair (
benchmark-ci-latency-<run_id>-<broker_safe>or equivalent for the other scenarios), independent of the above, can be done any time.
At larger message sizes the throughput test can outrun an instance's sustained AWS network bandwidth once its burst allowance is spent, giving unreliable numbers. Two configs keep that out of the published charts (raw CSVs stay in git):
scripts/throughput_skip_sizes.json— sizes skipped per instance (t4g.micro,r7g.mediumat 65536 B). The workflow skips them on new runs;build_data.pyalso redacts them from old data.scripts/website_hidden_instances.json— instances still benchmarked but hidden from all charts (z1d.large, whose large-message throughput is bandwidth-bound, not LavinMQ-bound).
Latency is unaffected: its per-size rate limits keep demand below every baseline. See issue #90.
Raw results are stored and available in this repository. These are also available at https://lavinmq.com/benchmark with graphs and a UI to easier compare the numbers.
Aggregated JSONs under results/ are published to benchmark.lavinmq.com and consumed by the
charts page in lavinmq-website.
To preview a local copy, start jekyll in lavinmq-website and run the following here:
python3 scripts/build_data.py # regenerate throughput/latency/mqtt_throughput JSONs
python3 scripts/serve.py # serve results/ on http://127.0.0.1:8081 with CORS.github/instructions/benchmark-comparison.instructions.md
guides AI coding agents (e.g. GitHub Copilot, Claude Code) through comparing benchmark summaries
across releases, checking for missing or invalid runs, accounting for environment differences,
and producing a concise writeup suitable for a PR description or comment. Ask your
agent to compare results under results/ (e.g. "compare v2.9.3 and v2.10.0 latency results") and it will pick up these instructions automatically.
Enable detailed Terraform provider logs by setting:
export TF_LOG_PROVIDER=DEBUGAccepted levels: INFO, DEBUG, WARN, ERROR, TRACE.