Skip to content

Custodian: a sweeper to delete apps in smaller chunks - #2845

Open
dwwoelfel wants to merge 22 commits into
mainfrom
custodian
Open

Custodian: a sweeper to delete apps in smaller chunks#2845
dwwoelfel wants to merge 22 commits into
mainfrom
custodian

Conversation

@dwwoelfel

@dwwoelfel dwwoelfel commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Creates a new custodian process that allows us to delete large apps. Previously, we had to set a very long timeout to delete large apps. That put pressure on the invalidator and would fail for very large apps.

This PR will delete the triples and transactions in batches first, then finally delete the app.

Each machine runs one deletion process that polls for new work in the background. We only have one because my main concern is preventing too much load on the database. If the database can handle it and the replication lag isn't affected, we could increase the number of processes.

Each process is essentially a linked list in the database. It looks like this:

Delete app:
Delete app -> Delete attrs -> Delete triples -> Delete transactions

Delete attr
Delete attr -> Delete triples

The worker grabs the tail of the list, performs the work, then deletes the item so the next worker can pick up the new tail of the list.

Delete transactions and Delete triples both run a long-running process that deletes the rows 1000 at a time (configurable with a flag). Each time it deletes, it 1. checks to make sure the app/attr has deletion_marked_at and 2. updates a heartbeat on the job. There's a separate cron process that frees stuck jobs.

In the background, we poll the replication slots to make sure that the lag doesn't get out of hand. If it does, we automatically pause the workers until replication catches up. We can raise or lower the lag threshold with a flag.

If a job fails, we'll try it two more times, then mark it failed. Then we'll try it again after 24 hours.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds a database-backed custodian worker for dependency-ordered app and attribute deletion. It supports leases, bounded cleanup, retries, replication-lag backpressure, lifecycle control, sweeper integration, workflow tests, and reactive processing updates.

Changes

Custodian deletion processing

Layer / File(s) Summary
Custodian storage and enqueue contracts
server/resources/migrations/124_add_custodian.*.sql, server/src/instant/custodian.clj, server/test/instant/custodian_test.clj
Adds the custodian table, indexes, timestamp trigger, rollback, and idempotent app and attribute deletion-plan enqueue operations.
Claiming and bounded deletion
server/src/instant/custodian.clj, server/test/instant/custodian_test.clj
Adds leased row claiming, stale-owner reaping, replication-lag backpressure, bounded cleanup, dependency processing, ownership validation, retry handling, and workflow tests.
Worker lifecycle and application integration
server/src/instant/custodian.clj, server/src/instant/core.clj, server/src/instant/flags.clj, server/src/instant/hard_deletion_sweeper.clj, server/test/instant/custodian_test.clj
Starts and stops the custodian worker with application lifecycle events. The hard-deletion sweeper now enqueues work through custodian. Tests isolate the worker during workflow checks.

Reactive change processing

Layer / File(s) Summary
Reactive batching and WAL handling
server/src/instant/reactive/aggregator.clj, server/src/instant/reactive/invalidator.clj
Reduces the initial sketch partition size from 1000 to 100 and accepts transaction-only WAL records.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Application
  participant HardDeletionSweeper
  participant Custodian
  participant Database
  Application->>Custodian: start worker
  HardDeletionSweeper->>Custodian: enqueue app or attribute deletion
  Custodian->>Database: claim dependency-ready row
  Custodian->>Database: delete bounded data batch
  Database-->>Custodian: commit and clear dependency
  Application->>Custodian: stop worker
Loading

Suggested reviewers: stopachka

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding a custodian sweeper that deletes apps in smaller chunks.
Description check ✅ Passed The description directly explains the custodian process, batch deletion workflow, retries, and replication-lag handling.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
server/src/instant/custodian.clj (4)

217-219: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid returning :* when only the count is needed.

The deleted CTE returns every column of every deleted row. For a batch of batch-size triples this materializes wide rows for no reason. Return a constant instead.

♻️ Proposed change
            [:deleted {:delete-from table
                       :where [:in :ctid {:select :ctid :from :to-delete}]
-                      :returning :*}]]
+                      :returning [[:inline 1] :one]}]]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/instant/custodian.clj` around lines 217 - 219, Update the deleted
CTE definition to return a constant value instead of all columns, replacing
:returning :* while preserving the existing delete condition and CTE structure.

11-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update the docstring: rows do not have done_at.

The schema in server/resources/migrations/124_add_custodian.up.sql has no done_at column. A step finishes by deleting its row, and the on delete set null FK clears the dependent's depends_on. The docstring at Line 12 and Line 14 still describes a done_at state.

📝 Proposed doc fix
-   `depends_on` points at the step that must finish first, so a row is runnable
-   once the row it depends on is `done_at`. Each worker claims one runnable row
-   with a lease, drains it in `batch-size` chunks (each chunk its own committed
-   transaction), then marks it done — which makes its dependent runnable. The
+   `depends_on` points at the step that must finish first, so a row is runnable
+   once `depends_on` is null. Each worker claims one runnable row with a lease,
+   drains it in `batch-size` chunks (each chunk its own committed transaction),
+   then deletes its row; the `on delete set null` FK clears `depends_on` on the
+   dependent, which makes the dependent runnable. The
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/instant/custodian.clj` around lines 11 - 16, Update the custodian
docstring to remove all references to rows becoming runnable through a done_at
state. Describe that completing a step deletes its row, the on-delete-set-null
foreign key clears the dependent row’s depends_on, and the dependent then
becomes runnable; retain the existing lease, batching, transaction, and cascade
behavior.

332-342: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Do not put side effects inside swap!.

swap! can retry its function when the atom is contended. A retry would call start-worker twice and leak the first worker's future and chime schedulers. stop has the mirror problem: it could call stop-worker twice. Use locking on the atom instead.

♻️ Proposed change
 (defn start []
-  (swap! worker
-         (fn [w]
-           (or w (start-worker)))))
+  (locking worker
+    (when-not `@worker`
+      (reset! worker (start-worker)))))
 
 (defn stop []
-  (swap! worker
-         (fn [w]
-           (when w
-             (stop-worker w))
-           nil)))
+  (locking worker
+    (when-let [w `@worker`]
+      (stop-worker w))
+    (reset! worker nil)))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/instant/custodian.clj` around lines 332 - 342, Replace the
side-effecting swap! callbacks in start and stop with locking on the worker
atom, performing start-worker or stop-worker exactly once while holding the lock
and updating the atom explicitly. Preserve start’s existing-worker reuse and
stop’s nil reset behavior.

253-259: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Add a default branch to the case on type.

case without a default throws IllegalArgumentException for an unrecognized type. tick! then swallows it and the row stays leased until the reaper frees it, which repeats forever. An explicit default makes the failure self-describing.

♻️ Proposed change
       "attr" (attr-model/hard-delete-multi! conn app_id #{attr_id})
-      "app" (app-model/delete-immediately-by-id! conn {:id app_id}))
+      "app" (app-model/delete-immediately-by-id! conn {:id app_id})
+      (throw (ex-info "custodian: unknown row type" {:id id :type type :app-id app_id})))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/instant/custodian.clj` around lines 253 - 259, Add an explicit
default branch to the case expression handling type in tick!, throwing a
descriptive error that includes the unrecognized type. Preserve the existing
branches for "triples", "transactions", "attr", and "app".
server/test/instant/custodian_test.clj (2)

141-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The assertions do not check the claim in the testing label.

The label states "both scoped to the attr", but no assertion inspects attr_id. The custodian-rows helper at Lines 27-31 does not even select attr_id. Add attr_id to the helper and assert it on both rows.

