Skip to content

fix(db): auto-clean conversation_turn_nodes and orphaned conversations - #12548

Open
pacocartones wants to merge 2 commits into
diegosouzapw:release/v3.8.51from
pacocartones:fix/db-cleanup-orphaned-conversation-nodes
Open

fix(db): auto-clean conversation_turn_nodes and orphaned conversations#12548
pacocartones wants to merge 2 commits into
diegosouzapw:release/v3.8.51from
pacocartones:fix/db-cleanup-orphaned-conversation-nodes

Conversation

@pacocartones

Copy link
Copy Markdown
Contributor

Summary

  • conversation_turn_nodes and agentic_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.ts never 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 GB storage.sqlite after 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.
  • The nodes are identity-only: the transcript view resolves each turn's display content from the call_logs row that last_correlation_id points at (156_conversation_turn_nodes.sql header). Once cleanupCallLogs purges that row the node can never render again, so both tables now follow the existing retention.callLogs window (src/types/databaseSettings.ts) instead of getting a knob of their own, as the issue proposes. No new dashboard setting, no new environment variable, and docs/reference/ENVIRONMENT.md is untouched because there is nothing new to document there.
  • Two new functions registered at the end of runAutoCleanup: cleanupConversationTurnNodes() deletes nodes with last_seen_at before the cutoff through the shared deleteFromTableBefore helper (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, with WHERE last_seen_at < ? AND NOT EXISTS (SELECT 1 FROM conversation_turn_nodes n WHERE n.conversation_id = agentic_conversations.id). The last_seen_at guard uses the index from migration 155 and keeps a root that createConversation wrote moments ago, before the same request inserted its nodes.
  • Semantics are the ones the issue describes: deleting an old node only affects reconnect anchors, and a conversation resumed after the window mints a new id, which is already the documented anchor-miss behavior of resolveConversationId. Deliberately out of scope: the "Clear all" / resetUsageHistory paths (a separate, user-triggered contract), and a last_seen_at index on conversation_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

  • Change type: DB (auto-cleanup lifecycle)
  • Focused tests and category gates from the golden path: tests/unit/db-cleanup-conversation-nodes-12453.test.ts + telemetry-auto-cleanup-6848 + db-cleanup + db-cleanup-xp-audit-log 23/23, npm run check:db-rules OK, node scripts/check/check-complexity-ratchets.mjs --base-ref origin/release/v3.8.51 OK (0 violations), npm run check:changelog-integrity OK, npm run typecheck:core 0 errors
  • npm run lint
  • Reconciled with the current active release base release/v3.8.51; focused checks rerun afterward
  • Production-code changes include a new or updated automated test in this PR
  • SonarQube is temporarily opt-in while the private project has no quota; it is not a PR gate.

Tests Added Or Updated

  • tests/unit/db-cleanup-conversation-nodes-12453.test.ts (new, 4 cases, real SQLite adapter in a mkdtemp DATA_DIR, same pattern as telemetry-auto-cleanup-6848.test.ts): nodes older than retention.callLogs deleted 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; runAutoCleanup reports conversationTurnNodes and agenticConversations with 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, cleanupAgenticConversations and their registration in runAutoCleanup are exercised end-to-end by the new test file; the existing tests/unit/db-cleanup.test.ts and telemetry-auto-cleanup-6848.test.ts stay green.
  • No touched file lost coverage.

Reviewer Notes

  • Purely additive in cleanup.ts: two new functions inserted before runAutoCleanup and two new entries at the end of its results map. 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 edits src/lib/db/agenticConversations.ts, which this PR does not touch.
  • Behavior change for operators: with the default 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.
  • The node DELETE scans conversation_turn_nodes (no last_seen_at index 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.

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
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.

fix(backend): conversation_turn_nodes / agentic_conversations have no retention — unbounded storage.sqlite growth

1 participant