Skip to content

ops: make the disk alarm say what is eating the disk, and not invent rates - #308

Merged
ralyodio merged 1 commit into
masterfrom
ops/disk-alarm-attribution
Sep 25, 2026
Merged

ralyodio merged 1 commit into
masterfrom
ops/disk-alarm-attribution

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Two fixes from the disk alarm's first real firing (#306 merged ~8 minutes before it fired).

Attribution

It fired CRITICAL: will fill in ~26h at 57 GB/h and said nothing about the cause. The cause turned out to be 38 GB of docker build cache from an image build — not the databases at all — and establishing that cost a round trip with another session.

It now reports docker usage and the largest databases inline, to syslog as well as mail:

CRITICAL: will fill in ~26h at 57 GB/h (1502 GB free).
  docker: Images 17.41GB (181.1MB reclaimable)
  docker: Build Cache 7.156GB (42.68MB reclaimable)
  databases: nichedb 163 GB, postgres 4764 MB, _supabase 7827 kB

Parsing needed --format rather than positional awk: "Build Cache" is two whitespace-separated fields in docker system df's table output, which shifts every column. The first version printed build-cache 40 — an object count — as though it were a size.

Rate sanity

Two samples seconds apart produce numbers like 166 GB/h from ordinary write jitter. That pollutes the growth record and can fire a spurious CRITICAL, since the 48h projection divides by the rate. Rates now require a 300s interval, and a too-short sample leaves the baseline untouched so the next real sample still measures from a sensible point.

Verified on dev2

  • attribution prints real sizes (Images 17.41GB, Build Cache 7.156GB, nichedb 163 GB)
  • two back-to-back runs both report rate=? instead of nonsense
  • test noise cleaned from /var/log/dev2-disk.log; the legitimate 3 and 57 GB/h entries remain

…rates

Two fixes from the alarm's first real firing.

ATTRIBUTION. It fired CRITICAL at 57 GB/h and said nothing about the cause.
The cause turned out to be 38 GB of docker build cache from an image build,
not the databases at all, and establishing that cost a round trip. The alarm
now reports docker usage and the largest databases inline, to syslog as well
as mail, so the first question anyone asks is already answered.

Parsing it needed --format rather than positional awk: "Build Cache" is two
whitespace-separated fields in `docker system df`'s table output, which shifts
every column and silently prints counts where you expect sizes. The first
version reported "build-cache 40" — an object count — as though it were a size.

RATE SANITY. Two samples seconds apart produce numbers like 166 GB/h from
ordinary write jitter. That pollutes the growth record and can fire a spurious
CRITICAL, since the 48h projection divides by it. Rates now need a 300s
interval, and a too-short sample leaves the baseline alone so the next real
sample still measures from a sensible point.

Verified on dev2: attribution prints real sizes (Images 17.41GB, Build Cache
7.156GB, nichedb 163 GB), and two back-to-back runs both report rate=? instead
of nonsense.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fi
# Sizes straight from the running cluster, largest first.
if docker exec -i supabase-db psql -U postgres -h localhost -d postgres -X -At \
-c "select string_agg(datname || ' ' || pg_size_pretty(pg_database_size(datname)), ', ' order by pg_database_size(datname) desc) from pg_database where not datistemplate" >/tmp/.dbsz 2>/dev/null; then
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

49 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 32 | LOW: 15

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:48
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM js-dynamic-code-execution lib/crawl-limits.ts:67
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:99
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-predictable-temp-path ops/selfhost/server/setup-supabase.sh:201
MEDIUM sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:14
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:24
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:20
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:24
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:25
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:26
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:31
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:35
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit da025ab into master Sep 25, 2026
10 checks passed
@ralyodio
ralyodio deleted the ops/disk-alarm-attribution branch September 25, 2026 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants