Skip to main content

Pulsar

Apache Pulsar serves Prometheus text at /metrics on its web port (:8080). A standalone process hosts the broker, the BookKeeper managed-ledger storage, and the functions worker together, and exposes all of their metrics on that one endpoint. The OpenTelemetry Collector's prometheus receiver scrapes it, collecting 270+ metrics from Pulsar 2.x+ across broker message rate / throughput / backlog and publish latency, per-topic and per-subscription rates and backlog, the BookKeeper storage write-latency path, and the JVM runtime. This guide configures the receiver and ships metrics to base14 Scout.

Prerequisites​

RequirementMinimumRecommended
Apache Pulsar2.x4.x (4.0.3)
OTel Collector Contrib0.90.00.153.0
base14 ScoutAny-

Before starting:

  • The Pulsar web port (8080) must be reachable from the host running the Collector.
  • Metrics are enabled by default - no configuration changes are needed on the Pulsar side.
  • A Scout account and OTLP endpoint.
  • OTel Collector installed - see Docker Compose Setup.

What You'll Monitor​

Metrics are grouped into three tiers by how you use them. Scrape Core always, alert on Operational, and reach for Diagnostic during an incident or capacity review.

A few things shape this surface, and they matter for every tier below:

  • up is the liveness signal here. The prometheus receiver emits up = 1 when /metrics responds - that is the scrape liveness signal. pulsar_health (Pulsar 4.0.0+) is Pulsar's own broker self health-check (1 = healthy), and broker-wide backlog (pulsar_broker_msg_backlog) is the headline "are consumers keeping up" signal.
  • Standalone collapses several roles into one /metrics. Broker aggregates (pulsar_broker_*), per-topic series (pulsar_* with a topic label), per-subscription series (pulsar_subscription_*), and the BookKeeper managed-ledger internals (pulsar_ml_*) all come from the same endpoint. A real cluster splits these across broker, bookie, and function-worker processes - the metric names are identical, so the same scrape config applies per process.
  • Two write paths, two latency histograms. pulsar_broker_publish_latency is the producer-facing publish latency, while pulsar_storage_write_latency_* and pulsar_storage_ledger_write_latency_* are the BookKeeper persistence latencies behind it. Compare them to localize a publish slowdown to broker vs storage.
  • Histograms expand into buckets. The _le_* / _sum / _count families are Pulsar's native bucket layout, not OTel exponential histograms; the receiver keeps each bucket as its own series.
  • Managed-ledger names keep their case as exposed. pulsar_ml_AddEntryErrors, pulsar_ml_ReadEntriesRate, and pulsar_ml_StoredMessagesSize reproduce BookKeeper's camel-case exactly - they are not normalized to snake_case.
  • The per-topic, per-subscription, and managed-ledger families warm up under traffic. They populate only after a topic exists and produces / consumes - a fresh idle standalone shows mostly broker-level zeros plus the JVM runtime. Left enabled, they populate once traffic flows.
  • The JVM / Jetty / Caffeine / process families are the host runtime, not Pulsar product signals: jvm_*, jetty_*, caffeine_cache_*, process_*. Filter to pulsar_.*|up with a keep rule if the runtime is covered elsewhere.

Core - is it up, moving messages, and keeping consumers fed​

MetricWhat it tells you
upScrape liveness - 1 = the Pulsar metrics endpoint responded. The liveness signal on this surface.
pulsar_healthBroker self health-check result - 1 = healthy (Pulsar 4.0.0+).
pulsar_broker_rate_inBroker-wide messages published per second - headline ingest throughput.
pulsar_broker_rate_outBroker-wide messages dispatched per second - headline delivery throughput.
pulsar_broker_msg_backlogBroker-wide unacked-message backlog - the headline "are consumers keeping up" signal.
pulsar_broker_publish_latencyProducer-facing publish latency - the write SLO.

Operational - what to alert on​

