Repository navigation
Conversation
First part of the #852 cleanup script: creates its dated backup, work and progress tables and walks the dedup index in time windows, recording every key with more than one NULL-channel copy. Resumes from the last window. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
Adds the clean command: pending keys are taken in whole-key batches; each transaction locks the key's NULL-channel copies, keeps the copy data-sink-worker reads, carries parent and conversation ids over to it, moves its updatedAt past every deleted copy and moves the other copies to the dated backup table. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
…nightly export clean waits while a Sequin replication slot lags past --max-slot-lag-mb, refuses to run during the nightly meetings export unless --allow-nightly, needs --max-rows or --until, and stops cleanly after the current batch on Ctrl-C or SIGTERM. A batch whose rows are locked is retried. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
…bservable Review fixes: rank and delete only the copies the batch locked (NOWAIT, in activityId order), recheck the NULL channel on update and delete, and roll the batch back unless every key keeps exactly one copy and every deleted row is in the backup. The kept row's updatedAt only moves past the deleted copies at millisecond precision, never back and never to the clock. Keys come from the work table by id; lock, deadlock and timeout failures retry with backoff and then defer the keys. Slot lag fails closed, stop conditions are rechecked after waits, flags are validated, real runs ask for confirmation and hold a per-tag advisory lock. Every batch records its metrics in a batches table and logs a structured batch.done line; runs log progress with remaining rows, ETA and backup size, and a summary with an exit code. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
…plicated keys Final review fixes: collect jumps over empty years and only moves its progress forward (never on an explicit --to); re-collecting re-opens finished keys that have duplicates again. clean gives the lock step its own 60 s timeout, splits a batch that times out instead of retrying it whole, can pause through the nightly export (--wait-nightly, --until up to 7 days), waits up to 2 minutes for the run lock and names its holder, exits 4 on a lost connection, logs batch.start, and records batch metrics inside the batch transaction. The kept row moves 2 ms past the deleted copies. status shows the active run and percent done. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
Day rounding used ms - ms % DAY, which rounds negative epochs up, so collect skipped the first day with rows on every window before 1970 (prod rows go back to year 0026). Also: a split batch stopped by the nightly window no longer leaves keys behind the cursor, Ctrl-C at the confirmation prompt aborts, a lost replication slot exits 1 instead of the usage code, a re-opened key keeps its deleted count, --help prints the full usage, and pg errors are typed instead of any. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
…in the null-channel cleanup The lock step now writes the locked ids into a session temp table (on commit delete rows) instead of returning them as a 20k-element array param, so the shared pg-promise error hook no longer logs ~800 KB per retried batch. A connection lost mid-run that reaches @crowd/database's bare process.exit() now exits 4 (restartable) instead of 1. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
…l-channel cleanup collect --rescan scans from the oldest row again: without it a final sweep with the same tag stopped at collect.uptodate and re-opened nothing. The clean plan takes the kept row's columns from window functions instead of joining the ranked CTE back to itself, which the planner ran as a nested loop over the whole batch per kept row (7x faster on a 20k-row batch). Also: the rate and ETA leave out slot and nightly waits, keys without duplicates count keys with no locked rows, the WAL metric is named serverWalBytes since it is server-wide, and a run lock still held after the wait exits 2 so a restart loop retries. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
The stored collect end was read with a plain select, which returned no row after a collect that never finished a window or only ran with --to, so every later collect failed with "No data returned from the query". It now reads max(windowEnd), which is null for an empty table. The run-lock holder lookup is also limited to the current database. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
PR SummaryHigh Risk Overview Wires Reviewed by Cursor Bugbot for commit 385e57b. Bugbot is set up for automated code reviews on this repo. Configure here. |
[High impact] The PR appears safe to merge; no new actionable issues were found. SummaryAdds a resumable script to collect and remove NULL-channel duplicates from
Reviews (12) · Last reviewed commit: "fix: stop a null-channel collect whose p..." · Reviewed by Greptile |
There was a problem hiding this comment.
🔵 Needs a closer look
Cleanup retries and split batches can bypass stop, time, and replication-lag safeguards, while batch deletion can exceed the configured maximum row limit.
No open issues 🎉
What changed in this PR
Adds a hand-run maintenance script to remove NULL-channel duplicates from activityRelations, retaining the copy selected by data-sink-worker and backing up deleted rows. It builds on the concurrent batch-move fix in #5258.
Changes:
- Adds resumable key collection, transactional cleanup, and status reporting.
- Preserves relationship fields and adjusts survivor timestamps for downstream replication, with safety checks, retries, and operational guards.
- Registers a package command for running the script.
| File | Description |
|---|---|
services/apps/script_executor_worker/src/bin/cleanup-null-channel-duplicates.ts |
Implements collection, cleanup, backups, guards, and progress reporting. |
services/apps/script_executor_worker/package.json |
Registers the cleanup script command. |
Review effort: Balanced
…ap scans Every batch attempt, retry and split half now re-checks the stop conditions (signal, --until, --max-rows, nightly window) and waits on replication-slot lag first, so a retry delay that crosses a deadline no longer starts more work. --max-rows trims each batch to what is left (whole keys only). A run ends with exit 3 while deferred keys from any run remain in its scope. A collect whose --from starts past the scanned range no longer records progress, so the skipped days are still scanned later. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
|
Re: Copilot review · summary findings
|
USAGE already carries the description and the issue reference. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved checkpoint and rescan-resume defects, incorrect nightly-stop exit handling, and ineffective dry-run sample limits can block valid cleanup runs and exceed intended query bounds.
No open issues 🎉
Review effort: Balanced
…r-platform collects resume --max-rows is now a ceiling: a key that doesn't fit what is left isn't started, and a batch that would still go over (copies added since collect) rolls back and the run stops with exit 2. An interrupted --rescan continues from its own progress row, removed when the rescan finishes. Clean counts collect coverage per platform, so collects done in separate platform runs are accepted. Not starting inside the nightly window exits 2 like stopping there does. Collect windows halve down to an hour, so one dense day can't block a collect. Dry runs count every batch tried towards --sample-batches. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
There was a problem hiding this comment.
🔵 Needs a closer look
Human assessment is needed because skipped keeper timestamp updates can let replicated DELETE before-images replace keeper metadata and propagate deleted-copy metadata into daily snapshots.
No open issues 🎉
Review effort: Balanced
|
Re: Copilot review · summary findings
|
…atus status always reported every platform of the tag, broken down per platform, so the accepted --platforms only suggested a filter that didn't happen. It is now rejected as an unknown option. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
|
Re: Copilot review · summary findings
|
There was a problem hiding this comment.
🔵 Needs a closer look
Deleting roughly 100M rows with live writers requires DBA and data-platform validation of query plans, locking, CDC convergence, and recovery beyond the reported synthetic local tests.
No open issues 🎉
Review effort: Balanced
|
Re: Copilot review · summary findings
|
… and --to Date rolled 2025-02-30 over to 2025-03-02, so a typo silently moved the collect range. A date now has to come back unchanged from parsing, or the run stops with a usage error. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
|
Re: Copilot review · summary findings
|
…ompt A run waiting for the operator's answer held the run lock and its session, and SIGTERM or SIGHUP didn't end the prompt. The lock is now taken after the confirmation, and the prompt closes on any stop signal. The help text also says that collect --dry-run scans the same range as a real collect. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>
|
Re: Copilot review · summary findings
|
There was a problem hiding this comment.
🔵 Needs a closer look
Deleting ~100M live rows requires DBA/data-pipeline validation of concurrent-write locking and Sequin-to-Tinybird delete ordering that source review and synthetic tests cannot establish.
No open issues 🎉
Review effort: Balanced
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0265d35. Configure here.
|
Re: Copilot review · summary findings
|
…d at the prompt Collect plans its range before the confirmation prompt and takes the run lock after it, so another run could move or finish the progress in between. After the lock it now reads the progress again and stops with exit 2 when it changed, instead of scanning from a stale start. Refs linuxfoundation/lfx-cdp-insights-ops#852 Signed-off-by: Uroš Marolt <uros@marolt.me>

