observability: chain-disk, restart-loop and Zcash chunk-store alerts (2026-08-26 chain disk-full outage) #241
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
key-store/key.store!241
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "claude/chain-health-alerts"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
The 1.8 TB chain disk (
/var/lib/key-store-chain, nvme1n1p1) filled to 100% around 2026-08-26 during a Reth resync.podman-key-store-zcash-mainnet-zebra,podman-key-store-ethereum-mainnet-{reth,lighthouse}andkey-store-bitcoindcrash-looped for ~9 days,key-store-zcash-chunk-producerloggedrpc getblockcount: io: Connection refusedon every pass, and https://zec.key.store/v1/mainnet/manifest.json stayed frozen atfinalized_through3459999. No alert fired, because:HostDiskLow/HostDiskCritical) only watch/;state=failedlong enough forCriticalUnitFailed(for: 3m);AnyUnitFailedselectstype="service", a label value node_exporter never emits (it is the serviceType=:oneshot/simple/notify…), so it has never been able to fire — two units are failed on the forge today with no alert;What
All rules are Grafana-managed, Nix-provisioned next to the existing forge-health groups (
services."key.store".grafana.alerting.chainHealth, default on; folder "key.store forge" → root policy →key.store dispatcherwebhook), so they go live with the forge switch rather than a founder-gatedcloud-tofuapply.> 85%used for 10 min (node_filesystem_*{mountpoint="/var/lib/key-store-chain"})< 100 GiBavailable for 5 minincrease(node_systemd_service_restart_total[10m]) > 5for anypodman-key-store-*,key-store-bitcoind,key-store-zcash-chunk-producerpass failed/follower pass failedin every 5-min window for 15 mingenerated_atolder than 6 h (a pass rewrites it every 30 s)finalized_throughunchanged for 30 hfinalized_through - verified_through > 1000for 6 hChunk-store probe (
services."key.store".zcash.chunks.probe, default on):key-store-zcash-chunks-probe.timerrunsservices/observability/zcash-chunks-manifest-textfile.shevery minute, reads the servedmanifest.jsonintokey_store_zcash_chunks_manifest_*node_exporter textfile gauges and keeps its own finalized/verified "advanced at" clocks underStateDirectory. On a read failure it re-emits the last good gauges withprobe_success 0, so the staleness clocks keep running instead of going NoData;OnFailure=key-store-failure-notice@covers a broken probe.Why not "finalized_through not advancing for > 6 h" literally: a compact range is 1000 blocks (~21 h at 75 s blocks), so
finalized_throughsteps roughly once a day and a 6 h rule on it alone would page daily. The manifest is rewritten on every pass, sogenerated_atis the 6 h freshness signal (it would have caught the incident within 6 h; the 15-min "pass failed" rule within 15 min; the restart-loop rule within 10 min; the disk rules days earlier). The 30 h finalization rule and the follower-lag rule cover the cases where passes still complete but ranges or layer 1b stop.Also:
https://zec.key.store/v1/mainnet/manifest.jsonadded to the blackbox targets (serving path + TLS via the existingPublicEndpointDown/TlsCertificateExpiring).infra/grafana/alerts.tf:AnyUnitFailedselectsname=~".+[.]service"instead of the never-matchingtype="service". This part needs a founder-gatedcloud-tofu(stackgrafana) apply; everything else is Nix.docs/infrastructure/forge-chain-nodes.md§ Alerts (rule table, probe, runbook);services/observability/graphs-on-forge.md.key-store-chain-storage-checkdf unit is kept as the journal-side record; the 85% page now comes from node_exporter.Validation
nix-instantiate --parseon the changed modules;nix evalof the probe unit/timer,alerting.chainHealth, and the blackbox target list.key-store-chain-health, 1 inkey-store-chain-health-logs, all withcondition: C, data,for, severity and summary./api/v1/query), including the correctedAnyUnitFailedselector (matches 410 service units; the old one matched 0).probe_success 1,generated_at/tip_height/finalized_through 3473999/verified_through 3472999as served.tofu fmt -checkclean.bash /root/drybranch.sh chain-health claude/chain-health-alerts→key-store-dry-chain-health.serviceResult=success, new generation/nix/store/c3m3sb7risvx3q1vlgi8b7dhnzav9n9x-nixos-system-keystore-forge-26.05.20260907.51f5794c; the built system containskey-store-zcash-chunks-probe.{service,timer}and thegrafana-envscript installs the two new rule files next to the forge-health ones.After merge
deploy-forge-host(switch) restartsgrafana-env→grafana(provisioning re-read) and starts the probe timer; the manifest gauges appear within ~4 minutes.cloud-tofu grafana apply(founder) for theAnyUnitFailedfix.🤖 Generated with Claude Code
The 1.8 TB chain disk (/var/lib/key-store-chain) filled to 100% around 2026-08-26 during a Reth resync; Zebra, Reth, Lighthouse and bitcoind crash-looped for nine days, key-store-zcash-chunk-producer logged "rpc getblockcount: io: Connection refused" on every pass and zec.key.store served a manifest frozen at finalized_through 3459999. No alert fired: the OpenTofu disk rules only watch "/", a crash-looping unit never sits in state=failed long enough for CriticalUnitFailed, AnyUnitFailed selects type="service" (a label value node_exporter never emits), and nothing read the manifest. - grafana module: alerting.chainHealth (default on) provisions two Grafana-managed groups next to forge-health, same folder / contact point / root policy: key-store-chain-health (chain disk > 85% used [warning] and < 100 GiB free [critical]; > 5 automatic restarts in 10 min for podman-key-store-* / key-store-bitcoind / key-store-zcash-chunk-producer [critical]; manifest not rewritten for 6 h [critical]; finalized_through not advancing for 30 h [warning]; layer-1b follower more than one range behind for 6 h [warning]; probe failing 30 min [warning]) and key-store-chain-health-logs ("pass failed" in every 5-min window for 15 min, Loki [warning]). - zcash module: chunks.probe (default on) -- key-store-zcash-chunks-probe timer runs services/observability/zcash-chunks-manifest-textfile.sh every minute: reads the served manifest.json into key_store_zcash_chunks_manifest_* textfile gauges and keeps its own finalized/verified "advanced at" clocks (finalized_through steps once per 1000-block range, ~21 h at 75 s blocks, so a 6 h rule on it alone would page daily; the manifest itself is rewritten every 30 s pass, hence generated_at is the 6 h freshness signal). On a read failure it re-emits the last good gauges with probe_success 0 so the staleness clocks keep running instead of going NoData. - forge host: OnFailure=key-store-failure-notice@ on the probe; https://zec.key.store/v1/mainnet/manifest.json added to the blackbox targets (serving path + TLS through the existing PublicEndpointDown / TlsCertificateExpiring rules). - infra/grafana/alerts.tf: AnyUnitFailed selects name=~".+[.]service" instead of type="service" (founder-gated cloud-tofu apply; two units are failed on the forge today with no alert). - docs: forge-chain-nodes.md "Alerts" (rule table, probe, runbook); graphs-on-forge.md alerting summary. Verified: nix-instantiate --parse; nix eval of the probe unit/timer, chainHealth options and blackbox targets; the provisioned YAML rendered (writeText stub) and parsed, 8 rules; every PromQL/LogQL expression parsed against the forge Prometheus/Loki; the probe script exercised against the live manifest (first run, advance, missing file, invalid JSON, fresh state); tofu fmt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>Pushed a follow-up commit lowering the layer-1b follower lagging threshold from
gt 1000togt 0(6 h) and updating the Alerts row: with #242 the producer verifies a range within a minute of its finalization, soverified_through == finalized_throughis the steady state and any sustained lag means the follower stopped.Merge order: #242 first, then this PR, so the lowered threshold never observes the old producer (which trailed by exactly one range and would have tripped it after 6 h).
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.