fix(db): dedupe signal_snapshots to bounded latest-per-key retention - #3857
Conversation
…3810) signal_snapshots had no dedup: generate-signal-snapshots inserts a new row per (signal_type, target_key) on every run instead of replacing the prior one, so within the existing 90-day retention window a single key could accumulate hundreds of superseded rows (342,243 rows for 2,183 distinct keys, contributing to hitting D1's size cap on 2026-07-06). Adds dedupeSignalSnapshots, which keeps only the latest row per (signal_type, target_key), batched per signal_type so each DELETE stays within D1's per-statement CPU budget. Wired into the existing daily prune-retention job/audit event and the read-only retention preview route alongside pruneExpiredRecords.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3857 +/- ##
=======================================
Coverage 93.48% 93.48%
=======================================
Files 333 333
Lines 33162 33188 +26
Branches 12128 12136 +8
=======================================
+ Hits 31001 31027 +26
Misses 1530 1530
Partials 631 631
🚀 New features to boost your workflow:
|
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-06 20:49:11 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 5 non-blocking
Concerns raised — review before merging
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
…s, panel (#4153) Advances #3810 -- the dedup/retention fix (dedupeSignalSnapshots) already shipped in #3857; this covers the remaining, deliberately-deferred half: observability for the ~10GB D1 storage cap that incident hit. Adds an opt-in Cloudflare Management API probe (src/selfhost/d1-size-probe.ts, gated on CLOUDFLARE_D1_MONITOR_ACCOUNT_ID/DATABASE_ID/API_TOKEN -- absent on almost every self-host install, which runs its own SQLite/Postgres backend) that polls D1's file size and RETENTION_POLICY's monitored tables' row counts every 15 minutes from the self-host process, publishing gittensory_d1_database_size_bytes, gittensory_d1_table_row_count{table=...}, and gittensory_signal_snapshots_rows_per_key (scoped to the latest-only-dedup signal types dedupeSignalSnapshots converges to ~1 row per key, so a climbing ratio means that job has stopped running or its allowlist regressed). Size and each table's row count are fetched independently and a failure in one never blanks the other or the previous good reading, recorded via gittensory_d1_probe_errors_total. Adds four Prometheus alerts (D1 size warn ~70% / critical ~90% of the ~10GB cap, the signal_snapshots dedup-regression ratio, and probe-failure) and a "Cloudflare D1 (Central Cloud)" row on the self-host Grafana dashboard.
Summary
signal_snapshotshas no dedup:generate-signal-snapshotsinserts a new row per(signal_type, target_key)on every run instead of replacing the prior one. Within the existing 90-day retention window this let a single key accumulate hundreds of superseded rows — 342,243 rows for 2,183 distinct keys, contributing to hitting D1's size cap on 2026-07-06.dedupeSignalSnapshots(src/db/retention.ts), which keeps only the latest row (highest rowid) per(signal_type, target_key), batched per signal_type so each DELETE stays within D1's per-statement CPU budget — the same batching split used during the incident's manual remediation.prune-retentionjob (runRetentionPrune), merging its audit-event detail/metadata with the existing prune results, and into the read-only/v1/internal/retention/previewroute alongsidepruneExpiredRecords.This addresses the dedup/retention-job requirement of #3810, which the issue itself calls "the actual root-cause fix." The remaining #3810 asks (a scheduled Cloudflare D1 size probe via the Management API, new
gittensory_d1_*gauge metrics, and new Prometheus alert/Grafana panel rules) are deliberately not included here: there are no Cloudflare Management API credentials/integration anywhere in this codebase today, and the self-host Prometheus/Grafana stack has no path to scrape a metric computed inside the Cloudflare Worker that owns D1 — wiring that up is a materially different, higher-risk architectural change (new secret handling, a new cross-deployment metrics bridge) that deserves its own scoped PR rather than being bundled with a clean, narrow, fully-tested fix. Left a comment on #3810 with this breakdown.Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally;codecov/patchrequires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateAll run via
npm run test:ci(full local gate, green) plus a standalonenpm audit --audit-level=moderate(0 vulnerabilities). Verified every new/changed line and branch insrc/db/retention.ts,src/queue/processors.ts, andsrc/api/routes.tsis covered locally (v8 lcov branch report), including the defensive?? 0fallback arms via hand-mockedenv.DBresults (no meta / no row), mirroring the existing pattern intest/unit/alerts.test.ts.Safety
UI Evidencesection below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails.Backend-only data-retention change: no auth/CORS/session surface, no UI, no docs/changelog impact.
UI Evidence
N/A — backend-only change, no visible UI surface.
Notes
gittensory_d1_database_size_bytes/gittensory_d1_table_row_countgauges, and the corresponding Prometheus alert rule + Grafana panel.