Summary
Adds a hand-run script that removes the NULL-channel duplicates in
activityRelations. Before #850, every re-send of an activity without a channel inserted a new row, because NULLs are distinct in the dedup index. The script keeps one copy per key and moves the others to a dated backup table. The copy it keeps is the one data-sink-worker already reads: newest byupdatedAt,createdAt,activityId, the same order asfindActivityRelationsByDedupKeys. It runs in short transactions that can be stopped and resumed.Changes
collectwalks the dedup index in time windows. It records every key with more than one NULL-channel copy for the chosen platforms in a work table. Windows halve on a statement timeout, days without rows are skipped, and it resumes from the last finished window.cleantakes whole keys, up to--batch-rowsrows per transaction:FOR UPDATE NOWAIT, so it never waits on app transactions.parentId,conversationIdandsourceParentIdonto the kept copy when that copy has none.updatedAtat least 2 ms past every deleted copy's, so CDC and Tinybird see it as newest.--max-rows/--untilrequired for a real run;--retry-deferred.collect --rescanscans from the oldest row again after clean. It re-opens keys that got duplicates in the meantime, and a secondcleanhandles them.statusfor progress at any time. Exit codes tell a wrapper whether to restart (2stopped,4connection lost) or stop and look (1).queryExecutor, on purpose. The advisory run lock, the session temp table of locked ids and theset localtimeouts all have to live on one connection for the whole run.Type of change
Related issue
Refs linuxfoundation/lfx-cdp-insights-ops#852