GroupMetricsWhat it tells you
Broker throughputpulsar_broker_throughput_in, pulsar_broker_throughput_outBroker-wide inbound / outbound bytes per second.
Broker entity countspulsar_broker_topics_count, pulsar_broker_subscriptions_count, pulsar_broker_producers_count, pulsar_broker_consumers_count, pulsar_active_connectionsTopics, subscriptions, producers, consumers, and live client connections on the broker.
Dispatch backpressurepulsar_broker_pending_bytes_to_dispatch, pulsar_broker_throttled_connectionsBytes queued for dispatch and connections being throttled - producers hitting rate limits.
Lookup pathpulsar_broker_lookup_failures_total, pulsar_broker_lookup_pending_requests, pulsar_broker_topic_load_pending_requestsFailed topic lookups, lookups in flight, and queued topic-load requests.
Broker storagepulsar_broker_storage_size, pulsar_broker_storage_write_rate, pulsar_broker_storage_read_rate, pulsar_broker_storage_read_cache_misses_rate, pulsar_broker_storage_backlog_quota_exceeded_evictions_totalStored size, write / read rates, read-cache misses, and messages evicted by quota policy (data loss).
Per-topic rate / throughputpulsar_rate_in, pulsar_rate_out, pulsar_throughput_in, pulsar_throughput_out, pulsar_publish_rate_limit_timesPer-topic publish / dispatch message rate, bytes per second, and rate-limit hits.
Per-topic backlogpulsar_msg_backlog, pulsar_storage_backlog_size, pulsar_storage_backlog_age_seconds, pulsar_storage_backlog_quota_limitPer-topic unacked backlog, backlog bytes, age of the oldest unacked message, and the configured quota.
Storage write latencypulsar_storage_write_latency_sum, pulsar_storage_write_latency_count, pulsar_storage_write_latency_le_*Per-topic persistence write-latency distribution - the storage write SLO behind a publish.
Per-subscription healthpulsar_subscription_back_log, pulsar_subscription_unacked_messages, pulsar_subscription_blocked_on_unacked_messages, pulsar_subscription_msg_rate_redeliver, pulsar_subscription_msg_drop_rate, pulsar_subscription_msg_rate_outPer-subscription backlog, unacked count, the blocked flag, redelivery and drop rate, and dispatch rate.
Write errorspulsar_ml_AddEntryErrors, pulsar_ml_ReadEntriesErrorsManaged-ledger entry-write / read errors - the BookKeeper path is failing.
Topic load / metadatapulsar_topic_load_failed_count, pulsar_metadata_store_ops_latency_msFailed topic loads and metadata-store (ZooKeeper / RocksDB) operation latency - slow metadata stalls topic ops cluster-wide.

Diagnostic - for investigation and tuning​

Higher cardinality; reach for these during an incident or capacity review.

GroupRepresentative metricsWhen you reach for it
Per-topic detailpulsar_average_msg_size, pulsar_in_bytes_total, pulsar_out_bytes_total, pulsar_in_messages_total, pulsar_out_messages_total, pulsar_storage_logical_size, pulsar_storage_offloaded_size, pulsar_storage_write_rate, pulsar_delayed_message_index_size_bytesPer-topic byte / message totals, logical and offloaded storage size, and delayed-delivery index size.
Entry-size / ledger-write histogramspulsar_entry_size_sum / _count / _le_*, pulsar_storage_ledger_write_latency_sum / _count / _le_*The message-entry size distribution and the BookKeeper ledger write latency behind the topic write path.
Per-namespace countspulsar_producers_count, pulsar_consumers_count, pulsar_subscriptions_count, pulsar_topics_countPer-namespace producer / consumer / subscription / topic accounting.
Per-subscription detailpulsar_subscription_consumers_count, pulsar_subscription_msg_ack_rate, pulsar_subscription_msg_throughput_out, pulsar_subscription_msg_rate_expired, pulsar_subscription_delayed, pulsar_subscription_in_replayConsumer count, ack rate, outbound throughput, TTL expiry, and delayed / replay detail on a subscription.
Subscription progress timestampspulsar_subscription_last_acked_timestamp, pulsar_subscription_last_consumed_timestamp, pulsar_subscription_last_mark_delete_advanced_timestampA stalled timestamp marks a stuck consumer.
Subscription server-side filterspulsar_subscription_filter_accepted_msg_count, pulsar_subscription_filter_rejected_msg_count, pulsar_subscription_filter_rescheduled_msg_countOutcomes when a subscription has a server-side selector attached.
Managed-ledger internalspulsar_ml_AddEntryBytesRate, pulsar_ml_ReadEntriesRate, pulsar_ml_StoredMessagesSize, pulsar_ml_NumberOfMessagesInBacklog, pulsar_ml_MarkDeleteRate, pulsar_ml_cache_*, pulsar_ml_AddEntryLatencyBuckets, pulsar_ml_LedgerSwitchLatencyBucketsBookKeeper add / read rates, stored size, mark-delete rate, the read cache, and the latency bucket layouts.
Schema registry / topic loadpulsar_schema_get_ops_latency, pulsar_schema_put_ops_latency, pulsar_topic_load_rate_s, pulsar_topic_load_times, pulsar_topic_load_time_*_msSchema read / write latency and topic-load rate / time percentiles.
Transactions / resource groupspulsar_txn_tb_active_total, pulsar_txn_tb_committed_total, pulsar_txn_tb_aborted_total, pulsar_resource_group_aggregate_usage_secs, pulsar_resource_group_calculate_quota_secsTransaction-buffer counts and resource-group usage / quota timings.
Functions workerpulsar_function_worker_*Functions-worker scheduling, rebalance, instance-count, and leader state.
Web thread poolpulsar_web_executor_active_threads, pulsar_web_executor_idle_threads, pulsar_web_executor_max_threadsWeb / admin thread-pool sizing.
JVM / Jetty / Caffeine / process runtimejvm_memory_bytes_used, jvm_memory_direct_bytes_used, jvm_gc_collection_seconds, jvm_threads_current, jetty_*, caffeine_cache_*, process_cpu_seconds_total, process_resident_memory_bytes, log4j2_appender_totalHost runtime - heap, off-heap direct memory, GC, threads, the admin HTTP server, in-process caches, and OS process stats.
Scrape metascrape_duration_seconds, scrape_samples_scrapedReceiver-side scrape health, not from Pulsar.

