Skip to content

feat: instrument the pub/sub layer with Prometheus histograms - #4548

Open
igor-kupczynski wants to merge 1 commit into
nats-pubsub-backendfrom
pubsub-metrics
Open

feat: instrument the pub/sub layer with Prometheus histograms#4548
igor-kupczynski wants to merge 1 commit into
nats-pubsub-backendfrom
pubsub-metrics

Conversation

@igor-kupczynski

@igor-kupczynski igor-kupczynski commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

Wraps every backend's msgqueue.PubSub (the interface #4480 split from the durable queue) in a decorator recording two Prometheus histograms:

hatchet_pubsub_publish_duration_seconds{kind, topic_kind, result}
hatchet_pubsub_transit_seconds{kind, topic_kind}

Backend choice needs numbers; kind makes a mixed fleet comparable.

Stacked on #4550 (NATS pub/sub backend); review that first — this diff is instrumentation-only. #4550 is opt-in and enables nothing on merge, so this PR is the real gate: a baseline must be collecting before any shard switches to NATS.

Two caveats (also documented):

  • Publish duration is caller-blocking time, not broker latency — NATS buffers and returns immediately, RabbitMQ writes synchronously — useful for publisher stalls, not broker ranking.
  • Transit latency subtracts two clocks, so pod clock skew applies; observations are clamped at zero. Buckets start at 100µs so two fast brokers don't share a bucket.

The transit stamp is a new published_at envelope field. Mixed fleets are safe both ways: older engines ignore the unknown JSON field, unstamped messages skip the transit observation, and omitzero keeps the field off durable messages. Pub stamps a copy — callers reuse the same *Message for the durable queue. The decorator sits inside the tenant-pubs gate, so DisableTenantPubs drops aren't counted as publishes.

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

What's Changed

  • internal/msgqueue/instrumented_pubsub.go — the decorator
  • pkg/integrations/metrics/prometheus/pubsub.go — histogram definitions and buckets
  • internal/msgqueue/msg.gopublished_at envelope field
  • pkg/config/loader/loader.go — wires the decorator for every backend
  • frontend/docs/pages/self-hosting/prometheus-metrics.mdx — docs

Checklist

Changes have been:

  • Tested (unit, integration, or manually with steps specified)
  • Linted and formatted
  • Documented (where applicable)
  • Added to CHANGELOG (where applicable) -- see Keep a Changelog

Testing

go test ./internal/msgqueue/...   # incl. instrumented decorator unit tests

Unit tests cover no mutation of the caller's Message, transit observed only for stamped messages, and the result label on success and error paths. The NATS integration suite also passes.


🤖 AI Disclosure
  • I acknowledge that an LLM was used in the creation of this Pull Request, in accordance with Hatchet's AI_POLICY.md.

  • Details: Cursor agent implemented the instrumentation, tests, and docs from a reviewed plan.

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hatchet-docs Ready Ready Preview, Comment Jul 28, 2026 2:08pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation engine Related to the core Hatchet engine labels Jul 28, 2026
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Benchmark results

goos: linux
goarch: amd64
pkg: github.com/hatchet-dev/hatchet/internal/msgqueue
cpu: AMD Ryzen 9 7950X3D 16-Core Processor          
                              │ /tmp/old.txt │            /tmp/new.txt             │
                              │    sec/op    │    sec/op     vs base               │
CompressPayloads_1x10KiB-8      74.42µ ±  4%   77.06µ ±  8%        ~ (p=0.065 n=6)
CompressPayloads_10x10KiB-8     850.9µ ±  5%   879.3µ ±  2%        ~ (p=0.180 n=6)
CompressPayloads_10x100KiB-8    10.47m ±  1%   10.69m ±  6%        ~ (p=0.065 n=6)
CompressPayloads_Concurrent-8   53.86µ ± 11%   64.95µ ± 18%  +20.58% (p=0.041 n=6)
geomean                         434.8µ         465.7µ         +7.13%

                              │ /tmp/old.txt │            /tmp/new.txt            │
                              │     B/op     │     B/op      vs base              │
CompressPayloads_1x10KiB-8      10.88Ki ± 1%   10.88Ki ± 1%       ~ (p=0.623 n=6)
CompressPayloads_10x10KiB-8     108.3Ki ± 1%   108.0Ki ± 1%       ~ (p=0.455 n=6)
CompressPayloads_10x100KiB-8    2.920Mi ± 0%   2.920Mi ± 0%  -0.00% (p=0.041 n=6)
CompressPayloads_Concurrent-8   54.20Ki ± 0%   54.35Ki ± 0%       ~ (p=0.180 n=6)
geomean                         117.5Ki        117.5Ki       +0.01%

                              │ /tmp/old.txt │            /tmp/new.txt            │
                              │  allocs/op   │ allocs/op   vs base                │
CompressPayloads_1x10KiB-8        5.000 ± 0%   5.000 ± 0%       ~ (p=1.000 n=6) ¹
CompressPayloads_10x10KiB-8       32.00 ± 0%   32.00 ± 0%       ~ (p=1.000 n=6) ¹
CompressPayloads_10x100KiB-8      63.00 ± 0%   63.00 ± 0%       ~ (p=1.000 n=6) ¹
CompressPayloads_Concurrent-8     17.00 ± 0%   17.00 ± 0%       ~ (p=1.000 n=6) ¹
geomean                           20.35        20.35       +0.00%
¹ all samples are equal

Compared against main (5bd467c)

Adds publisher-side and end-to-end latency histograms to the pub/sub layer, for
every backend, via a decorator:

  hatchet_pubsub_publish_duration_seconds{kind, topic_kind, result}
  hatchet_pubsub_transit_seconds{kind, topic_kind}

Choosing between pub/sub backends needs numbers, and the kind label makes a
mixed fleet directly comparable. These need to be deployed and collecting before
any shard is switched to a new backend, so that there is a baseline to compare
against rather than only simultaneous data from a fleet that already changed.

Publish duration measures how long Pub blocks the caller, which is not broker
latency: NATS buffers the write and returns immediately while RabbitMQ does a
synchronous channel write, so the two are not comparable that way. Transit
latency needs a wire stamp, so Message grows a published_at field; it is clamped
at zero to absorb clock skew, and the buckets start at 100us so that two fast
brokers don't collapse into a single bucket.

The decorator is wrapped inside the tenant-pubs gate so that gated drops are not
counted as publishes, and it stamps a copy rather than the caller's Message,
which callers also hand to the durable queue. Mixed fleets are safe in both
directions: older engines ignore the unknown JSON field, their unstamped
messages simply skip the transit observation, and omitzero keeps published_at
off durable messages entirely.
@igor-kupczynski
igor-kupczynski changed the base branch from main to nats-pubsub-backend July 28, 2026 14:06
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Optional test failure: The load-deadlock job failed on this PR (optimistic-scheduling=true). This check is non-mandatory and does not block merging, but may be worth investigating. View logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation engine Related to the core Hatchet engine sdk-go Related to the Go SDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant