Skip to content

feat(change-stream): SQLite-backed garbage collector - #653

Open
aldy505 wants to merge 11 commits into
mainfrom
aldy505/feat/change-stream/sqlite-garbage-collector
Open

aldy505 wants to merge 11 commits into
mainfrom
aldy505/feat/change-stream/sqlite-garbage-collector

Conversation

@aldy505

@aldy505 aldy505 commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Introduces SqliteGarbageCollector and SqliteGarbageCollectorStream which implements ChangeStream.
This is required for filesystem & S3-compatible API backends.
Note that it's not wired to the backends yet.

Supersedes #582
Refs FS-482

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread migrations/sqlite/0001_garbage_collector.sql Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
#[cfg(feature = "storage-cogs")]
mod cost_tracker;
mod factory;
mod garbage_collector_sqlite;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Collector is never constructed

Medium Severity

SqliteGarbageCollectorStream lives in a private module and is never re-exported or built by ChangeStreamFactory. No backend can select this collector, so the new feature is unreachable.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7dbb16c. Configure here.

Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
It's so hard to develop this on Windows. I need to grab my Linux laptop.
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
@codecov

codecov Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.26389% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.42%. Comparing base (81dbdc8) to head (f95fc6a).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
...vice/src/change_stream/garbage_collector_sqlite.rs 98.26% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #653      +/-   ##
==========================================
+ Coverage   91.33%   91.42%   +0.09%     
==========================================
  Files         117      118       +1     
  Lines       24171    24497     +326     
==========================================
+ Hits        22077    22397     +320     
- Misses       2094     2100       +6     
Components Coverage Δ
Rust Backend 94.88% <98.26%> (+0.05%) ⬆️
Rust Client 81.70% <ø> (ø)
Python Client 93.81% <ø> (ø)

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
@linear-code

linear-code Bot commented Sep 29, 2026

Copy link
Copy Markdown

FS-482

@matt-codecov matt-codecov 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.

a couple questions:

  • do we have performance goals for self-hosted deployments? i have been burned by poor single-insert sqlite performance before. if we need high throughput, we may want to introduce batched queries, but it introduces some risk of inventory corruption if we crash without flushing batches
  • do we have scaling requirements for self-hosted deployments? i've seen a few different "distributed sqlite" projects (https://rqlite.io/ / https://docs.fly.io/litefs/how-it-works/) but generally consider sqlite to be node-local

Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
@aldy505

aldy505 commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author
  • do we have performance goals for self-hosted deployments? i have been burned by poor single-insert sqlite performance before. if we need high throughput, we may want to introduce batched queries, but it introduces some risk of inventory corruption if we crash without flushing batches

If they're using k8s (the unofficial helm chart), they want to use the postgres implementation of this garbage collector. In which I will implement after SQLite is correctly implemented. This is how Taskbroker is doing as well, they provide both options, with SQLite as default.

I've contributed to rqlite before, but no thanks, let's not consider that approach.

@lcian lcian left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The biggest point to resolve will be #653 (comment) but in the meantime we can get the rest of the things in order before we find a solution for that.

Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread migrations/sqlite/0001_garbage_collector.sql
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
Comment thread objectstore-service/src/change_stream/garbage_collector_sqlite.rs Outdated
@aldy505
aldy505 requested review from lcian and matt-codecov October 1, 2026 01:46

@matt-codecov matt-codecov 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.

#677 changed the ChangeStream trait to help write GC correctly. unfortunately you will need to rebase and tweak your PR to implement:

  • begin_write(): create the GC record before committing in the storage backend
  • begin_update(): lengthen the expiration in GC before committing in the storage backend
  • commit_delete(): delete the GC record after deleting the stored object

if begin_write() or begin_update() fail, it's not safe to allow the backend commit to happen. otherwise we can permanently leak objects or cause GC to delete them prematurely. so those functions have to .await rather than spawn and they have to surface failure to the caller

if commit_delete() fails it's kind of harmless. GC will just have a ghost record floating around until its expiry. if the ghost record has manual expiration then it'll float around forever, but that's... probably a small edge case.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f95fc6a. Configure here.

.bind(id.as_storage_path().to_string())
.bind(expires_at.map(|t| i64::try_from(t.as_secs()).ok()))
.execute(&pool)
.await;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Write upsert shortens expiry too early

High Severity

begin_write upserts expires_at unconditionally, so an overwrite can move an existing object's GC deadline earlier before storage succeeds. ChangeStream treats begin_* as an upper bound and does not promise a matching commit_*. A failed or in-flight write can therefore make the collector delete a still-valid object.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f95fc6a. Configure here.

@aldy505
aldy505 requested a review from matt-codecov October 17, 2026 07:36
@aldy505
aldy505 added this pull request to stack #691 October 17, 2026 16:11
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.

3 participants