Skip to content

Bound PostgreSQL KV expiry housekeeping - #1314

Merged
dahlia merged 3 commits into
fedify-dev:2.0-maintenancefrom
dahlia:perf/postgres/full-table-expiry-sweep
Oct 20, 2026
Merged

dahlia merged 3 commits into
fedify-dev:2.0-maintenancefrom
dahlia:perf/postgres/full-table-expiry-sweep

Conversation

@dahlia

@dahlia dahlia commented Oct 19, 2026

Copy link
Copy Markdown
Member

Each PostgresKvStore mutation currently waits for an expiry sweep that can scan the entire table even when nothing has expired. Space out sweeps with a per-instance cooldown, cap each batch at 1,000 deletions, and retry sooner after a full batch to drain backlogs. A table-scoped advisory lock prevents overlapping sweeps across instances without a schema migration.

Pooled writes no longer wait for cleanup, and cleanup failures are logged instead of failing the write. Caller-owned transactions still await cleanup and propagate errors so a rollback cannot look like a successful write. Refresh creation timestamps on replacement and use the statement clock throughout TTL checks so delayed deletion preserves expiry semantics.

Verified with PostgreSQL query-plan prototypes, Deno/Node.js/Bun regression tests, and mise run check.

Fixes #1308.

@dahlia dahlia self-assigned this Oct 19, 2026
@dahlia dahlia added component/kv Key–value store related driver/postgres PostgreSQL driver (@fedify/postgres) labels Oct 19, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T18:58:33.568419Z 78c65f2 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Avoid repeated scans of live KV rows on each mutation. Run bounded,
non-overlapping batches with a cooldown and faster catch-up after a
full batch. Keep pooled writes independent of housekeeping failures.

Await cleanup inside caller-owned transactions so aborts cannot be
reported as successful writes. Use the statement clock for writes and
TTL checks so retained expired rows can be replaced and zero-TTL
entries stay invisible before physical deletion.

Fixes fedify-dev#1308

Assisted-by: OpenCode:deepseek-flash
Assisted-by: Codex:gpt-6.1-sol
Assisted-by: Codex:gpt-6-astra
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-fable-5-1
@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