💚 Proposed change
-              ["select id, type, depends_on, worker_id
+              ["select id, type, attr_id, depends_on, worker_id
                   from custodian where app_id = ?::uuid order by created_at" app-id]))
           (testing "chain is triples <- attr, both scoped to the attr"
             (is (nil? (:depends_on triples)))
-            (is (= (:id triples) (:depends_on attr-row)))))))))
+            (is (= (:id triples) (:depends_on attr-row)))
+            (is (= attr-id (:attr_id triples)))
+            (is (= attr-id (:attr_id attr-row)))))))))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/test/instant/custodian_test.clj` around lines 141 - 143, Update the
custodian-rows helper to select attr_id, then extend the “chain is triples <-
attr, both scoped to the attr” test to assert attr_id on both triples and
attr-row. Preserve the existing depends_on and id assertions.

16-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restart the worker only if it was running.

The fixture always calls custodian/start in the finally. If the worker was not running when this namespace started, the fixture leaves a live worker behind. That worker then claims and drains custodian rows created by other test namespaces, which reintroduces the exact race the fixture avoids. Capture the prior state and restore it.

♻️ Proposed change
 (use-fixtures :once
   (fn [f]
-    (custodian/stop)
-    (try
-      (f)
-      (finally
-        (custodian/start)))))
+    (let [was-running? (some? `@custodian/worker`)]
+      (custodian/stop)
+      (try
+        (f)
+        (finally
+          (when was-running?
+            (custodian/start)))))))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/test/instant/custodian_test.clj` around lines 16 - 22, Update the
use-fixtures setup to capture whether the custodian was running before calling
custodian/stop, then in the finally block call custodian/start only when that
prior state was running; otherwise leave it stopped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/src/instant/core.clj`:
- Around line 363-365: Update the shutdown hook in core/-main to call
hard-deletion-sweeper/stop before starting the parallel shutdown tasks, ensuring
the producer is closed before custodian/stop runs. Use the existing
hard-deletion-sweeper lifecycle cleanup function and preserve the current
shutdown tracing behavior.

In `@server/src/instant/custodian.clj`:
- Line 238: Remove the leftover `(tool/def-locals)` debug call from the
custodian namespace initialization, and remove the now-unused `tool` alias from
the `ns` declaration. Ensure instant.custodian loads without referencing the
nonexistent namespace.
- Around line 312-314: Update the reaper schedule in the custodian startup flow
around reap-tick and chime-core/periodic-seq so its interval is derived from the
configured lease timeout instead of a fixed one-hour Duration. Reuse the
existing lease-timeout configuration symbol and preserve the current periodic
reaping behavior.
- Around line 178-184: Update sample-lag! to call max-replication-lag-bytes with
the write pool from aurora/conn-pool :write instead of the read pool, while
preserving the existing threshold, reset!, and exception-recording behavior.

In `@server/src/instant/hard_deletion_sweeper.clj`:
- Around line 77-80: Update handle-sweep around the attrs-to-delete and
apps-to-delete doseq loops to catch enqueue exceptions per entity, record each
failure, and continue processing subsequent attributes and applications.
Preserve custodian/enqueue-attr-deletion! and custodian/enqueue-app-deletion!
behavior, including duplicate suppression and propagation of non-duplicate
database errors within the enqueue functions.

---

Nitpick comments:
In `@server/src/instant/custodian.clj`:
- Around line 217-219: Update the deleted CTE definition to return a constant
value instead of all columns, replacing :returning :* while preserving the
existing delete condition and CTE structure.
- Around line 11-16: Update the custodian docstring to remove all references to
rows becoming runnable through a done_at state. Describe that completing a step
deletes its row, the on-delete-set-null foreign key clears the dependent row’s
depends_on, and the dependent then becomes runnable; retain the existing lease,
batching, transaction, and cascade behavior.
- Around line 332-342: Replace the side-effecting swap! callbacks in start and
stop with locking on the worker atom, performing start-worker or stop-worker
exactly once while holding the lock and updating the atom explicitly. Preserve
start’s existing-worker reuse and stop’s nil reset behavior.
- Around line 253-259: Add an explicit default branch to the case expression
handling type in tick!, throwing a descriptive error that includes the
unrecognized type. Preserve the existing branches for "triples", "transactions",
"attr", and "app".

In `@server/test/instant/custodian_test.clj`:
- Around line 141-143: Update the custodian-rows helper to select attr_id, then
extend the “chain is triples <- attr, both scoped to the attr” test to assert
attr_id on both triples and attr-row. Preserve the existing depends_on and id
assertions.
- Around line 16-22: Update the use-fixtures setup to capture whether the
custodian was running before calling custodian/stop, then in the finally block
call custodian/start only when that prior state was running; otherwise leave it
stopped.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f10a412d-d77e-4a66-b58d-0898d8ac8daf

📥 Commits

Reviewing files that changed from the base of the PR and between ee33d01 and b3b17f5.

📒 Files selected for processing (7)
  • server/resources/migrations/124_add_custodian.down.sql
  • server/resources/migrations/124_add_custodian.up.sql
  • server/src/instant/core.clj
  • server/src/instant/custodian.clj
  • server/src/instant/flags.clj
  • server/src/instant/hard_deletion_sweeper.clj
  • server/test/instant/custodian_test.clj

Comment thread server/src/instant/core.clj
Comment thread server/src/instant/custodian.clj
Comment thread server/src/instant/custodian.clj Outdated
Comment thread server/src/instant/custodian.clj
Comment thread server/src/instant/hard_deletion_sweeper.clj

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/src/instant/custodian.clj (2)

353-363: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep worker lifecycle side effects outside swap! update functions.

Clojure may retry swap! update functions, so start-worker can create an extra worker/scheduler that is not stored in the atom, and stop-worker can close the same worker resources more than once. Guard start and stop so only one lookup/update happens after no worker is present/active, and create or close resources only once.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/instant/custodian.clj` around lines 353 - 363, The start and stop
lifecycle side effects must not run inside the retryable swap! functions. Update
start and stop to perform an atomic worker-state check/update first, then call
start-worker only when transitioning from no worker and stop-worker only once
when transitioning from an active worker, ensuring created or closed resources
are not duplicated during swap! retries.

