Add the broker-comparison module
Kafka, RabbitMQ and Pulsar measured side by side on ordering, replay and consumer scaling by driving the three client libraries directly, plus an operational-footprint measurement of each broker's own distribution. Nine tests, three brokers started without Docker, and every number in the documentation regenerated by scripts/run-all.sh.
This commit is contained in:
73
broker-comparison/scripts/footprint.sh
Executable file
73
broker-comparison/scripts/footprint.sh
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
# Measures the operational footprint of one broker: how long it takes to accept a connection,
|
||||
# how much memory it holds at idle, how many processes and listening ports it opens, and how
|
||||
# large its shipped default configuration is.
|
||||
#
|
||||
# These are proxies, not a benchmark. "Ops burden" is not directly measurable, but the number of
|
||||
# moving parts, the memory floor and the size of the configuration surface are, and they are what
|
||||
# people are actually asking about when they ask which broker is heavier to run.
|
||||
#
|
||||
# scripts/footprint.sh kafka|rabbit|pulsar
|
||||
set -euo pipefail
|
||||
BROKER="${1:?kafka|rabbit|pulsar}"
|
||||
|
||||
extra=""
|
||||
port_open() { (echo > "/dev/tcp/127.0.0.1/$1") 2>/dev/null; }
|
||||
rss_mb() { ps -eo rss,cmd | grep -F "$1" | grep -v grep | awk '{s+=$1} END {printf "%.0f", s/1024}'; }
|
||||
procs() { ps -eo cmd | grep -F "$1" | grep -cv grep; }
|
||||
settings() { grep -cE '^[a-zA-Z]' "$1" 2>/dev/null || echo 0; }
|
||||
|
||||
start=$(date +%s%3N)
|
||||
case "$BROKER" in
|
||||
kafka)
|
||||
: "${KAFKA_HOME:?set KAFKA_HOME}"
|
||||
# Wipe whatever log.dirs points at, so the format below is not refused with
|
||||
# "Invalid cluster.id ... Expected X, but read Y" from a previous run.
|
||||
KAFKA_LOG_DIRS=$(grep -E '^log.dirs=' "$KAFKA_HOME/config/server.properties" | cut -d= -f2)
|
||||
rm -rf ${KAFKA_LOG_DIRS//,/ }
|
||||
"$KAFKA_HOME/bin/kafka-storage.sh" format --standalone -t "$("$KAFKA_HOME/bin/kafka-storage.sh" random-uuid)" \
|
||||
-c "$KAFKA_HOME/config/server.properties" --ignore-formatted >/dev/null
|
||||
start=$(date +%s%3N) # after formatting: kafka-storage.sh is two more JVM starts and is a
|
||||
# one-off, not part of what a restart costs
|
||||
setsid nohup "$KAFKA_HOME/bin/kafka-server-start.sh" "$KAFKA_HOME/config/server.properties" \
|
||||
> /tmp/kafka-start.log 2>&1 < /dev/null &
|
||||
for _ in $(seq 1 90); do port_open 9092 && break; sleep 1; done
|
||||
ready=$(( $(date +%s%3N) - start ))
|
||||
marker="kafka.Kafka"; ports="9092 (broker), 9093 (controller)"; conf="$KAFKA_HOME/config/server.properties"
|
||||
dist="$KAFKA_HOME"
|
||||
;;
|
||||
rabbit)
|
||||
: "${RABBITMQ_HOME:?set RABBITMQ_HOME}"; : "${ERL_ROOT:?set ERL_ROOT}"
|
||||
scripts/rabbit-broker.sh >/dev/null
|
||||
ready=$(( $(date +%s%3N) - start ))
|
||||
# The broker is an Erlang VM, so the process to measure is beam.smp, not anything named
|
||||
# "rabbitmq". epmd is a second, tiny process and is counted separately below.
|
||||
marker="beam.smp"; ports="5672 (AMQP), 4369 (epmd), 25672 (inter-node)"
|
||||
conf="$RABBITMQ_HOME/etc/rabbitmq/rabbitmq.conf.example"; dist="$RABBITMQ_HOME"
|
||||
extra="epmd processes : $(procs epmd)
|
||||
NOTE: RabbitMQ ships no configuration file at all. rabbitmq.conf.example is
|
||||
entirely commented out and the boot log records \"Config file(s): (none)\".
|
||||
Everything in that file is documentation, not a default."
|
||||
;;
|
||||
pulsar)
|
||||
: "${PULSAR_HOME:?set PULSAR_HOME}"
|
||||
scripts/pulsar-broker.sh >/dev/null
|
||||
ready=$(( $(date +%s%3N) - start ))
|
||||
marker="PulsarStandaloneStarter"; ports="6650 (binary), 8080 (admin/REST), 2181 (ZooKeeper)"
|
||||
conf="$PULSAR_HOME/conf/standalone.conf"; dist="$PULSAR_HOME"
|
||||
;;
|
||||
*) echo "unknown broker $BROKER" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
cat <<EOF
|
||||
broker : $BROKER
|
||||
time from launch to first
|
||||
accepted connection : ${ready} ms
|
||||
resident memory at idle : $(rss_mb "$marker") MB
|
||||
server processes : $(procs "$marker")
|
||||
listening ports : $ports
|
||||
unpacked distribution size : $(du -sm "$dist" | cut -f1) MB
|
||||
settings in shipped default
|
||||
configuration file : $(settings "$conf") ($(basename "$conf"))
|
||||
${extra:-}
|
||||
EOF
|
||||
25
broker-comparison/scripts/pulsar-broker.sh
Executable file
25
broker-comparison/scripts/pulsar-broker.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start a real Apache Pulsar broker with no Docker and no root.
|
||||
#
|
||||
# The standalone distribution bundles ZooKeeper, BookKeeper and the broker in one process, which
|
||||
# is why a single tarball is enough. -nss skips the stream storage (BookKeeper's table service,
|
||||
# needed only by stateful Pulsar Functions) and -nfw skips the functions worker; both save
|
||||
# several hundred megabytes of heap and a few seconds of startup.
|
||||
#
|
||||
# PULSAR_HOME must point at an unpacked apache-pulsar-<version>-bin directory. Pulsar 4.2
|
||||
# supports Java 17 and 21; the transcripts here were produced on Temurin 21.
|
||||
set -eu
|
||||
: "${PULSAR_HOME:?set PULSAR_HOME to an unpacked apache-pulsar-*-bin directory}"
|
||||
export PULSAR_MEM="${PULSAR_MEM:--Xms384m -Xmx700m -XX:MaxDirectMemorySize=384m}"
|
||||
|
||||
cd "$PULSAR_HOME"
|
||||
setsid nohup bin/pulsar standalone -nss -nfw > /tmp/pulsar-start.log 2>&1 < /dev/null &
|
||||
for _ in $(seq 1 90); do
|
||||
if [ "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/admin/v2/brokers/version)" = "200" ]; then
|
||||
echo "broker ready on 6650 (admin 8080), version $(curl -s http://127.0.0.1:8080/admin/v2/brokers/version)"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "broker did not start; see /tmp/pulsar-start.log" >&2
|
||||
exit 1
|
||||
31
broker-comparison/scripts/rabbit-broker.sh
Executable file
31
broker-comparison/scripts/rabbit-broker.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start a real RabbitMQ broker with no Docker and no root.
|
||||
#
|
||||
# RabbitMQ is an Erlang application, so an Erlang runtime and epmd on the path are the whole
|
||||
# dependency. Point ERL_ROOT at an Erlang installation and RABBITMQ_HOME at an unpacked
|
||||
# rabbitmq-server-generic-unix tarball. Ubuntu 22.04 ships Erlang 24, whose newest compatible
|
||||
# broker is 3.10.25; 3.11 and later need Erlang 25.
|
||||
#
|
||||
# The one non-obvious step is starting epmd yourself: rabbitmq-server's own attempt to start it
|
||||
# fails in a container without a resolvable hostname, and the failure surfaces as a forty-line
|
||||
# Erlang crash dump ending in {'EXIT',nodistribution}.
|
||||
set -eu
|
||||
: "${ERL_ROOT:?set ERL_ROOT to an Erlang installation directory}"
|
||||
: "${RABBITMQ_HOME:?set RABBITMQ_HOME to an unpacked rabbitmq-server-generic-unix directory}"
|
||||
|
||||
export PATH="$ERL_ROOT/bin:$ERL_ROOT/erts-"*/bin":$PATH"
|
||||
export RABBITMQ_MNESIA_BASE="${RABBITMQ_MNESIA_BASE:-/tmp/rmq/data}"
|
||||
export RABBITMQ_LOG_BASE="${RABBITMQ_LOG_BASE:-/tmp/rmq/log}"
|
||||
export RABBITMQ_NODENAME="${RABBITMQ_NODENAME:-rabbit@localhost}"
|
||||
export HOME="${HOME:-/tmp/rmq}"
|
||||
mkdir -p "$RABBITMQ_MNESIA_BASE" "$RABBITMQ_LOG_BASE"
|
||||
|
||||
epmd -daemon 2>/dev/null || true
|
||||
sleep 1
|
||||
setsid nohup "$RABBITMQ_HOME/sbin/rabbitmq-server" > /tmp/rmq/boot.log 2>&1 < /dev/null &
|
||||
for _ in $(seq 1 60); do
|
||||
if (echo > /dev/tcp/127.0.0.1/5672) 2>/dev/null; then echo "broker ready on 5672"; exit 0; fi
|
||||
sleep 1
|
||||
done
|
||||
echo "broker did not start; see /tmp/rmq/boot.log" >&2
|
||||
exit 1
|
||||
37
broker-comparison/scripts/run-all.sh
Executable file
37
broker-comparison/scripts/run-all.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates every file under docs/output/. Each broker is started, measured and left running
|
||||
# only for its own group of tests, because three brokers do not fit comfortably in memory on a
|
||||
# small machine at the same time.
|
||||
#
|
||||
# Required environment:
|
||||
# KAFKA_HOME unpacked kafka_2.13-4.2.1
|
||||
# ERL_ROOT an Erlang 24 installation (RabbitMQ 3.10.x)
|
||||
# RABBITMQ_HOME unpacked rabbitmq-server-generic-unix-3.10.25
|
||||
# PULSAR_HOME unpacked apache-pulsar-4.2.4-bin
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
mkdir -p docs/output
|
||||
|
||||
# Kafka needs no external broker for the measurements: spring-kafka-test starts a real KRaft
|
||||
# broker in-process. KAFKA_HOME is only used by the footprint measurement.
|
||||
mvn -B -Dgroups=kafka test 2>&1 | grep -E 'Running |Tests run:|BUILD ' > docs/output/tests.txt
|
||||
|
||||
scripts/rabbit-broker.sh
|
||||
mvn -B -Dgroups=rabbit test 2>&1 | grep -E 'Running |Tests run:|BUILD ' >> docs/output/tests.txt
|
||||
|
||||
scripts/pulsar-broker.sh
|
||||
mvn -B -Dgroups=pulsar test 2>&1 | grep -E 'Running |Tests run:|BUILD ' >> docs/output/tests.txt
|
||||
|
||||
{
|
||||
echo "== Operational footprint, measured on one 2-core / 3.8 GB Linux box =="
|
||||
echo
|
||||
echo "Each broker started from its shipped distribution with default configuration and a"
|
||||
echo "700 MB heap cap, on Temurin JDK 21. Times are one sample on a small box: treat them as"
|
||||
echo "orders of magnitude, not as a benchmark."
|
||||
echo
|
||||
scripts/footprint.sh kafka; echo
|
||||
scripts/footprint.sh rabbit; echo
|
||||
scripts/footprint.sh pulsar
|
||||
} > docs/output/footprint.txt
|
||||
|
||||
echo "regenerated:"; ls -1 docs/output
|
||||
Reference in New Issue
Block a user