perf(testcontainers): replace rabbitmq-diagnostics healthcheck with raw TCP probe - #10331
Conversation
…aw TCP probe Every rabbitmq-diagnostics invocation boots a full Erlang VM, costing ~2.1s of CPU time per check (measured on rabbitmq:4.2.1-management). At the 1s healthcheck interval this pegs more than two cores per broker container for the entire life of the stack, and the ~0.3-1s wall time races against the 1s timeout on loaded CI machines. check_port_connectivity only verifies that the listener ports accept TCP connections, so a bash /dev/tcp probe on 5672 provides the identical readiness signal at ~1ms per check, with no broker log noise (RabbitMQ does not log connections closed before the protocol header). Exec form is required because the image's /bin/sh (dash) lacks /dev/tcp support. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 2 files
Confidence score: 4/5
- In
python_testcontainers/infrahub_testcontainers/docker-compose-cluster.test.yml, the broker healthcheck assumesbashexists even thoughMESSAGE_QUEUE_DOCKER_IMAGEis configurable; Alpine or custom images may fail health checks and prevent the stack from starting. Make the healthcheck shell-compatible with supported images or constrain/document the image requirement.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="python_testcontainers/infrahub_testcontainers/docker-compose-cluster.test.yml">
<violation number="1" location="python_testcontainers/infrahub_testcontainers/docker-compose-cluster.test.yml:40">
P2: This healthcheck hard-requires bash in the broker image, but the image is configurable via MESSAGE_QUEUE_DOCKER_IMAGE. If anyone runs the stack against a non-Debian rabbitmq image (e.g. an alpine variant or a custom build) that lacks bash, the exec-form probe fails to find bash, the healthcheck reports unhealthy, and depends_on: service_healthy for message-queue blocks the whole stack from starting. The previous rabbitmq-diagnostics form worked on any rabbitmq image. Note the fragility in the comment or fall back to a form that doesn't depend on bash when /bin/sh is dash.</violation>
</file>
Shadow auto-approve: would not auto-approve because issues were found.
Re-trigger cubic
| # raw TCP probe instead of rabbitmq-diagnostics: each diagnostics call | ||
| # boots a full Erlang VM (~2s CPU), which at a 1s interval pegs two | ||
| # cores per broker for the life of the stack | ||
| test: ["CMD", "bash", "-c", "</dev/tcp/127.0.0.1/5672"] |
There was a problem hiding this comment.
P2: This healthcheck hard-requires bash in the broker image, but the image is configurable via MESSAGE_QUEUE_DOCKER_IMAGE. If anyone runs the stack against a non-Debian rabbitmq image (e.g. an alpine variant or a custom build) that lacks bash, the exec-form probe fails to find bash, the healthcheck reports unhealthy, and depends_on: service_healthy for message-queue blocks the whole stack from starting. The previous rabbitmq-diagnostics form worked on any rabbitmq image. Note the fragility in the comment or fall back to a form that doesn't depend on bash when /bin/sh is dash.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At python_testcontainers/infrahub_testcontainers/docker-compose-cluster.test.yml, line 40:
<comment>This healthcheck hard-requires bash in the broker image, but the image is configurable via MESSAGE_QUEUE_DOCKER_IMAGE. If anyone runs the stack against a non-Debian rabbitmq image (e.g. an alpine variant or a custom build) that lacks bash, the exec-form probe fails to find bash, the healthcheck reports unhealthy, and depends_on: service_healthy for message-queue blocks the whole stack from starting. The previous rabbitmq-diagnostics form worked on any rabbitmq image. Note the fragility in the comment or fall back to a form that doesn't depend on bash when /bin/sh is dash.</comment>
<file context>
@@ -34,7 +34,10 @@ services:
+ # raw TCP probe instead of rabbitmq-diagnostics: each diagnostics call
+ # boots a full Erlang VM (~2s CPU), which at a 1s interval pegs two
+ # cores per broker for the life of the stack
+ test: ["CMD", "bash", "-c", "</dev/tcp/127.0.0.1/5672"]
interval: 1s
timeout: 1s
</file context>
ogenstad
left a comment
There was a problem hiding this comment.
I'd say it looks good to me but this is awful. Assuming that the command is just a wasteful tcp check it looks fine with me but as I wrote I think we also need to change this everywhere.
| # raw TCP probe instead of rabbitmq-diagnostics: each diagnostics call | ||
| # boots a full Erlang VM (~2s CPU), which at a 1s interval pegs two | ||
| # cores per broker for the life of the stack | ||
| test: ["CMD", "bash", "-c", "</dev/tcp/127.0.0.1/5672"] |
There was a problem hiding this comment.
I'm wondering if the problem here is rabbitmq-diagnostics -q check_port_connectivity or if it's the interval of 1 second. Looks like we have the same check with a 5 second interval in the regular setup (i.e. under ./development). If this truly is just a tcp handshake it seems like an awful waste to start an Erlang VM for it. 🤯 But we should not only change it for the devcontainers but for all of our rabbitmq health checks.
Summary
The aggressive 1s RabbitMQ healthcheck cadence in
infrahub-testcontainerssilently costs more than two CPU cores per broker container for the entire life of every test stack: eachrabbitmq-diagnosticsinvocation boots a full Erlang VM (~2.1s of CPU time measured onrabbitmq:4.2.1-management). On CI machines running several stacks in parallel, that CPU is stolen from the tests themselves. This PR keeps the aggressive interval but makes each check effectively free.Key Changes
message-queuehealthcheck in both testcontainers compose files (single-node and cluster) now uses a raw TCP probe (bash -c '</dev/tcp/127.0.0.1/5672') instead ofrabbitmq-diagnostics -q check_port_connectivity, cutting per-check cost from ~2.1s CPU to ~1ms (~2000x).check_port_connectivityonly verifies that listener ports accept TCP connections, and RabbitMQ opens the AMQP listener at the very end of boot.["CMD", "bash", ...]) is required because the image's/bin/shis dash, which lacks/dev/tcp.Validation
Verified empirically against
rabbitmq:4.2.1-management:rabbitmq-diagnostics -q check_port_connectivity: 2.15s CPU (user+sys) per run; bash TCP probe: ~1ms.docker compose up --waitin ~2.6s (first probe fails mid-boot, second succeeds).Test Plan
docker compose up --wait; a green run exercises the new healthcheck end-to-end.🤖 Generated with Claude Code