Monitoring and Alerting
Monitor ObjectBox Sync Server in production using its Prometheus metrics endpoint, visualize metrics with Grafana, and set up alerting to get notified when something needs your attention.
ObjectBox Sync Server exposes its runtime metrics in the Prometheus text format. This allows you to integrate the Sync Server into a standard monitoring stack: Prometheus scrapes and stores the metrics, Grafana visualizes them in dashboards, and either of the two can actively alert you (e.g. via email or Slack) when something goes wrong.
For a quick look at live server statistics without any external tooling, you can also use the "Sync Statistics" page of the Admin web UI. The Prometheus endpoint is the right choice for continuous monitoring in production: long-term storage, custom dashboards, and alerting.
The metrics endpoint
The metrics endpoint is part of the admin HTTP server (the same server that serves the Admin web UI) and is available at the path /api/sync/prometheus. For example, with the default developer admin configuration (adminBind set to http://127.0.0.1:9980), the metrics URL is http://127.0.0.1:9980/api/sync/prometheus. You can verify it works by opening it in a browser or using curl:
curl http://127.0.0.1:9980/api/sync/prometheusThis returns the current metric values in plain text, for example:
obx_uptime 6231 1781430000000
# HELP obx_messages Number of messages exchanged
# TYPE obx_messages counter
obx_messages{type="recv"} 6824 1781429999641
obx_messages{type="sent"} 7012 1781429999641
...The metrics endpoint currently does not require authentication. Since it shares the host and port with the Admin web UI, apply the same precautions: do not expose the admin port to untrusted networks. Typically, you bind it to localhost or an internal network interface only, and let Prometheus scrape it from within that network.
Metric values are sampled from the internal statistics collector, which updates once per second. Each metric line includes an explicit timestamp (milliseconds since epoch) matching the time the values were sampled.
Available metrics
All metrics are prefixed with obx_. Most are counters: values that only ever increase ("total number of X since server start") and reset to zero when the server restarts. In Prometheus, you usually look at counters through rate() or increase() to get "X per second" or "X in the last N minutes". Gauges in contrast represent a current value that can go up and down (e.g. the number of currently connected clients).
General
obx_uptime(counter): server uptime in seconds. A value lower than the previous one indicates a server restart.
Clients and connections
obx_connected_clients(gauge): number of currently connected sync clients.obx_connects(counter): total number of client connections established.obx_login_successes(counter): total number of successful client logins (including authentication).obx_heartbeats_received(counter): total number of heartbeats received from clients.obx_client_failures{type="..."}(counter): failures related to clients, bytypelabel:login: general login failureslogin_auth_unavailable: an authentication component was unavailablelogin_user_bad_credentials: clients supplied bad credentialslogin_user_no_permission: authenticated users lacked the permission to connectmsg_sent: errors while sending messagesheartbeat: clients disconnected due to missing heartbeatsdisconnect: processing was aborted because the client disconnected
obx_errors{type="..."}(counter): server-side errors, bytypelabel:protocol: protocol errors, e.g. offending clientsin_handler: errors inside message handlers
Messages and data exchange
obx_messages{type="recv"|"sent"}(counter): number of sync protocol messages received from/sent to clients.obx_message_bytes{type="recv"|"sent"}(counter): number of bytes received/sent via messages (measured at the application level; network-level numbers may differ).obx_full_syncs(counter): number of full syncs performed (clients synchronizing from scratch rather than via delta sync).
Transactions (sync data flow)
obx_txs_applied{from="client"|"local"}(counter): number of transactions applied to the server database, split by origin: received from sync clients vs. initiated locally on the server.obx_client_tx_ops_applied(counter): total number of operations (e.g. puts and removes) inside applied client transactions.obx_client_tx_bytes_applied(counter): total size in bytes of applied client transactions.obx_skipped_tx_dups(counter): transactions skipped because they were already applied before (duplicates, e.g. after reconnects).obx_txs_sent{type="..."}(counter): transactions sent to clients, bytypelabel:historic: from the sync history, e.g. to catch up reconnecting clientshistoric_merged: additional transactions merged into historic update messagesnew: "live" updates pushed to connected clients
obx_async_db_commits(counter): database commits done by the asynchronous transaction queue.obx_client_txs_behind(gauge): how many transactions the most-behind connected client is behind the current server state.obx_tx_history_sequence(counter): the current sequence number of the transaction log history.obx_tx_log_age(histogram): age of incoming transaction logs when they are stored on the server, i.e. the time (in seconds) between a transaction being created at its source and it being applied here. This is an indicator of end-to-end sync latency; e.g. high values can point to clients that were offline for a while submitting old data, or to a server falling behind. Buckets: 0.1, 1, 10, 60 seconds, 1 hour, 1 day, +Inf.
Server internals
obx_queue_length{type="tasks"|"async"}(gauge): length of internal queues:tasksis the main worker pool queue,asyncis the asynchronous database transaction queue. Persistently growing values indicate the server cannot keep up with the load.obx_task_counts{type="completed"|"failed"|"continued"}(counter): number of internal tasks completed, failed, and continued (rescheduled).obx_task_time{place="...",priority="..."}(histogram): time (in seconds) tasks spentin_queue(waiting) andprocessing(executing), per task priority (low_throttle,low_fail,regular,regular_quick,high). Useful to detect server overload: increasing queue times mean tasks wait longer before being processed.obx_cache{from="..."}(counter): hits and misses of internal ID-mapping caches (global_ids_hits/global_ids_misses,peer_ids_hits/peer_ids_misses,peer_local_ids_hits/peer_local_ids_misses).obx_cache_size{from="global_ids"|"peer_ids"}(gauge): number of objects in the internal ID-mapping caches.
Cluster
The following values are available when running a Sync Cluster:
obx_cluster_peer_state(gauge): current cluster role of this server: 0 = unknown (e.g. not started), 1 = leader, 2 = follower, 3 = candidate (an election is in progress).obx_connected_peers(gauge): number of currently connected cluster peers.obx_forwarded_messages{type="recv"|"sent"}(counter): messages forwarded between cluster peers (e.g. writes forwarded from followers to the leader).
MongoDB connector
The following values are available when using the MongoDB Sync Connector:
obx_mongodb_changes{type="..."}(counter): changes received from the MongoDB change stream, bytypelabel:by_others: changes made by other applications, to be syncedby_us: changes the Sync Server itself wrote to MongoDBfiltered_out: changes not relevant for sync, e.g. unknown collectionsunknown_operation: change stream operations that are not supportedskipped_without_doc: changes skipped because no document was attached
obx_mongodb_errors{type="..."}(counter): errors in the MongoDB connector, by source:mongodb: errors interacting with MongoDBobjectbox: general errors on the ObjectBox sideobjectbox_change_prepareandobjectbox_change_apply: errors while preparing/applying incoming changes to ObjectBox
obx_mongodb_warnings(counter): total number of warnings issued by the MongoDB connector.obx_mongodb_change_stream_opens(counter): number of times the connector opened the MongoDB change stream (since version 2026-06-12). A value greater than 1 means the stream was reopened, e.g. after change stream errors or a full sync.obx_mongodb_connector_state(gauge): current connector state: 0 = created, 1 = running, 2 = connected, 3 = failure, 4 = stopped.obx_mongodb_connector_running(gauge): 1 if the connector thread is running, 0 otherwise. A simple flag to alert on a stopped connector.obx_mongodb_change_stream_open(gauge): 1 while the MongoDB change stream is open, 0 otherwise (since version 2026-06-12). The stream is expected to be closed before the initial import from MongoDB was done and during a full sync; apart from that, a closed stream means changes from MongoDB are currently not received (see alerting below).obx_mongodb_full_sync_active(gauge): 1 while a full sync with MongoDB is in progress, 0 otherwise.obx_mongodb_initial_import_required(gauge): 1 if the initial import from MongoDB has not been done yet, 0 otherwise.
Hooking up Prometheus
To let Prometheus scrape the Sync Server, add a scrape job to your Prometheus configuration (prometheus.yml). Note that metrics_path must be set, as the Sync Server does not use the default /metrics path:
After reloading Prometheus, the target should show up as "UP" on the Prometheus status page (Status → Targets). You can then explore the metrics in the Prometheus expression browser, e.g. by querying obx_connected_clients or rate(obx_messages[5m]).
If you run the Sync Server in Docker (e.g. via Docker Compose alongside Prometheus), make sure the admin HTTP server is reachable from the Prometheus container: bind it to a non-localhost interface inside the container (e.g. --admin-bind 0.0.0.0:9980) and use the container/service name as the target (e.g. sync-server:9980). Do not publish the admin port to the public network.
Visualizing with Grafana
Once Prometheus collects the metrics, add Prometheus as a data source in Grafana (Connections → Data sources → Prometheus, then enter the Prometheus server URL). You can then build a Sync Server dashboard. Useful starter panels:
Connected clients (
obx_connected_clients) - the most basic health indicator of your sync deployment.Sync activity - transactions applied per second:
rate(obx_txs_applied[5m]), optionally split by thefromlabel.Network throughput - bytes per second:
rate(obx_message_bytes[5m]), split by thetypelabel (recv/sent).Errors and failures -
increase(obx_errors[5m])andincrease(obx_client_failures[5m]), split bytype. In a healthy system, these stay at or near zero.Server load - queue lengths (
obx_queue_length) and task queue waiting time, e.g. the 95th percentile:histogram_quantile(0.95, rate(obx_task_time_bucket{place="in_queue"}[5m])).Uptime (
obx_uptime) - drops to zero indicate restarts.
If you use the MongoDB Sync Connector, also add obx_mongodb_connector_running and obx_mongodb_change_stream_open (both should constantly be 1 in normal operation) and rate(obx_mongodb_changes[5m]) to see data flowing in from MongoDB.
Alerting
Dashboards are great for analysis, but you do not want to stare at them all day. Both Prometheus (via Alertmanager) and Grafana Alerting can evaluate alert rules and notify you actively, e.g. via email, Slack, PagerDuty, or a webhook. Which one to use is mostly a matter of preference: if you already run Grafana, its built-in alerting is the quickest way to get notifications; Alertmanager is the classic choice in Prometheus-centric setups.
The general pattern for counters is: alert when the counter increased during a recent time window, i.e. when errors are recurring - a single hiccup (e.g. one failed login due to a typo) usually does not warrant a notification.
Example 1: Recurring MongoDB connector errors
When the MongoDB Sync Connector repeatedly fails (e.g. MongoDB is down or misconfigured), you want to know about it quickly - data is not being synchronized in the meantime. The following Prometheus alert rules fire when the connector is not running, when the change stream (which receives changes from MongoDB) stays closed, or when errors keep occurring over a 15-minute window:
Note: the change stream rule needs Sync Server version 2026-06-12 or later (which also made the connector recover the change stream after errors automatically; the alert catches cases where that recovery does not succeed, e.g. MongoDB staying unreachable).
Example 2: Spike in client login failures
A sudden burst of login failures can indicate a misconfigured client release, expired credentials (e.g. an outdated JWT signing key), or someone probing your server. This rule fires when there are more than 10 login failures within 5 minutes:
More alerting ideas
Depending on your deployment, also consider alerting on:
Server restarts:
resets(obx_uptime[1h]) > 0detects (repeated) restarts; combine withfor:to catch crash loops.Server errors:
increase(obx_errors[15m]) > 0- protocol and handler errors should be rare; recurring ones deserve a look.Overload:
obx_queue_lengthstaying high or growing for several minutes means the server cannot keep up with the load.Cluster without a leader: in a cluster setup, a cluster should always have exactly one leader; alert when
count(obx_cluster_peer_state == 1) < 1for more than a minute.No connected clients:
obx_connected_clients == 0during hours when you expect activity may indicate a network or certificate issue in front of the server.MongoDB change stream flapping:
increase(obx_mongodb_change_stream_opens[1h]) > 3- the connector reopens the change stream automatically after errors, but frequent reopens point to an unstable connection to MongoDB.Missing metrics: rules like
obx_mongodb_connector_running == 0do not fire when the metric is absent (e.g. the Sync Server is down or unreachable, so there is no data at all). Cover this case withup{job="objectbox-sync-server"} == 0(Prometheus sets theupmetric per scrape target) orabsent(obx_mongodb_connector_running); in Grafana, configure the alert rule's "no data" state to also fire the alert.
To set up the same alerts in Grafana instead, create an alert rule (Alerting → Alert rules → New alert rule) with the same PromQL expression as the query, and attach a notification policy (contact point) such as email or Slack.
Last updated