Full metric list: run curl -s http://localhost:8080/metrics against your Pulsar broker.

Key Alerts to Configure​

Threshold guidance for the most useful Core and Operational series. These are starting points; tune them to your workload.

MetricThresholdWhy it matters
up== 0 for > 1mThe metrics endpoint stopped responding - check the broker process and the web port.
pulsar_health< 1The broker self health-check is failing (Pulsar 4.0.0+) - check BookKeeper and the metadata store.
pulsar_broker_msg_backlog / pulsar_storage_backlog_age_secondsBacklog rising vs baseline, or age climbingConsumers are not keeping up - scale consumers or investigate slow processing.
pulsar_subscription_blocked_on_unacked_messages== 1A subscription hit its unacked-message limit - consumers are not acking; check consumer health.
pulsar_subscription_msg_rate_redeliverRising vs baselineConsumers are failing to ack and messages are being redelivered - check consumer errors and ack timeout.
pulsar_ml_AddEntryErrors> 0The BookKeeper write path is failing - check bookie health and disk.
pulsar_broker_publish_latency / pulsar_storage_write_latency_*p99 rising vs baselineWrites are slow - compare broker vs storage latency to localize.
rate(pulsar_broker_storage_backlog_quota_exceeded_evictions_total)> 0Messages are being evicted by quota policy (data loss) - raise the quota or drain the backlog.
rate(pulsar_broker_lookup_failures_total)> 0Clients cannot resolve topic ownership - check the metadata store and broker load.
pulsar_metadata_store_ops_latency_msp99 rising vs baselineSlow metadata operations stall topic load / unload cluster-wide - check ZooKeeper / RocksDB.

The liveness, health, blocked-flag, and error / eviction alerts read states or events; the latency and backlog alerts are relative to your own baseline. Set absolute thresholds only against your own workload and provisioned capacity.

Access Setup​

Pulsar exposes Prometheus metrics natively - no exporter sidecar is needed. The /metrics endpoint on the web port (8080) is unauthenticated by default.

Bring up a standalone instance, which runs the broker, BookKeeper storage, and functions worker in one process:

Run Pulsar standalone
docker run -d --name pulsar \
-p 6650:6650 -p 8080:8080 \
apachepulsar/pulsar:4.0.3 \
bin/pulsar standalone

Verify the metrics endpoint is serving:

Verify access
# Confirm the broker is healthy
curl -s http://localhost:8080/admin/v2/brokers/health

# Confirm the Prometheus endpoint responds
curl -s http://localhost:8080/metrics | head -20

A secured production cluster fronts the web port with TLS and authentication. When metrics are behind auth, point the scrape job at https and supply the broker's credentials to the receiver; restrict the endpoint at the network layer either way.

Configuration​

The recommended config scrapes Pulsar's /metrics and keeps only the Pulsar series with a metric_relabel_configs keep filter, dropping the JVM / Jetty / Caffeine runtime noise the endpoint also emits:

config/otel-collector.yaml
receivers:
prometheus:
config:
scrape_configs:
- job_name: pulsar
scrape_interval: 10s
static_configs:
- targets:
- ${env:PULSAR_HOST}:8080 # Pulsar web port; default /metrics
metric_relabel_configs:
- source_labels: [__name__]
regex: "pulsar_.*|up"
action: keep

processors:
resource:
attributes:
- key: environment
value: ${env:ENVIRONMENT}
action: upsert
- key: service.name
value: ${env:SERVICE_NAME}
action: upsert

batch:
timeout: 10s
send_batch_size: 1024

exporters:
otlphttp/b14:
endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT}
tls:
insecure_skip_verify: true

service:
pipelines:
metrics:
receivers: [prometheus]
processors: [resource, batch]
exporters: [otlphttp/b14]

The keep filter scopes collection to the pulsar_* product families plus the up liveness series, and drops the jvm_* / jetty_* / caffeine_cache_* / process_* host-runtime series. Remove the filter if you want the runtime in Scout too.

