Skip to content

Commit b840bcc

Browse files
waleedlatif1claude
andcommitted
refactor(copilot): replace boot-time catch-up with manual reconcile script
The boot-time messages catch-up ran on every replica × every deploy, burning ~210K row-touches/day on prod to solve a problem (dual-write transient drift) that doesn't affect users while JSONB is canonical. Replace it with a one-shot bun script that can be run manually before the eventual R+1 read cutover, or after a known outage. Default scope is the full table; --since='<interval>' narrows the window. bun apps/sim/scripts/copilot-messages-reconcile.ts bun apps/sim/scripts/copilot-messages-reconcile.ts --since='7 days' Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent b7d172b commit b840bcc

3 files changed

Lines changed: 81 additions & 50 deletions

File tree

apps/sim/instrumentation-node.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,4 @@ export async function register() {
312312

313313
const { startMemoryTelemetry } = await import('./lib/monitoring/memory-telemetry')
314314
startMemoryTelemetry()
315-
316-
// Fire-and-forget catch-up sweep: bounded to the last 7 days, idempotent,
317-
// runs in the background so server boot isn't blocked.
318-
void import('./lib/copilot/chat/messages-catchup')
319-
.then((mod) => mod.catchUpCopilotChatMessages())
320-
.catch((err) => {
321-
logger.warn('Failed to schedule copilot chat messages catch-up', err)
322-
})
323315
}

apps/sim/lib/copilot/chat/messages-catchup.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env bun
2+
3+
/**
4+
* One-shot reconciliation: copy any messages present in
5+
* `copilot_chats.messages` JSONB but missing from `copilot_chat_messages`.
6+
*
7+
* Idempotent via `ON CONFLICT (chat_id, message_id) DO NOTHING`. Safe to
8+
* re-run. Intended to be run manually before cutting reads over to the new
9+
* table (R+1 of the dual-write rollout), or after a known dual-write outage.
10+
*
11+
* Usage:
12+
* DATABASE_URL=... bun apps/sim/scripts/copilot-messages-reconcile.ts [--since=<interval>]
13+
*
14+
* Examples:
15+
* bun apps/sim/scripts/copilot-messages-reconcile.ts
16+
* bun apps/sim/scripts/copilot-messages-reconcile.ts --since='7 days'
17+
* bun apps/sim/scripts/copilot-messages-reconcile.ts --since='1 hour'
18+
*
19+
* Omit --since to reconcile the entire table.
20+
*/
21+
22+
import { sql } from 'drizzle-orm'
23+
import { db } from '../../../packages/db/db.js'
24+
25+
function parseSinceArg(argv: string[]): string | null {
26+
const arg = argv.find((a) => a.startsWith('--since='))
27+
if (!arg) return null
28+
const value = arg.slice('--since='.length).trim()
29+
if (!value) {
30+
throw new Error('--since requires a value, e.g. --since="7 days"')
31+
}
32+
if (!/^[\w\s]+$/.test(value)) {
33+
throw new Error(`--since value must be a simple interval like "7 days"; got: ${value}`)
34+
}
35+
return value
36+
}
37+
38+
async function main(): Promise<void> {
39+
const since = parseSinceArg(process.argv.slice(2))
40+
const windowClause = since
41+
? sql`AND c.updated_at > now() - ${sql.raw(`interval '${since}'`)}`
42+
: sql``
43+
44+
console.log(
45+
since
46+
? `Reconciling copilot_chat_messages for chats updated in the last ${since}…`
47+
: 'Reconciling copilot_chat_messages across the full copilot_chats table…'
48+
)
49+
const startedAt = Date.now()
50+
51+
const result = await db.execute(sql`
52+
INSERT INTO copilot_chat_messages (chat_id, message_id, role, content, model, created_at, updated_at)
53+
SELECT
54+
c.id,
55+
msg.value->>'id',
56+
msg.value->>'role',
57+
msg.value,
58+
c.model,
59+
(msg.value->>'timestamp')::timestamptz,
60+
(msg.value->>'timestamp')::timestamptz
61+
FROM copilot_chats c
62+
CROSS JOIN LATERAL jsonb_array_elements(c.messages) AS msg(value)
63+
WHERE jsonb_typeof(c.messages) = 'array'
64+
AND jsonb_array_length(c.messages) > 0
65+
${windowClause}
66+
ON CONFLICT (chat_id, message_id) DO NOTHING
67+
`)
68+
69+
const elapsedMs = Date.now() - startedAt
70+
const rowCount = (result as { rowCount?: number }).rowCount ?? 0
71+
console.log(`Inserted ${rowCount} new rows in ${(elapsedMs / 1000).toFixed(1)}s.`)
72+
}
73+
74+
main()
75+
.catch((err) => {
76+
console.error('Reconciliation failed:', err)
77+
process.exit(1)
78+
})
79+
.finally(() => {
80+
process.exit(0)
81+
})

0 commit comments

Comments
 (0)