Synadia Insights
Upgrading to 1.2.0
Insights 1.2.0 introduces multi-system support: one Insights node can monitor several NATS systems at once, each with its own database, API subtree and web pages. That shows up in several places in this guide, because anything that used to be implicit now names the system it belongs to.
Most of the upgrade takes care of itself. The database migrates on first start and your check history comes with it, and a single-system config file from 0.1.10 starts as it is: keys that no longer exist are ignored rather than rejected.
Two settings are worth a look before you restart, and only if you use them. Check codes have clearer names, so a check-thresholds override, a disabled-checks entry or a notification subscriptions or ignore entry that names a code needs the new name. A handful of threshold parameters were also renamed, and some take typed values like 100ms and 1 MiB rather than a bare number with the unit in the parameter name.
insights config check finds those names against your existing config without starting anything, so run it first. It reports one group of problems at a time, so run it again after each round of edits until it prints configuration is valid. It checks names, not values: a new threshold value is still yours to read against the tables below.
Does this affect you?
Each row is something you can check in a few seconds.
| Area | Affects you if | Go to |
|---|---|---|
| Check codes | grep -nE '\b[A-Z]+_[0-9]{3}\b' config.yaml returns anything | Checks |
| Check parameters | a check-thresholds override names a parameter ending _ms, _secs, _minutes, _mib, _gib, _kib, _pct or _frac | Checks |
| Notification config | grep -n 'max-retries' config.yaml returns a line, or an endpoint sets timeout: | Configuration |
| Backup automation | a script runs insights ops backup with --output -, or publishes to $INS.db.backup | Operations |
| Saved SQL and dashboards | a query names hx.check_results or an audit. macro, calls json_extract on metadata, backoff, mirror_subject_transforms or subject_transforms, or compares storage_type, retention_policy, discard_policy, store_compression or persist_mode against a capitalized value | Data |
| NATS clients | a tool you wrote publishes to $INS.db.*, $INS.checks.* or $INS.ops.* | Interfaces |
| Sizing automation | a script reads total_gb, query_gb, write_gb or index_gb from ops.sizing | Interfaces |
| HTTP gateway callers | a caller posts to a /db/... path | Interfaces |
| MCP automations | a saved prompt or script calls an Insights MCP tool without a system argument | Interfaces |
| Alert receivers | an Alertmanager route, inhibition or silence, or a webhook consumer, matches the entity_name label | Interfaces |
| Prometheus and Grafana | a dashboard aggregates without grouping by system | Interfaces |
| Container tooling | a script, probe or init step runs a shell or package manager inside the Insights image | Operations |
| Sink stream name | sink.stream, --sink.stream or INSIGHTS_SINK_STREAM is set to anything other than scrape | Interfaces |
| Monitored NATS servers | any of them runs NATS Server older than 2.10.27 | Before you upgrade |
| Telemetry | you run a trial build (a -licensed archive or the insights-licensed image) and would rather opt out | Configuration |
Before you upgrade
1. Check the minimum NATS Server version. Insights 1.2.0 requires NATS Server 2.10.27 or later. Earlier 2.10 patches do not serve the raftz and ipqueuesz endpoints, so the checks that read them stay quiet rather than reporting an error, which is easy to miss.
nats server report jetstream # or: curl -s http://<server>:8222/varz | jq .version
2. Keep a backup from your current version. The database moves forward on first start and a 0.1.10 binary cannot read a 1.2.0 catalog, so a copy taken now is what makes going back an option.
insights ops backup --output insights-pre-1.2.0.db
Take the backup with the 0.1.10 binary
Back up with the binary that matches the running server. Once a 1.2.0 node has started against the data directory, the catalog is already migrated. Going back restores from this file.
3. Validate your config with the new binary. Get the 1.2.0 binary as described in Installation. insights config check reads the config file and identity.json, and creates the data directory if it is missing, as serve would. It opens no database and starts no listener, so it is safe to run against a live deployment.
insights config check --config /etc/insights/config.yaml
Many configs pass unchanged. Where one does not, the report names the offending code or parameter. A code that changed only in case and separator names its replacement (SERVER_003 was renamed to server-003), and a parameter lists the valid names. A code whose number or entity changed prints unknown check code with no suggestion, so look it up in the mapping table. The report shows one group of problems per run, notification patterns before check codes, so repeat it until it prints configuration is valid. Add --strict to make unclaimed state a non-zero exit for a deploy gate. See insights config check.
4. Reconcile ids if the report mentions unclaimed state. Because a node can now serve several systems, a data directory records the node and system ids it was built under, which keeps each catalog matched to the system it holds. Renaming an id in config used to leave the old catalog behind and start an empty history; it is now something you declare. Against a stopped instance:
insights config migrate # adopt the new naming scheme
insights config migrate --rename system:west=north # an id that actually changed
insights config migrate --prune system:retired-eu # forget one, deleting nothing
With no flags this adopts the 1.2.0 naming scheme, which is the ordinary upgrade case: the ids held still and the names beneath them moved. Neither --rename nor --prune deletes data. See insights config migrate.
Configuration
Removed
| Key | Environment variable | Note |
|---|---|---|
backup.object-store-ttl | INSIGHTS_BACKUP_OBJECT_STORE_TTL | Backups are built locally now and are not uploaded anywhere. |
web.metrics-timeout | INSIGHTS_WEB_METRICS_TIMEOUT | The component metrics poller they configured is off; nothing in Insights displayed it. |
web.metrics-interval | INSIGHTS_WEB_METRICS_INTERVAL | |
notifications.*.timeout | none | Each request times out after 10s. |
notifications.*.max-retries | none | Each delivery makes up to 3 attempts (one when the receiver answers with a 4xx other than 429), and a failed delivery is retried for that endpoint at up to one-minute intervals until it succeeds. |
None of these stop a node from starting when they come from a config file or the environment: the keys are ignored, so remove them whenever it suits you. The --web.metrics-interval and --web.metrics-timeout flags are the exception; on a command line they fail with unknown flag.
Renamed
| Old | New | Note |
|---|---|---|
insights query | insights db query | The old spelling remains as an alias on the same command, so scripts keep working. |
insights ops backup --output <file> | insights ops backup save --output <file> | save is the default subcommand, so the old invocation still works. |
New
Node and system identity. Both subtrees are new, and they name the API space each part of the deployment answers on. system.id defaults to default and node.id defaults to the id of the single system the node serves, so a single-system deployment works without setting either. Set them when you want to address something by a name you chose.
node:
id: 'hub-us-east' # INSIGHTS_NODE_ID
metadata: # labels for `insights node list` and the Nodes tab
region: us-east-1
system:
id: 'prod' # INSIGHTS_SYSTEM_ID
metadata:
env: prod
See Architecture and Configuration.
Multiple systems. One node can own several monitored systems, each with its own DuckDB catalog, JetStream stream and $INS.sys.<id>.* API. Set systems: only on a node monitoring more than one system; it replaces system.id and the flat scraper config rather than sitting alongside them. A node serving more than one system has no single system id to derive its own from, so node.id is required there.
node:
id: 'hub-1'
systems:
- id: 'core-prod'
scrape:
nats:
server: 'nats://core.internal:4222'
creds: /etc/insights/core-prod.creds
interval: 20s
retention: 24h
- id: 'edge-west' # collector-fed: no scrape block
retention: 6h
See Deployment.
Collectors. insights-collector is a new, smaller binary that only scrapes: it has no database and reads only the data-dir, node, system, sys, scraper, sink, license, updater and telemetry settings. Its default command, serve, publishes each scrape to a stream, and a central node picks the system up by declaring it with no scrape block, as above. The collector's node.id defaults to its system.id, and a second node claiming an id already in use refuses to start, so give the collector and the central node distinct node.id values. See Federated Deployment.
Turning a check off. disabled-checks is new. A disabled check keeps running and stays visible marked disabled, with its real findings; it is left out of health grades and is never delivered as a notification. It suits a check that cannot be acted on in your topology, rather than one whose signal you would still want to see.
disabled-checks:
- account-012
See Configuration.
Notification subscriptions take patterns. subscriptions and the new ignore list accept an exact code, a trailing-wildcard prefix, or *. ignore applies after subscriptions, so it carves exceptions out of a wildcard. An empty subscriptions list delivers nothing, so subscribing stays explicit.
notifications:
webhook:
- name: 'PagerDuty Relay'
url: 'https://example.com/webhook'
subscriptions:
- server-*
- stream-002
ignore:
- server-046
systems: # empty = every system this node owns
- core-prod
systems: is new alongside multi-system support: it scopes an endpoint to particular systems, and an empty list delivers for all of them. See Notifications.
Sink stream shape. sink.replicas and sink.storage are new. Replicas above 1 need a clustered sink, since the embedded server is a single node. The stream is a buffer rather than the history, so storage: memory is a reasonable choice where losing the buffer on restart is acceptable.
Aggregated Prometheus endpoint. prometheus.aggregate serves every node's metrics from one endpoint, discovering nodes over $INS.ping, which saves adding a scrape target per node. Enable it on exactly one node: scraping two aggregators would report every series twice.
prometheus:
aggregate: true
timeout: 2s # keep this shorter than your scrape timeout
See Metrics.
Inbox prefix. nats.inbox-prefix sets the request/reply inbox prefix, for accounts that are not granted _INBOX.>.
Telemetry, on by default in trial builds. A pulse every five minutes carries a locally generated random instance id, the release version and platform, a few configuration durations and counts, the size of the watched environment, how many checks have a finding, and usage counters for the web UI and for requests made by the CLI, coding agents, MCP and HTTP clients. It carries nothing about the monitored system: no server, cluster, account, subject, stream or consumer names, no addresses, no message data, no configuration values, no query text. Only trial builds carry the reporter; the Insights build contains none. insights web sends no pulse of its own but passes the page views it serves to the node holding each system's database, so set the same switch there to stop that.
telemetry:
enabled: false # or --telemetry=false, INSIGHTS_TELEMETRY=false
print: true # log every pulse and read exactly what would leave
See Telemetry.
Changed defaults
db.memory-limit defaults to 4GiB rather than 4GB. DuckDB reads GB as 10^9 bytes, so the old default capped each database at 3.7 GiB and the new one at 4.0 GiB. A value you set yourself keeps its meaning; write it in MiB or GiB to get the size it names.
Otherwise, anything you already set keeps its meaning. The subsystem switches (scraper, indexer, web, simulator, prometheus, updater) each gained an environment variable, so INSIGHTS_WEB=false works alongside INSIGHTS_WEB_ENABLED=false, and updater gained the --updater flag the others already had. The --<subsystem>.enabled flags still work but are hidden from --help. In YAML the section is still a map, so keep using its enabled: key.
Checks
Check codes now name the entity a finding is about. A finding on a server starts with server-, one on a stream with stream-, and so on, so the code tells you where to look before you read anything else. The previous codes grouped by subsystem instead, which is why CLUSTER_007 and META_001 both described servers. Codes are also lowercase and hyphenated throughout, matching how they appear in the UI and the API.
Your history comes with you. A migration rewrites stored findings onto the new codes in one transaction, so timelines stay unbroken and open findings keep their age.
The config file is the one place that may need an edit, because codes appear there by name: in check-thresholds, in disabled-checks, and in the subscriptions and ignore lists on a webhook or Alertmanager endpoint. If you set none of them, there is nothing to change.
Codes and parameters are validated at startup
A code matching no check is reported at startup with an error naming it, and the node does not start. A check-thresholds entry under an unrecognized code used to be passed over without comment, which meant a threshold you thought you had set was quietly doing nothing. Where only the spelling changed the error names the replacement, so SERVER_001 tells you to write server-001. Where the number or the entity changed, the new name cannot be derived from the old one and the error says only unknown check code, so look it up in the mapping table. A parameter name that no longer exists fails the same way and lists the valid names.
Every place a code appears in the config is checked this way, the top-level and per-system check-thresholds and disabled-checks included:
check-thresholds:
server-003: # was SERVER_003
cpu_percent: 80
notifications:
webhook:
- name: 'Relay'
url: 'https://example.com/webhook'
subscriptions:
- server-001 # was SERVER_001
Codes also appear outside the config file, where nothing validates them for you: the Alertmanager alertname, the finding feed's subject token, and hx.check_findings.code in saved SQL and agent prompts.
Eight checks were retired
Seven of them have no replacement and no new spelling, so a config entry naming one fails startup as an unknown code; remove it. Their stored findings are dropped rather than remapped.
CHANGE_003, JETSTREAM_007, OPT_IDLE_006, OPT_PLACE_002, SERVER_006, SERVER_009, SERVER_017.
Several were folded into another check rather than dropped outright. Their rows still go, because the absorbing check measures a different threshold and inheriting the old findings would attribute them to the wrong measurement.
The eighth, server-049 (Leafnode Auto Compression with High Count, was OPT_SYS_017), is retired after the rename: leafz reports the negotiated compression, not the configured s2_auto mode, so the check could not establish what it claimed. Its stored findings are kept and still display. An entry naming exactly server-049 in check-thresholds, disabled-checks, subscriptions or ignore is accepted with a warning at startup and has no effect; OPT_SYS_017 fails as an unknown code.
OPT_PLACE_004 is a related case: it continues as account-014, but its stored findings do not carry over. The old check recorded a server's key where the new one records an account's, so the next evaluation writes them correctly instead.
Some thresholds take typed values
A threshold that encoded its unit in the parameter name now takes a value that states it. Durations take Go duration strings, sizes take IEC binary strings or a plain byte count, and percentages end in _percent and take 0 to 100. This makes an override readable on its own. insights config check names any parameter whose name needs updating, but not a value written for the old unit, so convert values yourself: min_mono_frac: 0.8 becomes min_mono_percent: 80.
| Check (1.2.0) | Was | Now |
|---|---|---|
connection-001 | rtt_ms: 100 | rtt: 100ms |
connection-002 | pending_mib: 1 | warn_percent: 75 |
connection-004 | idle_minutes: 5 | idle: 5m |
account-008 | max_size_kib: 64 | max_size: 64 KiB |
account-011 | min_conn_pct: 10 | min_conn_percent: 10 |
account-012 | warn_percent, min_total_bytes | gateway_percent: 30, min_traffic: 1 MiB |
leafnode-002 | rtt_ms: 100 | rtt: 100ms |
stream-010 | stale_minutes: 5 | stale: 5m |
stream-020 | consumer_leader_pct | consumer_leader_percent |
stream-022 | max_memory_mib: 100 | max_memory: 100 MiB |
stream-023 | max_uncompressed_gib: 1 | max_uncompressed: 1 GiB |
stream-028 | warn_window_minutes, crit_window_minutes | elevated_window: 1h, severe_window: 6h |
stream-031 | min_ack_wait_secs: 30 | min_ack_wait: 30s |
stream-032 | min_mono_frac: 0.8 | min_mono_percent: 80 |
system-004 | window_minutes: 10 | window: 10m |
server-020 | max_delta: 500 | churn_percent: 50, min_conns: 100 |
server-037 | saturation_pct | saturation_percent |
server-041, server-042 | pending_mib: 1 | pending: 1 MiB |
server-050 | min_growth_pct, min_mono_frac | min_growth_percent, min_mono_percent |
server-051 | warn_bytes_gib, crit_bytes_gib, warn_pct, crit_pct | warn_size: 10 GiB, crit_size: 50 GiB, warn_percent: 50, crit_percent: 80 |
server-063 | warn_seconds, crit_seconds | warn_duration: 5s, crit_duration: 30s |
consumer-019 | warn_abs, warn_multiplier, crit_abs, crit_multiplier | elevated_abs, elevated_multiplier, severe_abs, severe_multiplier |
server-010 | rtt_ms: 50 | multiplier: 2, severe_multiplier: 3, min_rtt: 10ms, measured against the rest of the cluster's routes |
server-018 | rtt_ms: 50 | multiplier: 2, min_rtt: 25ms, measured against the other gateways between the same two clusters |
Two defaults moved, both toward fewer findings: account-007 (was OPT_ACCT_001) warns at 90% rather than 85%, and consumer-017 (was OPT_SYS_003) at 90% rather than 80%. If you had raised either one yourself, the override is no longer needed.
Several checks gained optional parameters whose defaults preserve current behavior, so there is nothing to set: crit_percent on server-005 and server-011, max_restarts on server-008, sustain on server-014 and server-048, and sustain with baseline on system-009.
Some severities moved
Nothing to edit, though alert routing keyed on severity will catch a slightly different set.
- Warning to critical:
consumer-006,consumer-007,consumer-008,consumer-009,consumer-011. - Critical to warning:
server-014(critical again for a disabled JetStream, or once a recovering meta layer outlastssustain),system-007(critical again when offline peers leave the meta group below quorum). - Critical to info:
server-007,consumer-004,consumer-005,stream-009. - Warning to info:
server-002,server-010,server-048,consumer-018.
server-010 and server-048 can escalate from info the same way.
Nine checks are new
server-053, server-054, server-055, server-056, server-064, server-065, stream-033, stream-034, consumer-020. They are on by default. Run insights checks info <code> for what each one measures, or see the checks reference.
server-065 is the one most likely to have something to say on an existing deployment: it reports a server with no varz reply in an epoch where another server replied and a peer still routes to it. stream-034 and consumer-020 report io.nats.monitor.* metadata values that are invalid for their key.
Full code mapping
insights checks list prints the current set. The mapping below is the one the migration applies, so it is also how your stored history was rewritten.
| 0.1.10 | 1.2.0 | Note |
|---|---|---|
ACCOUNTS_001 | account-001 | |
ACCOUNTS_002 | account-002 | |
ACCOUNTS_003 | account-003 | |
ACCOUNTS_004 | account-004 | |
ACCOUNTS_005 | account-005 | |
ACCOUNTS_006 | account-006 | |
CHANGE_001 | server-023 | |
CHANGE_002 | server-024 | |
CHANGE_003 | retired | the check no longer exists; stored findings are dropped |
CHANGE_004 | stream-001 | |
CLUSTER_001 | server-057 | |
CLUSTER_003 | server-059 | |
CLUSTER_004 | server-060 | |
CLUSTER_005 | server-061 | |
CLUSTER_006 | server-020 | |
CLUSTER_007 | server-021 | |
CLUSTER_008 | server-022 | |
CONN_001 | connection-001 | |
CONN_002 | connection-002 | |
CONN_003 | connection-003 | |
CONSUMER_001 | consumer-001 | |
CONSUMER_002 | consumer-002 | |
CONSUMER_003 | consumer-003 | |
CONSUMER_004 | consumer-004 | |
CONSUMER_005 | consumer-005 | |
CONSUMER_006 | consumer-006 | |
CONSUMER_007 | consumer-007 | |
CONSUMER_008 | consumer-008 | |
CONSUMER_009 | consumer-009 | |
CONSUMER_010 | consumer-010 | |
CONSUMER_011 | consumer-011 | |
CONSUMER_012 | consumer-012 | |
JETSTREAM_001 | stream-002 | |
JETSTREAM_002 | stream-003 | |
JETSTREAM_003 | stream-004 | |
JETSTREAM_004 | server-062 | |
JETSTREAM_005 | server-025 | |
JETSTREAM_006 | system-003 | |
JETSTREAM_007 | retired | the check no longer exists; stored findings are dropped |
JETSTREAM_008 | stream-007 | |
JETSTREAM_009 | server-028 | |
JETSTREAM_010 | stream-005 | |
JETSTREAM_011 | stream-006 | |
JETSTREAM_013 | stream-008 | |
JETSTREAM_014 | stream-009 | |
JETSTREAM_015 | stream-010 | |
JETSTREAM_017 | stream-011 | |
JETSTREAM_018 | stream-012 | |
JETSTREAM_019 | stream-013 | |
JETSTREAM_020 | stream-014 | |
JETSTREAM_021 | stream-015 | |
JETSTREAM_022 | stream-016 | |
JETSTREAM_023 | stream-017 | |
JETSTREAM_024 | stream-018 | |
JETSTREAM_025 | stream-019 | |
LEAF_001 | leafnode-001 | |
LEAF_002 | leafnode-002 | |
LEAF_003 | leafnode-003 | |
META_001 | server-058 | |
META_002 | system-011 | |
META_003 | system-004 | |
META_004 | server-063 | |
META_005 | system-006 | |
META_006 | system-007 | |
META_007 | system-008 | |
META_008 | server-030 | |
META_009 | system-009 | |
OPT_ACCT_001 | account-007 | |
OPT_ACCT_002 | account-008 | |
OPT_BALANCE_001 | server-031 | |
OPT_BALANCE_002 | server-032 | |
OPT_BALANCE_003 | server-033 | |
OPT_BALANCE_004 | server-034 | |
OPT_BALANCE_005 | server-035 | |
OPT_BALANCE_006 | server-036 | |
OPT_BALANCE_007 | stream-020 | |
OPT_BALANCE_008 | server-037 | |
OPT_COST_001 | stream-021 | |
OPT_COST_002 | stream-022 | |
OPT_COST_003 | server-038 | |
OPT_COST_004 | stream-023 | |
OPT_COST_005 | server-039 | |
OPT_IDLE_001 | server-040 | |
OPT_IDLE_002 | stream-024 | |
OPT_IDLE_003 | consumer-013 | |
OPT_IDLE_004 | consumer-014 | |
OPT_IDLE_005 | account-009 | |
OPT_IDLE_006 | retired | the check no longer exists; stored findings are dropped |
OPT_IDLE_007 | connection-004 | |
OPT_PLACE_001 | account-011 | |
OPT_PLACE_002 | retired | the check no longer exists; stored findings are dropped |
OPT_PLACE_003 | account-012 | |
OPT_PLACE_004 | account-014 | findings not carried over |
OPT_SYS_001 | stream-025 | |
OPT_SYS_002 | consumer-016 | |
OPT_SYS_003 | consumer-017 | |
OPT_SYS_004 | consumer-018 | |
OPT_SYS_005 | server-041 | |
OPT_SYS_006 | leafnode-004 | |
OPT_SYS_007 | server-043 | |
OPT_SYS_008 | account-013 | |
OPT_SYS_009 | server-044 | |
OPT_SYS_010 | server-045 | |
OPT_SYS_011 | server-046 | |
OPT_SYS_012 | server-047 | |
OPT_SYS_013 | server-048 | |
OPT_SYS_014 | server-042 | |
OPT_SYS_015 | consumer-019 | |
OPT_SYS_016 | stream-026 | |
OPT_SYS_017 | server-049 | retired after the rename; stored findings kept |
OPT_SYS_018 | stream-027 | |
OPT_SYS_019 | stream-028 | |
OPT_SYS_020 | stream-029 | |
OPT_SYS_021 | stream-030 | |
OPT_SYS_022 | server-050 | |
OPT_SYS_023 | server-051 | |
OPT_SYS_024 | stream-031 | |
OPT_SYS_025 | stream-032 | |
OPT_SYS_026 | server-052 | |
SERVER_001 | server-001 | |
SERVER_002 | server-002 | |
SERVER_003 | server-003 | |
SERVER_004 | server-004 | |
SERVER_005 | server-005 | |
SERVER_006 | retired | the check no longer exists; stored findings are dropped |
SERVER_007 | server-007 | |
SERVER_008 | server-008 | |
SERVER_009 | retired | the check no longer exists; stored findings are dropped |
SERVER_010 | server-010 | |
SERVER_011 | server-011 | |
SERVER_012 | server-012 | |
SERVER_013 | server-013 | |
SERVER_014 | server-014 | |
SERVER_015 | server-015 | |
SERVER_016 | server-016 | |
SERVER_017 | retired | the check no longer exists; stored findings are dropped |
SERVER_018 | server-018 | |
SERVER_019 | server-019 | |
SERVICE_001 | service-001 | |
SERVICE_002 | service-002 | |
USER_001 | user-001 | |
USER_002 | user-002 |
Data
Eighteen migrations run on first start. They add columns and a table, rewrite stored check codes, unwrap JSON columns, rename the findings table, backfill the new table, add a column to the findings table, retire a check, rewrite enum spellings, drop repeated account identity rows and drop the audit schema. Five of them change what an existing query returns, and all five are worth a look if you have saved SQL or Grafana panels.
hx.check_results is now hx.check_findings
Every row in the table is an entity that tripped a check. Checks record only what fails, so there is no pass row and no severity meaning "ok", and the rest of the product already said finding. The table now matches.
The rename copies no rows, but no view is left behind under the old name, so a query naming hx.check_results needs updating. Find them with:
grep -rn 'check_results' /path/to/your/queries /path/to/grafana/dashboards
The code column is rewritten to the new spelling at the same time, so a query filtering WHERE code = 'SERVER_001' returns no rows rather than an error. Worth updating in the same pass.
Check macros moved from audit to checks and contexts
The macros behind the checks lived in a schema named audit. They now live in two schemas named for what they hold, and a migration drops audit:
| 0.1.10 | 1.2.0 |
|---|---|
audit.check_account_001(...) | checks.account_001(...) |
audit.subjects_overlap(...) and the other helpers | checks.subjects_overlap(...) |
audit.context_stream_raft_peers(...) | contexts.stream_raft_peers(...) |
A check's macro is its code with hyphens turned into underscores, so the Full code mapping gives the new name. A threshold parameter the check renamed takes its new name, per Some thresholds take typed values. A query that calls an audit. macro fails with a catalog error rather than returning nothing. Find them with:
grep -rn 'audit\.' /path/to/your/queries /path/to/grafana/dashboards
The discovery endpoints list the new schemas. A client that passes {"schema": "audit"} to $INS.sys.<id>.db.macros or runs insights db macros --schema audit gets an empty list. Ask for checks or contexts instead.
JSON columns hold documents rather than encoded strings
Several JSON columns held their document encoded a second time: the JSON string "{\"k\":\"v\"}" rather than the object. json_type() reported VARCHAR and every json_extract path on it returned NULL. Two consumer enums that NATS Server renders as JSON literals kept their quotes the same way, so priority_policy never equalled pinned_client.
The migration unwraps each affected column in place:
| Table | Columns |
|---|---|
hx.server_ident | metadata |
hx.stream_opts | metadata, mirror_subject_transforms |
hx.stream_source_opts | subject_transforms |
hx.consumer_opts | metadata, backoff, priority_policy, replay_policy |
These columns now behave the way their documentation always described, so a plain json_extract(metadata, '$.key') works. If you had written a workaround, usually a nested extract or a comparison against a quoted literal like priority_policy = '"pinned_client"', replace it with the direct form.
Enum columns hold the NATS API spelling
Insights stored stream and consumer enums in a display spelling that is not what the NATS API sends, and several checks compared against the API spelling and never matched. It now stores the API spelling, and a migration rewrites the recognized values already stored. Empty, NULL and unrecognized values are left as they are.
| Columns | Was | Now |
|---|---|---|
hx.stream_ident.storage_type, hx.consumer_opts.storage_type | File, Memory | file, memory |
hx.stream_opts.retention_policy | Limits, Interest, WorkQueue | limits, interest, workqueue |
hx.stream_opts.discard_policy | DiscardOld, DiscardNew | old, new |
hx.stream_opts.store_compression | None, S2 | none, s2 |
hx.stream_opts.persist_mode | Default, Async | default, async |
hx.consumer_opts.replay_policy, hx.consumer_opts.priority_policy | quoted, such as "instant" | unquoted |
A saved query such as WHERE storage_type = 'File' returns no rows after the upgrade rather than an error; switch it to the lowercase value. Newly indexed rows also record the API's defaults where the server omits the field, so persist_mode reads default and priority_policy reads none. hx.consumer_opts.storage_type is still the parent stream's storage; the consumer page shows the consumer's effective storage separately.
Raft state is uppercase
hx.raft_groups.state holds the value NATS Server reports: LEADER, FOLLOWER, CANDIDATE, CLOSED. Several checks compared it against Leader and Follower, which matched nothing. They now compare against what is stored, so the checks reading raft state report correctly.
The stored data is unchanged. A query of your own comparing this column against a capitalized value was returning no rows before and still does; switch it to uppercase.
Repeated account identity rows are removed
hx.account_ident holds one row per account identity. A database indexed before that rule was enforced carries copies, and retention never removed them while the account stayed active. Every join on pk multiplied an account's rows by the number of copies it had, so any account total built that way was inflated.
The migration keeps the first observation of each (pk, name) and deletes the rest. A query of your own that joins hx.account_ident will return smaller numbers afterwards; those are the correct ones. A saved account panel whose values drop on upgrade is showing this, not a loss of data.
New columns
All are additive with defaults, so nothing existing changes behavior. Each one answers a question that previously had no column.
| Table | Column | What it records |
|---|---|---|
hx.epochs | servers_expected, servers_observed, partial_endpoints | How much of the fleet a scrape reached. NULL means unknown, which should not be read as complete. |
hx.server_opts | config_digest | Hash of the running configuration, which separates a restart with the same config from a reconfiguration. '' means unreported, as with NATS Server before 2.11. |
hx.server_opts | route_pool_size | Route connection pool size. 0 means unreported. |
hx.server_stats | statsz_fallback | True when the row came from STATSZ because the server's varz was stalled. Such a row carries only server-level stats, so leafs, remotes and gateways are NULL rather than zero. |
hx.server_health_stats | error_accounts, error_streams, error_consumers | Which assets a healthz error named. |
hx.raft_group_stats | wal_error | The write-ahead log error raftz reports for a group. '' means none. |
hx.check_findings | observed_epoch | The last epoch that confirmed the finding. NULL means the row's own epoch; an earlier value marks an account-005 or service-002 finding held through incomplete subscription data. |
hx.account_js_tier_opts | new table | One row per JetStream limit set an account JWT declares, keyed by replication tier. Tier '' is the untiered block. Backfilled from stored claims. |
If you aggregate over hx.server_stats, either exclude statsz_fallback rows or treat their NULL columns as missing, so a fallback row does not read as a server with zero leafnodes.
Retention
db.retention.interval is a minimum rather than a fixed period. A sweep runs on the indexer right after the first epoch committed past the interval, and never at the same time as a commit. Nothing to change; sweep timestamps are no longer evenly spaced.
Interfaces
Subjects name the system or node they address
With several systems on one node, every $INS subject carries the id of the system or node it addresses. $INS.ping stays flat, since discovery is the one case where every instance should answer on the same subject.
| Was | Now |
|---|---|
$INS.db.* (query, query.stream, explain, schemas, tables, columns, macros) | $INS.sys.<system>.db.* |
$INS.db.backup | $INS.sys.<system>.db.backup.stream |
$INS.checks.* | $INS.sys.<system>.checks.* |
$INS.ops.info, ops.sizing, ops.memory, ops.storage, ops.scraper.toggle, ops.scraper.trigger, ops.diagnostics.* | $INS.sys.<system>.ops.* |
$INS.ops.scraper | $INS.sys.<system>.ops.scraper.status |
$INS.ops.events.history | $INS.sys.<system>.ops.disconnect.history |
$INS.ops.pprof, ops.nats.info, ops.version, ops.version.check | $INS.node.<node>.ops.* |
$INS.epoch.committed | $INS.sys.<system>.feed.epoch |
$INS.events.disconnect | $INS.sys.<system>.feed.disconnect |
$INS.ops.memory.profile | $INS.sys.<system>.feed.memory |
A useful side effect is that several Insights deployments can share one NATS account: each has its own subject space, so their streams and endpoints cannot be mistaken for one another.
The CLI and web tier discover an id before they call, so they need nothing from you. A client you wrote against the flat subjects needs the id added. insights system list and insights node list show what is reachable. See the API reference.
A named sink stream needs a new name
Scrapes are published under $INS.sys.<system>.scrape.>. Insights 0.1.10 created its sink stream capturing <stream>.>, and a stream that already exists is used as found, never edited. With the default name nothing collides: 1.2.0 creates a fresh scrape_<system> stream and the 0.1.10 scrape stream is left unused. With a name set through sink.stream, 1.2.0 finds the 0.1.10 stream under that name, the scraper refuses its subject, and nothing new is indexed. The log shows it once at startup:
level=ERROR msg="scraper runner error" ... error="stream \"<stream>\" should have subject \"$INS.sys.<system>.scrape.>\""
Check before you restart:
grep -n 'stream:' config.yaml # under sink:
env | grep INSIGHTS_SINK_STREAM
If either names a stream, remove the setting (1.2.0 then uses scrape_<system>) or change it to a name no stream has yet. The indexer tracks its position per stream name, so a new name starts at the beginning of the new stream. Once epochs are arriving, delete the old stream with nats stream rm <stream>, and the unused scrape stream too on a node that ran with the default.
Sizing reports memory in GiB
The ops.sizing reply renames total_gb, query_gb, write_gb and index_gb to total_gib, query_gib, write_gib and index_gib. The values were already whole GiB. insights ops sizing prints its recommendation as --db.memory-limit=<n>GiB, where it used to print GB and so applied about 7% less than it recommended.
The HTTP gateway names the system in the path
POST /sys/<system>/db/query # was POST /db/query
GET /systems # the systems this node serves
Every /db/* route moves the same way: columns, explain, macros, query, schemas and tables. The path token is the same id as the subject token. There is no default system and no cached topology, so a request is always answered by the system it named. See HTTP gateway.
MCP tools take a system argument
Every Insights MCP tool except systems requires a system argument, and a call without one is refused before it runs. An agent working interactively resolves it by calling systems first; a saved prompt or script that calls tools directly needs the id added. See MCP server.
Alert payloads
entity_name is an annotation rather than a label, so a name that resolves a few epochs after a finding first fires no longer changes the Alertmanager fingerprint and fires a stable alert again. Routes, inhibitions and silences that match entity_name stop matching; match entity_key or entity_pk, which are still labels. Every alert and the payload's group labels carry system.
Alertmanager endpoints now receive every standing finding as firing once a minute, with endsAt four minutes out, so Alertmanager resolves an alert on its own when Insights stops sending it. Delivery to an endpoint is at-least-once: a failed delivery is retried until it succeeds, so a receiver can occasionally see a transition twice. See Payload labels and annotations.
Prometheus series carry a system label
Every metric family is per-system, since one node can own several systems with their own catalog, indexer, scraper and epoch clock, and without the label their samples would collide into one series.
On a single-system node the label has one value, so a sum() gives the same number as before. topk and label joins behave differently, so add by (system) where a panel is meant to be per-system.
New series: insights_epoch_servers_expected, insights_epoch_servers_observed, insights_epoch_partial_endpoints, and insights_metrics_source_up on an aggregating endpoint, where every series also gains a source label. See Metrics.
Saved search and filter expressions
List pages and the search box share one parser, which changes two things a saved expression or bookmarked filter can depend on. and, or and not are operators in any case, so a bare lowercase value spelled that way needs quoting. check: takes the new codes, so check:SERVER_003 matches nothing; write check:server-003. See Search.
Web URLs include the system
Every page lives under /systems/<id>/…. On a node serving one system, old paths redirect into it and preserve the query string, so existing bookmarks and deep links keep working. On a node serving several, an old path opens the system list. Tooling that constructs Insights URLs is worth updating to the new form.
Operations
The Insights image has no shell
The Linux images on registry.synadia.io (insights, insights-licensed and the -collector tag) are built on distroless static, which carries CA certificates, time zone data and a handful of OS packages, and no shell or package manager, so scanners have far less to report against them. The Insights image still runs as UID 1000 with /var/lib/insights owned by that UID, as in 0.1.10, so a data volume that worked before needs nothing. The collector image is new in this release, built the same way, and has nothing to migrate.
What changes is anything that runs a command inside the container: docker exec <container> sh, a Kubernetes exec probe calling sh -c, or an init container that reuses the Insights image to chown a volume. Point probes at GET /healthz instead, and use a small general-purpose image for init steps. See Health probe.
ops backup builds the file on your machine
Saving is insights ops backup save, still the default subcommand, so an existing invocation keeps working. The copy streams table by table and is assembled locally, so the disk it needs is yours rather than the server's, and a backup no longer competes for space on the instance. It runs at the same Insights version as the server, and a version difference is reported up front rather than producing a file you cannot restore.
Three things changed with it:
--output -is no longer available. The copy writes into a seekable DuckDB file, so it cannot be piped until complete. A script doinginsights ops backup --output - | gzip > b.gzshould write the file and compress it afterwards.- The endpoint is
$INS.sys.<system>.db.backup.stream, in place of$INS.db.backup. - The
backup:config section is gone, since backups are no longer uploaded to an object store.
The command confirms the size at a terminal, so add --force for anything running from cron or CI.
insights ops backup save --force --output /backups/insights-$(date +%F).db
insights ops backup view <file> serves the web UI against a backup, opened read-only, which is a convenient way to check one. See insights ops backup.
Run under a restart policy
A DuckDB recovery attempt that cannot return within 30 seconds now exits so a supervisor can restart the process, rather than leaving listeners serving a database that cannot answer. Under a Kubernetes Deployment, a systemd unit with Restart=, or --restart on a container, this recovers on its own. Without one, the process stays down where it would previously have stayed up in a state it could not serve from. See Restart policy.
Naming the target makes client commands faster
With nothing naming a target, a client subcommand waits out the two-second $INS.ping discovery window so every responder has a chance to reply. Set --system (or INSIGHTS_SYSTEM, or system.id in a shared config file) for repeated invocations. A named target skips discovery, which also means silence from it is reported as the error it is.
Discovery needs permission to publish $INS.ping and receive replies; a named call needs only its endpoint permissions. See Selecting a system or node.
Notification endpoint state left JetStream
Endpoint config is no longer mirrored into a JetStream KV bucket, and the delivery log moved to a stream named for the node (webhook-deliveries_<nodeID>). Nothing removes what 0.1.10 created: the webhook-endpoints KV bucket and the webhook-deliveries stream stay behind in the sink's JetStream, unused. Remove them once the node is on 1.2.0:
nats kv del webhook-endpoints
nats stream rm webhook-deliveries
If your NATS permissions granted either one explicitly, that grant is unused too.
Agent skill downloads moved
The info dialog no longer builds and serves the per-agent skill bundles. Install them from synadia-io/insights, which is where the plugin marketplace and Gemini extension already lived. See AI Agents.
After you upgrade
Confirm your checks resolve. Startup would have reported an unknown code, so this is mostly about a check you meant to disable and spelled correctly for the wrong entity.
insights config check
insights checks list
Give the new critical severities a day. Five consumer checks moved from warning to critical, so a pager routed on critical may see traffic it did not before. server-065 is also new and reports a server with no varz reply in an epoch where a peer still routes to it.
Check that epochs are complete. The new completeness columns show whether a scrape reached the whole fleet, which is useful to establish as a baseline.
SELECT epoch, servers_expected, servers_observed, partial_endpoints
FROM hx.epochs
WHERE servers_observed < servers_expected
ORDER BY epoch DESC LIMIT 20;
Going back
The database moves forward on first start: the findings table is renamed and rewritten, JSON columns are unwrapped, enum values are rewritten, repeated account identity rows are deleted, the audit schema is dropped, and columns and a table are added. A 0.1.10 binary will not find hx.check_results in a 1.2.0 catalog.
Going back means restoring the backup from Before you upgrade into a fresh data directory, pointing the old binary at it, and letting it scrape forward again. Epochs indexed since the upgrade are not carried back. Keep the pre-upgrade config file alongside the backup, since the new check codes are not valid on 0.1.10 either.