40-45: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add lower-bound validation for custodian runtime flags.

:custodian-batch-size is passed directly as the delete query :limit; zero or negative values select no rows, make drain! return ::completed, and cause process-row! to finish without deleting work. Reject non-positive batch and lease values, and reject negative idle sleep.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/instant/custodian.clj` around lines 40 - 45, Add lower-bound
validation to the flag accessors batch-size, idle-sleep-ms, and
lease-timeout-seconds: require batch size and lease timeout to be positive, and
idle sleep to be non-negative. Ensure invalid runtime flag values are rejected
before they reach drain!, process-row!, or the delete query.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@server/src/instant/custodian.clj`:
- Around line 353-363: The start and stop lifecycle side effects must not run
inside the retryable swap! functions. Update start and stop to perform an atomic
worker-state check/update first, then call start-worker only when transitioning
from no worker and stop-worker only once when transitioning from an active
worker, ensuring created or closed resources are not duplicated during swap!
retries.
- Around line 40-45: Add lower-bound validation to the flag accessors
batch-size, idle-sleep-ms, and lease-timeout-seconds: require batch size and
lease timeout to be positive, and idle sleep to be non-negative. Ensure invalid
runtime flag values are rejected before they reach drain!, process-row!, or the
delete query.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: eed0800f-cd1a-4431-9929-5af2410e6b74

📥 Commits

Reviewing files that changed from the base of the PR and between b3b17f5 and b36e059.

📒 Files selected for processing (1)
  • server/src/instant/custodian.clj

Base automatically changed from restores-from-admin to main August 7, 2026 17:01
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

View Vercel preview at instant-www-js-custodian-jsv.vercel.app.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/test/instant/custodian_test.clj (2)

322-332: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test the replication-lag threshold boundary.

The values 200 MiB and 0 do not verify the configured 100 MB boundary. A much lower threshold would still pass both cases. Add assertions for the configured threshold and one byte above it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/test/instant/custodian_test.clj` around lines 322 - 332, Update
sample-lag-sets-backing-off-against-the-threshold to test custodian’s configured
100 MB boundary directly: assert no backoff at exactly the threshold and backoff
at one byte above it, using max-replication-lag-bytes values that distinguish
these adjacent cases.

304-317: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the ownership-loss failure specifically.

Line 316 accepts any Exception. A database failure or an unrelated processing error would also pass this test. Assert the expected ownership-specific exception type and error data or message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/test/instant/custodian_test.clj` around lines 304 - 317, Update the
assertion around the private custodian/process-row! call in
drain-throws-if-we-no-longer-own-the-row to require the specific ownership-loss
exception type and verify its expected error data or message, rather than
accepting any Exception. Keep the existing setup that changes worker_id to
someone-else and preserve the test’s focus on ownership loss.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@server/test/instant/custodian_test.clj`:
- Around line 322-332: Update sample-lag-sets-backing-off-against-the-threshold
to test custodian’s configured 100 MB boundary directly: assert no backoff at
exactly the threshold and backoff at one byte above it, using
max-replication-lag-bytes values that distinguish these adjacent cases.
- Around line 304-317: Update the assertion around the private
custodian/process-row! call in drain-throws-if-we-no-longer-own-the-row to
require the specific ownership-loss exception type and verify its expected error
data or message, rather than accepting any Exception. Keep the existing setup
that changes worker_id to someone-else and preserve the test’s focus on
ownership loss.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3311cb02-2dff-4ba1-8249-dac751dfaa52

📥 Commits

Reviewing files that changed from the base of the PR and between 66b5f56 and e550ab6.

📒 Files selected for processing (2)
  • server/src/instant/custodian.clj
  • server/test/instant/custodian_test.clj
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/src/instant/custodian.clj

@dwwoelfel dwwoelfel changed the title [WIP] add a sweeper to delete apps in smaller chunks Add a sweeper to delete apps in smaller chunks Aug 8, 2026
@dwwoelfel dwwoelfel changed the title Add a sweeper to delete apps in smaller chunks Custodian: a sweeper to delete apps in smaller chunks Aug 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/src/instant/reactive/invalidator.clj (1)

123-130: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Read transaction DELETE data from :identity.

When the custodian deletes rows from transactions, the old row is available in :identity, not :columns. The current extraction functions read only :columns, so app-id can be nil and the when guard drops the WAL record before invalidation. The transaction ID and creation timestamp have the same problem.

Select :identity for :delete changes and use that source consistently for all transaction metadata.

Proposed fix
+(defn transaction-columns [{:keys [action columns identity]}]
+  (if (= :delete action)
+    identity
+    columns))
+
 (defn extract-app-id
-  [{:keys [columns] :as _change}]
-  (app-id-from-columns columns))
+  [change]
+  (app-id-from-columns (transaction-columns change)))
 
 (defn extract-tx-id [{:keys [columns] :as _change}]
-  (topics/get-column columns "id"))
+  (topics/get-column (transaction-columns _change) "id"))
 
 (defn extract-tx-created-at [{:keys [columns] :as _change}]
-  (when-let [^String created-at (topics/get-column columns "created_at")]
+  (when-let [^String created-at (topics/get-column (transaction-columns _change) "created_at")]
     (.toInstant (Timestamp/valueOf created-at))))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/instant/reactive/invalidator.clj` around lines 123 - 130, Update
