fix(db): auto-clean conversation_turn_nodes and orphaned conversations - #12548
Open
pacocartones wants to merge 2 commits into
Open
Conversation
conversation_turn_nodes and agentic_conversations (migrations 155/156) had no retention path: nothing ever deleted from them, so storage.sqlite grew without bound (1.15M node rows, ~775 MB in four days on one busy coding-agent workload). The nodes are identity-only and resolve their display content from the call_logs row last_correlation_id points at, so once call-log retention purges that row the node is dead weight. Both tables now follow the existing retention.callLogs window inside runAutoCleanup: nodes older than the window are deleted, then agentic_conversations rows past the window that no longer have any node are swept with a NOT EXISTS probe bounded by the indexed last_seen_at column. Closes diegosouzapw#12453
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
conversation_turn_nodesandagentic_conversations(migrations 155/156, feat(dashboard): agentic conversation tracking — v4, decoupled + storage-architecture concern resolved #10263) had no retention path at all:src/lib/db/cleanup.tsnever referenced either table, there is no cascade FK, and nothing else deletes from them. The reporter's numbers in fix(backend): conversation_turn_nodes / agentic_conversations have no retention — unbounded storage.sqlite growth #12453 (1.15M node rows, ~775 MB of a 1.1 GBstorage.sqliteafter four days, ~190 MB/day) are the same failure class as feat(backend): auto-cleanup/upsert for telemetry tables to bound storage.sqlite #6848 / fix: startup cleanup ignores dashboard data-retention setting (always deletes logs older than 7d) #4354 / fix(db): pre-migration backups are never pruned — db_backups grew to 204 GB / 49k files #10421.call_logsrow thatlast_correlation_idpoints at (156_conversation_turn_nodes.sqlheader). OncecleanupCallLogspurges that row the node can never render again, so both tables now follow the existingretention.callLogswindow (src/types/databaseSettings.ts) instead of getting a knob of their own, as the issue proposes. No new dashboard setting, no new environment variable, anddocs/reference/ENVIRONMENT.mdis untouched because there is nothing new to document there.runAutoCleanup:cleanupConversationTurnNodes()deletes nodes withlast_seen_atbefore the cutoff through the shareddeleteFromTableBeforehelper (src/lib/db/cleanup/usagePurge.ts, so a database that predates migration 156 is a no-op rather than an error);cleanupAgenticConversations()then sweeps roots past the same cutoff that have no remaining node, withWHERE last_seen_at < ? AND NOT EXISTS (SELECT 1 FROM conversation_turn_nodes n WHERE n.conversation_id = agentic_conversations.id). Thelast_seen_atguard uses the index from migration 155 and keeps a root thatcreateConversationwrote moments ago, before the same request inserted its nodes.resolveConversationId. Deliberately out of scope: the "Clear all" /resetUsageHistorypaths (a separate, user-triggered contract), and alast_seen_atindex onconversation_turn_nodes. The node DELETE is a table scan today because migration 156 defined no index on that column; it runs once per cleanup cycle, and I can send the index as a follow-up migration if the maintainer wants it.Related Issues
Validation
tests/unit/db-cleanup-conversation-nodes-12453.test.ts+telemetry-auto-cleanup-6848+db-cleanup+db-cleanup-xp-audit-log23/23,npm run check:db-rulesOK,node scripts/check/check-complexity-ratchets.mjs --base-ref origin/release/v3.8.51OK (0 violations),npm run check:changelog-integrityOK,npm run typecheck:core0 errorsnpm run lintrelease/v3.8.51; focused checks rerun afterwardTests Added Or Updated
tests/unit/db-cleanup-conversation-nodes-12453.test.ts(new, 4 cases, real SQLite adapter in amkdtempDATA_DIR, same pattern astelemetry-auto-cleanup-6848.test.ts): nodes older thanretention.callLogsdeleted and recent ones kept; stale orphan root swept while a stale-but-anchored root and a fresh root without nodes both stay; a chain whose nodes expire is swept in the same pass, and never before its nodes are gone;runAutoCleanupreportsconversationTurnNodesandagenticConversationswith the expected counts. Red on the base (4/4 fail:cleanupConversationTurnNodes is not a function,cleanupAgenticConversations is not a function,conversationTurnNodes missing from results), green with the change (4/4).Coverage Notes
src/lib/db/cleanup.ts:cleanupConversationTurnNodes,cleanupAgenticConversationsand their registration inrunAutoCleanupare exercised end-to-end by the new test file; the existingtests/unit/db-cleanup.test.tsandtelemetry-auto-cleanup-6848.test.tsstay green.Reviewer Notes
cleanup.ts: two new functions inserted beforerunAutoCleanupand two new entries at the end of itsresultsmap. feat(teams): add billing cost centers and soft shared budgets [defer to 3.8.51] #10409 also edits this file, but in different hunks (cleanupMcpAudit,cleanupA2aEvents,resetUsageHistory), so the two should merge cleanly in either order. feat(dashboard): parent-link, genuine-continuation badge, and modal perf fixes #12448 editssrc/lib/db/agenticConversations.ts, which this PR does not touch.retention.callLogs(30 days), conversations idle for longer than that lose their reconnect anchor and start a new conversation id on resume. Operators who set a short call-log retention (the reporter runs 3 days) get the same window for anchors, which is the trade-off the issue asks for.conversation_turn_nodes(nolast_seen_atindex in migration 156). On the reporter's 1.15M-row table that is one pass per cleanup cycle; if that is a concern I will add the index as a follow-up migration rather than widen this PR.