seppv0.1.0
Observability

Metrics

How sepp exposes metrics over OTLP and Prometheus.

Sepp publishes metrics through two independent channels: OTLP push to a collector and a Prometheus scrape endpoint. You can enable either, both or neither.

The [metrics] section requires a restart to apply. All fields, with their defaults:

[metrics]
enabled = false
otlp_endpoint = "http://localhost:4317"
export_interval_ms = 5000
prometheus_enabled = false
prometheus_listen_addr = "0.0.0.0:9464"

Enabling OTLP metrics

Set enabled = true and sepp will push metrics to the OpenTelemetry collector at otlp_endpoint every export_interval_ms milliseconds. Both the counter and histogram instruments and the observable gauges are exported. The collector sees the same catalogue described below regardless of whether it arrives via OTLP or Prometheus.

The otlp_endpoint defaults match the collector's standard gRPC receiver, so if you run the collector locally on the default port no further wiring is needed. Point it elsewhere to talk to a sidecar or a hosted backend that accepts OTLP.

Prometheus scrape endpoint

Set prometheus_enabled = true. Sepp opens a plain HTTP/1.1 listener on prometheus_listen_addr and serves GET /metrics in the standard Prometheus exposition format.

The endpoint carries no authentication. Bind it to a private interface (e.g. 127.0.0.1:9464) when the host is reachable from multiple networks and configure Prometheus to scrape it:

scrape_configs:
  - job_name: sepp
    static_configs:
      - targets: ["localhost:9464"]

OTLP and Prometheus are independent. Enabling both is fine — the Prometheus reader and the OTLP periodic reader coexist inside the same meter provider and both see every instrument.

Metric catalogue

Every metric is prefixed sepp. and tagged with labels where shown. Counters are cumulative since the process started; histograms record milliseconds; gauges are point-in-time snapshots polled by the exporter.

Job lifecycle counters

These track jobs as they move through the queue. All are labeled with queue.

MetricTypeLabelsDescription
sepp.jobs.enqueuedCounterqueueJobs accepted for enqueue (excludes duplicates, includes scheduled)
sepp.jobs.reservedCounterqueueJobs handed to a worker
sepp.jobs.ackedCounterqueueJobs acknowledged as complete
sepp.jobs.nackedCounterqueueJobs nacked (retried or dead-lettered)
sepp.jobs.dead_letteredCounterqueue, causeJobs moved to the dead-letter store. cause is one of: attempts_exhausted, rejected (worker forced it), lease_expired, admin
sepp.jobs.deduplicatedCounterqueueEnqueues that matched an active idempotency key
sepp.jobs.rejectedCounterqueue, reasonEnqueues rejected at validation before reaching the committer. reason is one of: unknown_queue, payload_too_large, encoding_not_allowed, job_type_not_allowed, custom_entries_too_many, custom_map_too_large, custom_key_too_long, queue_name_too_long, job_type_name_too_long, idempotency_key_too_long, scheduled_too_far, invalid_request, queue_full, queue_closing

Reserve and sweep counters

MetricTypeLabelsDescription
sepp.reserve.emptyCounterqueueA reserve call returned no jobs for this queue
sepp.sweep.promotionsCounterqueueScheduled jobs whose time arrived and were moved to ready
sepp.sweep.lease_redeliveriesCounterqueueLeases that expired and the job was put back in ready
sepp.sweep.dedup_expirationsCounterqueueIdempotency keys that aged out of their dedup window
sepp.dead_letters.expiredCounterDead-letter records pruned by retention expiry
sepp.dead_letters.drainedCounterDead-letter records wiped by an admin drain

Request metrics

MetricTypeLabelsDescription
sepp.requestsCountermethod, outcomegRPC calls. method is the RPC name (enqueue_batch, reserve, etc.). outcome is ok or error
sepp.request.durationHistogrammethodRPC wall-clock time in milliseconds

Queue-depth gauges

Each gauge emits a data point per queue with a queue label.

MetricTypeDescription
sepp.queue.readyGaugeJobs available for immediate reservation
sepp.queue.scheduledGaugeJobs waiting for their scheduled_at time
sepp.queue.inflightGaugeJobs currently leased to a worker
sepp.queue.dead_letteredGaugeJobs in the dead-letter store

Commit and command-queue metrics

MetricTypeLabelsDescription
sepp.commit.durationHistogramTime spent inside one committer cycle (apply + fsync) in milliseconds
sepp.command_queue.depthGaugeNumber of pending commands waiting for the committer thread. Spikes under burst load; sustained elevation means write throughput is saturated

How cycle metrics flush

Job counts aren't incremented one-by-one on the hot path. The committer accumulates per-cycle deltas — how many enqueues, reserves, acks, etc. happened in one commit — and flushes them to the OTel instruments in a batch after the cycle finishes. This means counters update at the cadence of the committer (typically tens of milliseconds), not every individual operation and a single cycle's worth of data is atomic from the collector's perspective.

Queue-depth gauges are refreshed the same way: the committer snapshots the in-memory indexes after each cycle and stores the snapshot behind an ArcSwap. The gauge callbacks read the latest snapshot at export time, so the gauges always reflect the state as of the last completed commit.

Building a dashboard

The counters and gauges above give you enough to build the essential panels:

  • Job throughput — rate of sepp.jobs.enqueued and sepp.jobs.acked stacked, broken down by queue. A gap between enqueue and ack rate is your backlog growing.
  • Queue healthsepp.queue.ready as a timeseries per queue, overlaid with sepp.queue.inflight. Inflight climbing while ready stays flat means jobs are taking too long to process.
  • Error signalssepp.jobs.rejected by reason (a spike in queue_full means you hit a depth cap) and sepp.jobs.dead_lettered by cause (a spike in lease_expired suggests workers are too slow or crashing).
  • Latencysepp.request.duration p50/p95/p99 per method and sepp.commit.duration p95 to spot storage contention.
  • Backpressuresepp.command_queue.depth. A healthy server hovers near zero. Sustained elevation means the committer can't keep up with write load.

The admin UI dashboard gives you the live state without any setup. Prometheus and Grafana give you history, retention and alert rules. See the observability overview for wiring them together.

On this page