the transaction metadata extraction around transactions-change, app-id, tx-id,
and tx-created-at to select :identity for :delete changes and :columns
otherwise. Use this same selected source consistently when calling
extract-app-id, extract-tx-id, and extract-tx-created-at so DELETE WAL records
retain their metadata and reach invalidation.
server/src/instant/custodian.clj (1)

103-115: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the docstring to include the attrs step.

enqueue-app-deletion-q now inserts four rows: triples <- transactions <- attrs <- app. The docstring still lists three steps.

📝 Proposed fix
 (defn enqueue-app-deletion!
-  "Idempotently enqueues the chain triples <- transactions <- app for an app,
+  "Idempotently enqueues the chain triples <- transactions <- attrs <- app for an app,
    atomically so a worker never sees a partial chain."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/instant/custodian.clj` around lines 103 - 115, Update the
docstring for enqueue-app-deletion! to list the complete deletion chain as
triples <- transactions <- attrs <- app, matching the four rows inserted by
enqueue-app-deletion-q.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/src/instant/custodian.clj`:
- Around line 428-430: Add a deletion-marked guard to the terminal "app" and
"attr" branches in the custodian step dispatch before invoking
app-model/delete-immediately-by-id! or attr-model/hard-delete-multi!. Reuse the
existing batch restore-check mechanism and not-deleted-message behavior so a
restored target causes the terminal delete to fail rather than deleting by id.

---

Outside diff comments:
In `@server/src/instant/custodian.clj`:
- Around line 103-115: Update the docstring for enqueue-app-deletion! to list
the complete deletion chain as triples <- transactions <- attrs <- app, matching
the four rows inserted by enqueue-app-deletion-q.

In `@server/src/instant/reactive/invalidator.clj`:
- Around line 123-130: Update the transaction metadata extraction around
transactions-change, app-id, tx-id, and tx-created-at to select :identity for
:delete changes and :columns otherwise. Use this same selected source
consistently when calling extract-app-id, extract-tx-id, and
extract-tx-created-at so DELETE WAL records retain their metadata and reach
invalidation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 263a6d00-14cd-42ba-8aba-79dc70d3b004

📥 Commits

Reviewing files that changed from the base of the PR and between 66b5f56 and e36835e.

📒 Files selected for processing (6)
  • server/resources/migrations/124_add_custodian.down.sql
  • server/resources/migrations/124_add_custodian.up.sql
  • server/src/instant/custodian.clj
  • server/src/instant/reactive/aggregator.clj
  • server/src/instant/reactive/invalidator.clj
  • server/test/instant/custodian_test.clj
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/test/instant/custodian_test.clj

Comment thread server/src/instant/custodian.clj Outdated
slot-name))]
(with-open [connection ^PgConnection connection]
(doseq [sketches (partition-all 1000 (initial-sketch-seq connection copy-sql))]
(doseq [sketches (partition-all 100 (initial-sketch-seq connection copy-sql))]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just to reduce memory usage when creating the aggregator from scratch.

(seq triples)
(seq attrs))
(seq attrs)
(seq transactions))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the invalidator slot to make progress when we delete a bunch of transactions that have no triples.

@dwwoelfel
dwwoelfel marked this pull request as ready for review August 8, 2026 00:20
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.

1 participant