Files with missing lines Coverage Δ
packages/postgres/src/kv.ts 97.12% <100.00%> (+<0.01%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dahlia
dahlia force-pushed the perf/postgres/full-table-expiry-sweep branch from e7753ea to bba2b12 Compare October 19, 2026 18:25
@dahlia

dahlia commented Oct 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7753ea43d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/postgres/src/kv.ts Outdated
Comment thread packages/postgres/src/kv.ts Outdated
@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: e7bc7f44-1b10-4d84-9a96-8e77cbc4b324
📥 Commits

Reviewing files that changed from the base of the PR and between bba2b12 and 78c65f2.

📒 Files selected for processing: 4
  • CHANGES.md
  • changes.d/postgres/postgres-kv-expiry.md
  • packages/postgres/src/kv.expiry.test.ts
  • packages/postgres/src/kv.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

PostgreSQL key-value expiry cleanup now runs in bounded, scheduled sweeps with coordination across store instances. Cleanup errors differ for pooled clients and caller-owned transactions. Writes refresh TTL timestamps, and expiry checks use the SQL statement clock.

Changes: PostgreSQL KV expiry cleanup and TTL

Layer / File(s) Summary
Bounded cleanup scheduling and transaction behavior
packages/postgres/src/kv.ts, packages/postgres/src/kv.expiry.test.ts, CHANGES.md, changes.d/postgres/postgres-kv-expiry.md
Cleanup deletes up to 1,000 expired rows per sweep, uses a table-level advisory lock, and skips rows locked by other transactions. Pooled-client writes do not wait for cleanup. Caller-owned transactions await cleanup and propagate cleanup errors. Tests cover scheduling, batching, lock behavior, failures, table layouts, and drop() waiting for active cleanup.
TTL refresh and statement-clock checks
packages/postgres/src/kv.ts, packages/postgres/src/kv.expiry.test.ts, CHANGES.md, changes.d/postgres/postgres-kv-expiry.md
Inserts and overwrites set created using statement_timestamp(). Expiry checks use that clock. Tests cover replacement timestamps, expired-key replacement, and zero-TTL visibility.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: 2chanhaeng

Merge Risk: ⚪ Minimal · up to 78c65

The PR limits PostgreSQL KV expiry cleanup to bounded batches and coordinates sweeps across store instances. Ordinary pooled writes no longer wait for cleanup or fail when cleanup fails. Caller-owned transactions still surface cleanup errors. Replacing a key also refreshes its TTL. No concrete defect was identified, and the change appears ready to merge.

Security architecture risk: 🔵 Low · up to bba2b

The change is confined to the PostgreSQL key-value store and preserves read-time expiry checks while separating pooled writes from cleanup failures. Transaction rollback safeguards and bounded deletion reduce failure coupling. Physical deletion remains best effort, and deployment-specific behavior has not been established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — One sweep can affect expired entries across the configured table, not merely the triggering key or prefix. This table-wide cleanup authority predates the PR; the new implementation limits each deletion batch. Deployment-specific tenant sharing and remote reachability are not established by the supplied evidence.

Trust Boundaries and Controls

  • observed — Caller-provided keys, values, TTLs, and prefixes remain SQL parameters, while table selection uses the driver's identifier helper. Cleanup uses the same constructor-bound SQL client and table; the change does not introduce another credential source or a caller-selectable cleanup target per mutation.
  • observed — Transaction cleanup errors are rethrown rather than hidden as successful writes. Tests define an actual SQL-error rollback scenario and concurrent mutation failure propagation; another regression exercises a locked row refreshed concurrently with sweeping. These are source-level counterevidence, not independently executed test results.

Resilience and Maintainability Implications

  • inferred — Physical erasure has no guaranteed deadline: both versions initiate cleanup through mutations, and the head adds batching, cooldown, and nonfatal pooled cleanup failures. Logical invisibility remains enforced, but physical retention can continue without later successful sweeps. The inspected TTL interface does not promise a physical-erasure deadline, so this is not retained as an introduced security finding.
Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files. (2 skipped: 2… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title is concise, specific, and accurately summarizes the main change: bounded PostgreSQL KV expiry housekeeping.
Description check Passed The description is directly related to the changeset and clearly explains cooldowns, bounded cleanup, advisory locking, transaction behavior, timestamp handling, and verification.
Linked Issues check Passed Issue #1308 requires reduced sweep frequency, no overlapping sweeps, bounded deletion with later progress, separate cleanup failures from pooled writes, and preserved TTL behavior. kv.ts implements …
Out of Scope Changes check Passed The implementation and regression tests directly support issue #1308. The changelog files document the same cleanup, transaction, and TTL behavior. No unrelated implementation, schema migration, publi…
Full details: Linked Issues check

Explanation

Issue #1308 requires reduced sweep frequency, no overlapping sweeps, bounded deletion with later progress, separate cleanup failures from pooled writes, and preserved TTL behavior. kv.ts implements cooldown scheduling, 1,000-row batches, table-scoped advisory locking, and faster retry after a full batch. Pooled writes detach and log cleanup failures. Caller-owned transactions await cleanup and propagate errors. Statement timestamps and refreshed creation timestamps preserve TTL behavior. kv.expiry.test.ts covers burst scheduling, concurrency, bounded progress, failure handling, transactions, reads, CAS, replacement, stale tokens, and partitioned or inherited tables.

Full details: Out of Scope Changes check

Explanation

The implementation and regression tests directly support issue #1308. The changelog files document the same cleanup, transaction, and TTL behavior. No unrelated implementation, schema migration, public API change, or unrelated test scope is identified.

Full details: Docstring Coverage

Explanation

Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

Reserved clients can run manual transactions without exposing
savepoint(). Await their cleanup and propagate its errors so an
aborted transaction cannot look like a successful mutation.

Cover set/delete failures in manual transactions and shared cleanup
error propagation without invoking client ownership methods.

fedify-dev#1314 (comment)

Assisted-by: Codex:gpt-6.1-sol
CTIDs identify rows only within one physical relation. Match both
tableoid and CTID so partitioned and inherited tables cannot multiply
one expiry batch. Retain the CTID array for targeted TID lookups.

Resolve the shared lock from the logical table's row type rather than
the first visible child row, keeping its identity stable across
partitions, empty tables, and driver identifier transforms.

Regressions cover overlapping CTIDs, catch-up batches, and parent
locks on partitioned and inherited tables. Native PostgreSQL 16/18
plans retain TID scans with 79,000 live rows.

fedify-dev#1314 (comment)

Assisted-by: Codex:gpt-6.1-sol
@dahlia

dahlia commented Oct 19, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 78c65f2c87

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dahlia
dahlia merged commit 5a048d1 into fedify-dev:2.0-maintenance Oct 20, 2026
17 checks passed
@dahlia dahlia linked an issue Oct 20, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/kv Key–value store related driver/postgres PostgreSQL driver (@fedify/postgres)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Avoid a full-table expiry sweep on every PostgreSQL KV mutation

1 participant