Metrics
With observability.metrics.prometheus.mode set to "enabled", Andromeda serves Prometheus metrics at /metrics on the management port. This page lists every metric it emits, what its labels hold, and which ones to alert on.
Metrics are off by default, and need management.mode set to "separate-port" or "shared-port". Every name starts with andromeda_, and every metric carries a # HELP line. Durations end in _duration_seconds and sizes in _bytes, and everything specific to PostgreSQL starts with andromeda_postgresql_.
Label values come from fixed lists, apart from tenant (the tenant name), the HTTP endpoint (a route template) and the HTTP status (a status code). An outcome label is ok or error, and cancelled where an operation can be cancelled.
A gauge or counter stays in the scrape for as long as the value it reports holds, even when nothing changes it for hours. Only the pool, table and history chunk series carry a tenant label, and they are the only series that ever leave the scrape. They go five minutes after their tenant is unloaded.
Scraping
The scrape answers in the Prometheus text format, with Content-Type: text/plain; version=0.0.4. Point Prometheus at the management port, never the data port.
scrape_configs:
- job_name: andromeda
metrics_path: /metrics
static_configs:
- targets: ["andromeda:1086"]
With management.transport = "tls", add scheme: https and the CA under tls_config. In "shared-port" mode, the scrape is on the data port instead. Monitoring covers the management port and the layouts.
Metrics are kept in the broker's memory and start from zero when it restarts.
Build
andromeda_build_info(gauge,version): always1.versionis the version of the running broker, soandromeda_build_infojoined oninstanceshows which version each broker runs.
HTTP requests
These cover every request the API listener handles. In "separate-port" mode, the management port isn't counted. In "shared-port" mode, the scrape itself isn't counted.
andromeda_http_requests_total(counter,endpoint,method,status): requests answered.methodis the request method, orOTHERfor a method HTTP doesn't define.endpointis the route the request matched, such as/ngsi-ld/v1/entities/{entity_id}, never the raw path, andunmatchedfor a request no route matched.statusis the response status code.andromeda_http_request_duration_seconds(histogram,endpoint,method,status): how long each request took to answer, from 5 ms to 10 s.andromeda_http_requests_pending(gauge,endpoint,method): requests in progress. A route that has had a request reads0once nothing is in progress.
Entities, history and subscriptions
andromeda_entity_operations_total(counter,operation,outcome) andandromeda_entity_operation_duration_seconds(histogram,operation): entity operations and how long they took, from 1 ms to 10 s.operationis one ofcreate,merge,replace,delete,retrieve,query,batch-create,batch-upsert,batch-update,batch-mergeorbatch-delete.outcomeisokorerror.andromeda_temporal_operations_total(counter,operation,outcome) andandromeda_temporal_operation_duration_seconds(histogram,operation): Temporal API operations.operationis one ofcreate-or-update,add-attributes,delete,delete-attribute,modify-attribute-instance,delete-attribute-instance,retrieve,queryorquery-ids.andromeda_subscription_operations_total(counter,operation,outcome): subscriptions created, updated and deleted.operationiscreate,updateordelete.andromeda_subscription_candidates(histogram,stage): for each change, how many subscriptions were still in play after each step of matching.- The stages run in order:
active,routed(entity selectors),watched(triggers and watched attributes),evaluated, thenmatched. evaluatedfar belowactivemeans the routing index is doing its job.
Notifications
andromeda_notification_deliveries_total(counter,outcome) andandromeda_notification_delivery_duration_seconds(histogram): HTTP and MQTT delivery attempts, and how long each took, from 1 ms to 10 s.okmeans the endpoint accepted the notification.errorcovers a refusal, a timeout and an endpoint that couldn't be reached.andromeda_notification_oversized_total(counter,limit): notifications refused before sending because they were too large.limitsays which limit refused them:max-http-bodyisnotifications.delivery.max_http_body.max-mqtt-packetisnotifications.delivery.max_mqtt_packet.mqtt-server-announcedis a Maximum Packet Size the MQTT broker announced belowmax_mqtt_packet. Raisingmax_mqtt_packetdoesn't help with this one, because the MQTT broker's own limit has to go up.
Queues and background tasks
andromeda_queue_outcomes_total(counter,queue,outcome): what happened to each item claimed from a work queue in the tenant databases.queueisnotification-deliveries,pending-side-effects(batches answered withPrefer: respond-async) ortemporal-append-intents(history waiting to be written).outcomeiscompleted(done),retried(failed, and scheduled for another attempt),exhausted(failed for the last time, and left the queue),lease-lost(its claim ran out before it was done, so another pass takes it),held-back(released without an attempt, to wait for an earlier item it must follow), orstuck.stuckis a history write, or the notifications of arespond-asyncbatch, that failed again aftertemporal.history.stuck_after_attemptsorentity.side_effects.stuck_after_attemptsattempts. It stays queued and is retried, since what the broker accepted is never thrown away. Every such failure counts once, alongside itsretried.andromeda_task_failures_total(counter,class,criticality): background tasks that failed for good, after their restarts ran out. Theobservedandtenant-degradingones stay in thebackgroundTaskscomponent of the health report until the tenant runtime is evicted or the broker restarts. The metric labels keep the kebab-case spelling shown here, while the health report spells the same values in camelCase (tenantDegrading,invariantViolation).classistransient,permanentorinvariant-violation.criticalitysays what the failure did:observed(listed in thebackgroundTaskscomponent of the health report, which staysup),tenant-degrading(that component isdegraded),tenant-fatal(every task of the tenant was stopped) orprocess-fatal(the broker's own tasks were stopped too).
Database
andromeda_postgresql_statement_duration_seconds(histogram,statement) andandromeda_postgresql_statement_failures_total(counter,statement): how long the hottest statements took, from 0.5 ms to 10 s, and how often they failed.
statement names the kind of statement, never its SQL.
- Entity writes:
entity-insert,entity-update,entity-delete. - The notification queue:
notification-delivery-claim,notification-delivery-acknowledge,notification-delivery-hold. - The queue of
respond-asyncbatches:pending-side-effect-claim,pending-side-effect-release. - The history queue:
temporal-intent-claim,temporal-intent-retry,temporal-intent-release,temporal-intent-release-probe,temporal-intent-application. - Entity creation:
entity-evolution-withdrawal, which withdraws the Temporal Evolution a creation established. - Identity row locks:
entity-row-lock. Before a commit or a history application runs any of its writes, it locks every identity row it is about to write, in identifier order. For a commit, the same statement also reads the version that a write leaving current state unchanged is held to. - Temporal queries:
temporal-candidate-entities(which entities a query selects),temporal-candidate-entity-count,temporal-history,temporal-scope-history,temporal-aggregation,temporal-aggregation-shape-check(whether the instances to aggregate can be aggregated). - Temporal queries over entities a context source contributed to:
temporal-source-filter-evaluation,temporal-source-window-start-instances,temporal-source-window-start-scope,temporal-source-window-relationships.
The pool and table series below describe each tenant's database.
andromeda_postgresql_pool_connectionsandandromeda_postgresql_pool_idle_connections(gauges,tenant): open and idle connections in one tenant's pool, sampled every five seconds. Open connections at the pool's size (tenancy.default_tenant.max_connectionsortenancy.databases.max_connections) with few idle means requests are waiting for one (see Database connections).andromeda_postgresql_table_bytesandandromeda_postgresql_table_index_bytes(gauges,tenant,table): on-disk size ofentities,entity_search,current_attributes,subscriptionsandsubscription_delivery_state, and of their indexes.- These are sampled once a minute, and only when the tenant has been written to since the last sample. Between samples, they keep the last value while the tenant stays loaded.
andromeda_postgresql_table_updates_totalandandromeda_postgresql_table_hot_updates_total(counters,tenant,table): rows the same tables had updated, and how many of those updates were heap-only (they wrote no index entry). They are read from PostgreSQL's table statistics on the same samples, and a statistics reset never moves them backwards.rate(andromeda_postgresql_table_hot_updates_total[1h]) / rate(andromeda_postgresql_table_updates_total[1h])is the heap-only share of the last hour's updates, and has no value for a table nothing updated.andromeda_postgresql_history_largest_rowstore_chunk_bytes(gauge,tenant,table): the largestattribute_historychunk still waiting for columnstore conversion. It is0without a columnstore or with nothing to convert. Alert at about 4 GB (see Maintenance).
The tenant label holds the tenant name, and @default for the default tenant. A tenant name starts with a letter or an underscore, so no named tenant shares that value, not even one called default. These series stop being updated when a tenant is unloaded, and they are dropped five minutes later.
PostgreSQL maintenance
Maintenance explains what each of these tells you and when to act. None carries a tenant label.
andromeda_postgresql_maintenance_decisions_total(counter,decision): what each maintenance pass decided. The values aredisabled,global-lock-unavailable,no-tenant,tenant-unavailable,schema-behind,idle,probed,queued,would-act,deferred,acted,action-failedandcancelled.andromeda_postgresql_maintenance_actions_total(counter) andandromeda_postgresql_maintenance_action_duration_seconds(histogram), both byactionandoutcome: actions taken and how long they ran, from 0.1 s to an hour.actionisreindex,vacuum,analyze,recovery,recompressorrepack.outcomeisok,errororcancelled.andromeda_postgresql_maintenance_reclaimed_bytes(histogram,action) andandromeda_postgresql_maintenance_removed_dead_tuples(histogram,action): what a successful rebuild or repack gave back, from 1 MiB to 64 GiB, and the dead rows a vacuum removed, from 100 to a billion.andromeda_postgresql_maintenance_repack_phase_seconds(histogram,phase): how long each phase of a table repack took, from 0.1 s to six hours.phaseissetup,copy,index-build,replay,swap,droporanalyze. Repacking tables says what a slow phase means.andromeda_postgresql_freeze_age_max,andromeda_postgresql_multixact_age_maxandandromeda_postgresql_freeze_pressure_databases(gauges, the last bylevel): the oldest transaction and multixact ages across the tenant databases, and how many databases are atnormal,overdueandcritical.andromeda_postgresql_autovacuum_workers_busy,andromeda_postgresql_autovacuum_workers_maxandandromeda_postgresql_overdue_relations(gauges): autovacuum workers in use and allowed, and how many tables the latest tenant visit found overdue for a vacuum.andromeda_postgresql_replication_slot_max_retained_wal_bytes(gauge): the most WAL any replication slot keeps on disk.0without a slot.
Tenants
andromeda_tenant_runtimes_cached(gauge): tenants loaded right now.andromeda_tenant_cold_builds_total(counter,outcome) andandromeda_tenant_cold_build_duration_seconds(histogram): tenants loaded from cold, and how long loading took, from 10 ms to a minute.outcomeisokorerror.andromeda_tenant_build_admission_wait_duration_seconds(histogram): how long a tenant waited for its turn to load, from 1 ms to 10 s.andromeda_tenant_requests_shed_total(counter,wait): requests answered503because a tenant could not be reached or loaded in time, by where they waited. Each also logs onewarnline,the request was shed as saturated, withtenant,waitandwaited_seconds.waitis:build-lock: another request was loading the same tenant.build-permit: other tenants held every loading slot.teardown-slot: every slot for closing a loaded tenant was busy, so no room could be made.provisioning: a new tenant's database was still being created. Creating it goes on after the request is answered.runtime-build: the tenant was still being loaded. Loading it goes on after the request is answered.registry-readorregistry-write: the platform pool for the tenant's registry row.registration-transaction: the platform pool for registering a new tenant.migration-pool: the connection a new tenant's migrations run over.runtime-pool: a connection the load acquires.andromeda_live_changes_abandoned_total(counter): committed entity changes that got no notification because a stopping broker, or a tenant being unloaded, ran out ofnotifications.evaluation.drain_timeoutbefore evaluating them. Each such stop also logs onewarnline,stopping tenant gave up evaluating committed changes, withabandoned. Onlylivedelivery can abandon changes.andromeda_tenant_evictions_total(counter,reason): tenants unloaded.reasonisidle,excess(the cache was full),dormant(its next work is scheduled for later),orphaned(its registry row was removed) orsuspended.andromeda_tenant_wakes_total(counter,outcome): attempts to load a tenant for its scheduled work.outcomeisalready-cached,loaded,nothing-due,deferred,build-failedorunregistered.- A wake pass claims only as many due tenants as it has places for. A place is either free or held by a loaded tenant the pass may unload, which means no request is using it and it is past
tenancy.residency.woken_lifetimeor its due work has already run. When there is no place at all, the pass claims nothing and waits for one to free up, and the tenants it left keep their turn. - A wake is
deferredonly when a request took the place in the moment between the pass counting it and the load. The wake is retried as soon as one of the woken tenants may give up its place. - A wake is
build-failedwhen the tenant could not be loaded, and is retriedtenancy.residency.min_wake_intervallater. andromeda_tenant_overdue_wakes(gauge) andandromeda_tenant_overdue_wake_max_age_seconds(gauge): wakes overdue as the last wake pass ended, and how long ago the oldest of them fell due. A wake is overdue when it was deferred, or when it was due and a pass left it for a later one, either because the pass had already loaded as many tenants astenancy.residency.max_wakes_per_passallows or because it had no place for it. Both are zero while the broker loads tenants as fast as their work falls due. While any wake is overdue, the broker also logs awarnline at most once a minute,tenant wakes are overdue: more tenants have work due than the broker can load, withoverdueandoldest_overdue_seconds.
Tenants explains loading and unloading.
Name dictionaries
Each loaded tenant caches its attribute names and its entity type names, up to database.name_cache.max_size_per_dictionary each. These series add up every loaded tenant's caches, and dictionary is attribute-names or entity-type-names. Name caches explains the caches.
andromeda_name_dictionary_lookups_total(counter,dictionary,outcome): names looked up in a cache, on reads and writes.outcomeishit(the cache held the name) ormiss(the name was read from the database). A miss rate that stays high means the cache is too small for the names in use.andromeda_name_dictionary_evictions_total(counter,dictionary,cause): names dropped from a cache.causeissize(the cache was full) oridle(unused fordatabase.name_cache.idle_eviction_after). An evicted name is read from the database the next time it is needed.andromeda_name_dictionary_entries(gauge,dictionary): names the caches hold right now.andromeda_name_dictionary_insertions_total(counter,dictionary): names added to a dictionary for the first time. A name another writer added first isn't counted again, and a write that is rolled back still counts the names it added.
Federation
andromeda_federation_requests_total(counter,operation,outcome) andandromeda_federation_request_duration_seconds(histogram,operation): requests sent to context sources, and how long they took, from 1 ms to 10 s.operationisquery,retrieveorprovision(a write forwarded to a source).andromeda_federation_upstream_status_total(counter,status): what the sources answered.statusis the class of the HTTP status code:1xx,2xx,3xx,4xxor5xx, andotherfor a code outside them.
What to watch
- The
backgroundTaskscomponent of/ngsi-ld/v1/healthbeingdegraded, orandromeda_task_failures_totalwithcriticality="tenant-degrading": a task of a tenant, or of the broker, stopped for good and its work is not running. Queued deliveries stay queued. andromeda_task_failures_totalwithcriticality="tenant-fatal"or"process-fatal": a tenant's background work has stopped, or the broker's own has.andromeda_queue_outcomes_totalwithoutcome="stuck"or"exhausted": a queue item keeps failing, or gave up.- For
notification-deliveries, an endpoint is down or refusing. - For
pending-side-effects, the notifications of accepted batches are late. Astuckjob keeps being retried, andexhaustedmeans a job whose stored changes could not be read back. - A
stuckhistory write keeps being retried, so the history of an accepted write is late, and the entity's later history waits behind it. Deleting that entity's Temporal Evolution removes the stuck write along with the rest of its history. rate(andromeda_notification_deliveries_total{outcome="error"}[5m]): endpoints refusing notifications. The subscription'slastFailureandnotification.statusshow which one (see Delivery status).andromeda_postgresql_statement_failures_totalrising: the database refuses statements. See Store outages.andromeda_tenant_cold_builds_total{outcome="error"}: a tenant couldn't be loaded, so the request or scheduled work that needed it failed.andromeda_tenant_overdue_wake_max_age_secondsgrowing: more tenants have work due thantenancy.residency.max_residentlets the broker load, and periodic notifications fall behind without bound. Raisemax_resident. Loweringtenancy.residency.woken_lifetimewon't help, because while every place is taken a woken tenant already gives up its place as soon as its work has run. Subscriptions and notifications says when this happens.
Configuration
| Key | Default | What it does |
|---|---|---|
observability.metrics.prometheus.mode | "disabled" | "enabled" installs the Prometheus recorder and serves /metrics. While it is "disabled", nothing is recorded. It needs management.mode to be "separate-port" or "shared-port" |