Environment Variables​

.env
PULSAR_HOST=localhost
ENVIRONMENT=your_environment
SERVICE_NAME=your_service_name
OTEL_EXPORTER_OTLP_ENDPOINT=https://<your-tenant>.base14.io

Verify the Setup​

Start the Collector and check for metrics within 60 seconds:

Verify metrics collection
# Check Collector logs for scraped Pulsar metrics
docker logs otel-collector 2>&1 | grep -i "pulsar"

# Confirm the broker health metric is exposed
curl -s http://localhost:8080/metrics | grep pulsar_health

# Confirm broker throughput is exposed
curl -s http://localhost:8080/metrics | grep pulsar_broker_rate_in

Troubleshooting​

Connection refused on port 8080​

Cause: The Collector cannot reach the Pulsar web endpoint.

Fix:

  1. Verify Pulsar is running: docker ps | grep pulsar or bin/pulsar-admin brokers list.
  2. Confirm the web port is open: curl http://localhost:8080/metrics.
  3. Check firewall rules if the Collector runs on a separate host.

Only broker-level metrics appear​

Cause: There is no topic with traffic yet. The per-topic (pulsar_* with a topic label), per-subscription (pulsar_subscription_*), and managed-ledger (pulsar_ml_*) families populate only once a topic exists and produces / consumes.

Look at: the broker-level series first - if pulsar_broker_* populates but the per-topic / per-subscription / pulsar_ml_* families read 0 or are absent, the broker is healthy and simply idle.

Fix:

  1. Create a topic and produce a test message: bin/pulsar-client produce persistent://public/default/test -m "hello".
  2. Run a consumer so the subscription and managed-ledger families advance.
  3. Re-check: curl -s http://localhost:8080/metrics | grep pulsar_subscription.

Publish latency is high​

Cause: Writes are slow, either at the broker or in BookKeeper storage.

Look at: pulsar_broker_publish_latency against the storage latencies - pulsar_storage_write_latency_* and the Diagnostic pulsar_storage_ledger_write_latency_*. If the storage latency tracks the publish latency, the bottleneck is BookKeeper; if not, it is broker-side. Check pulsar_ml_AddEntryErrors for write-path failures.

Fix:

  1. Inspect bookie health and disk if storage latency is climbing.
  2. Check broker CPU and jvm_gc_collection_seconds if the broker side is slow.

No metrics appearing in Scout​

Cause: Metrics are collected but not exported.

Fix:

  1. Check Collector logs for export errors: docker logs otel-collector.
  2. Verify OTEL_EXPORTER_OTLP_ENDPOINT is set correctly.
  3. Confirm the pipeline includes both the receiver and the exporter.

FAQ​

Does this work with Pulsar running in Kubernetes?​

Yes. Set the Collector's targets to the broker's web-port service DNS (e.g., pulsar-broker.pulsar.svc.cluster.local:8080). With the Apache Pulsar Helm chart the broker web port is exposed by default. The Collector can run as a sidecar or a DaemonSet.

What is the difference between standalone and a cluster for monitoring?​

Standalone runs the broker, BookKeeper bookie, and functions worker in one process, so all metrics come from one /metrics. A production cluster splits those roles across separate processes - the metric names are the same, so add a scrape job per broker, bookie, and function-worker endpoint:

config/otel-collector.yaml (cluster)
receivers:
prometheus:
config:
scrape_configs:
- job_name: pulsar-broker
static_configs:
- targets:
- broker-1:8080
- broker-2:8080
- job_name: pulsar-bookie
static_configs:
- targets:
- bookie-1:8000
- bookie-2:8000
- job_name: pulsar-function-worker
static_configs:
- targets:
- fn-worker-1:6750

Each process is scraped independently and identified by its instance label.

Why do I only see broker-level metrics?​

The per-topic, per-subscription, and managed-ledger families populate only once a topic exists and has active producers or consumers. An idle standalone shows mostly broker-level zeros plus the JVM runtime. Produce and consume on a topic to advance them.

Why are the pulsar_ml_* metrics in camel-case?​

The BookKeeper managed-ledger family (pulsar_ml_AddEntryErrors, pulsar_ml_ReadEntriesRate, pulsar_ml_StoredMessagesSize, and the rest) keeps BookKeeper's camel-case by design. The prometheus receiver reproduces the name exactly as exposed and does not normalize it to snake_case.

What's Next?​

  • Create Dashboards: Explore pre-built dashboards or build your own. See Create Your First Dashboard.
  • Monitor More Components: Add monitoring for Kafka, RabbitMQ, and other components.
  • Fine-tune Collection: The Diagnostic tier is higher cardinality. You can scope its collection with metric_relabel_configs if you need to, and reach for it during incident investigation.
Was this page helpful?