Skip to content

feat: add spock.resolutions_retention_days GUC and cleanup_resolutions()#412

Open
rasifr wants to merge 3 commits intomainfrom
feature/SPOC-266/resolutions-table-cleanup
Open

feat: add spock.resolutions_retention_days GUC and cleanup_resolutions()#412
rasifr wants to merge 3 commits intomainfrom
feature/SPOC-266/resolutions-table-cleanup

Conversation

@rasifr
Copy link
Copy Markdown
Member

@rasifr rasifr commented Apr 9, 2026

Summary

Adds spock.resolutions_retention_days GUC (int, default 100, min 0) to control how long rows are kept in spock.resolutions. The apply worker deletes rows older than the configured window at most once per day. Setting to 0 disables automatic cleanup entirely.

Also adds spock.cleanup_resolutions(), a superuser-only SQL function that returns the number of rows deleted, for manual invocation.

A log_time index is added to both the fresh-install and upgrade scripts to keep the daily DELETE efficient on large tables.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78b71f16-6265-44dd-a852-73b852507123

📥 Commits

Reviewing files that changed from the base of the PR and between 8247abd and b9adb1d.

⛔ Files ignored due to path filters (1)
  • tests/regress/expected/resolutions_retention.out is excluded by !**/*.out
📒 Files selected for processing (10)
  • Makefile
  • docs/configuring.md
  • docs/spock_functions/functions/spock_cleanup_resolutions.md
  • include/spock_conflict.h
  • sql/spock--5.0.6--6.0.0-devel.sql
  • sql/spock--6.0.0-devel.sql
  • src/spock.c
  • src/spock_apply.c
  • src/spock_conflict.c
  • tests/regress/sql/resolutions_retention.sql
✅ Files skipped from review due to trivial changes (6)
  • Makefile
  • docs/configuring.md
  • src/spock_apply.c
  • docs/spock_functions/functions/spock_cleanup_resolutions.md
  • sql/spock--6.0.0-devel.sql
  • tests/regress/sql/resolutions_retention.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • sql/spock--5.0.6--6.0.0-devel.sql

📝 Walkthrough

Walkthrough

Add retention-based automatic deletion for spock.resolutions via new GUC spock.resolutions_retention_days (default 100) and a C-backed spock.cleanup_resolutions() function; includes apply-worker scheduling, index, documentation, and regression tests.

Changes

Cohort / File(s) Summary
Configuration & GUC Definition
src/spock.c, include/spock_conflict.h
Added spock.resolutions_retention_days GUC (default 100, PGC_SUSET) and declared the extern variable and cleanup API prototype.
Core Cleanup Implementation
src/spock_conflict.c
Added spock_cleanup_resolutions_core() performing SPI DELETE by log_time, exported spock_cleanup_resolutions() (background-safe, transaction/snapshot managed, warnings on errors), and SQL wrapper spock_cleanup_resolutions_sql() (superuser check). Introduced global int spock_resolutions_retention_days.
Apply Worker Integration
src/spock_apply.c
Added daily cleanup scheduling (RESOLUTIONS_CLEANUP_INTERVAL_MS) in apply_work() invoking spock_cleanup_resolutions() when not in a transaction and retention > 0.
SQL Schema & Migration
sql/spock--5.0.6--6.0.0-devel.sql, sql/spock--6.0.0-devel.sql
Created B-tree index on spock.resolutions(log_time). Added C-backed SQL function spock.cleanup_resolutions() RETURNS bigint and revoked default PUBLIC privileges.
Documentation
docs/configuring.md, docs/spock_functions/functions/spock_cleanup_resolutions.md
Documented new spock.resolutions_retention_days GUC (default 100, 0 disables automatic cleanup) and added spock.cleanup_resolutions() docs (synopsis, return bigint, superuser requirement, behavior relative to GUCs and example usage).
Regression Test Suite
tests/regress/sql/resolutions_retention.sql
Added regression test covering retention behavior, manual backdating, GUC interactions (spock.resolutions_retention_days, spock.save_resolutions), and spock.cleanup_resolutions() outcomes across provider/subscriber scenarios.
Test Infrastructure
Makefile
Updated REGRESS list to include the new resolutions_retention regression test (reordered).

Poem

🐰 I nibble logs both old and wan,
I hop and tidy by the dawn.
Days set to keep, days set to sweep,
Quiet burrows, tidy heap.
Clean and bouncy — hop along, anon! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding a GUC and cleanup function for resolutions retention management.
Description check ✅ Passed The description is directly related to the changeset, providing clear context about the new GUC, cleanup function, index, and behavioral details.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/SPOC-266/resolutions-table-cleanup

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 and usage tips.

@codacy-production
Copy link
Copy Markdown

codacy-production bot commented Apr 9, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 duplication

Metric Results
Duplication 0

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/regress/sql/resolutions_retention.sql (1)

94-106: Consider resetting the provider-side retention GUC for completeness.

The cleanup section resets spock.resolutions_retention_days on the subscriber but not on the provider. While the provider doesn't modify this GUC in the test, explicitly resetting both nodes would ensure a clean slate for subsequent tests.

Proposed addition
 \c :subscriber_dsn
 RESET spock.resolutions_retention_days;
 ALTER SYSTEM SET spock.save_resolutions = off;
 SELECT pg_reload_conf();
+
+\c :provider_dsn
+RESET spock.resolutions_retention_days;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/regress/sql/resolutions_retention.sql` around lines 94 - 106, Add
resetting of the provider-side GUC spock.resolutions_retention_days in the
cleanup block so both nodes return to defaults: after switching to the provider
connection (currently via "\c :provider_dsn") ensure you RESET
spock.resolutions_retention_days and, if you disable resolution saving on the
provider, also reapply ALTER SYSTEM SET spock.save_resolutions = off followed by
SELECT pg_reload_conf() to mirror the subscriber cleanup; update the cleanup
sequence around spock.repset_remove_table / spock.replicate_ddl and the
subsequent ALTER SYSTEM/pg_reload_conf calls to include these provider-side
resets.
src/spock_conflict.c (1)

936-1001: Well-designed transaction management for background execution.

The error handling correctly:

  • Saves the caller's memory context before transaction start
  • Copies error data to the surviving context before abort
  • Uses AbortCurrentTransaction() which handles SPI/snapshot cleanup automatically
  • Downgrades to WARNING to avoid disrupting replication

One minor defensive consideration: edata->message could theoretically be NULL in unusual error scenarios.

Defensive NULL check
 		ereport(WARNING,
 				(errcode(edata->sqlerrcode),
-				 errmsg("%s", edata->message)));
+				 errmsg("%s", edata->message ? edata->message : "cleanup_resolutions failed")));
 		FreeErrorData(edata);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/spock_conflict.c` around lines 936 - 1001, The code in
spock_cleanup_resolutions uses edata->message in the ereport(WARNING ...) call
but edata->message can be NULL; update the PG_CATCH block to defensively check
edata->message (the ErrorData pointer returned by CopyErrorData()) and pass a
safe fallback string (e.g. "no error message" or similar) to ereport so you
never call errmsg with a NULL pointer; reference the PG_CATCH block, the edata
variable, CopyErrorData(), and the ereport(WARNING...) invocation when making
this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/spock_apply.c`:
- Around line 3057-3075: The spock_cleanup_resolutions() call runs unprotected
inside the apply loop and can throw into the replication-exception path; wrap
the call in a local exception block using PG_TRY / PG_CATCH around
spock_cleanup_resolutions() (located near last_cleanup_timestamp and
GetCurrentTimestamp checks) so cleanup failures are handled locally: on error,
log the failure (with error details) and clear the error with FlushErrorState()
/ ReThrowError? (do not re-raise), then continue the main loop and still update
last_cleanup_timestamp only on success (or decide to back off by leaving it
unchanged). Ensure no transaction state is assumed (check IsTransactionState
usage remains) and avoid propagating the exception to replication exception
handling.

---

Nitpick comments:
In `@src/spock_conflict.c`:
- Around line 936-1001: The code in spock_cleanup_resolutions uses
edata->message in the ereport(WARNING ...) call but edata->message can be NULL;
update the PG_CATCH block to defensively check edata->message (the ErrorData
pointer returned by CopyErrorData()) and pass a safe fallback string (e.g. "no
error message" or similar) to ereport so you never call errmsg with a NULL
pointer; reference the PG_CATCH block, the edata variable, CopyErrorData(), and
the ereport(WARNING...) invocation when making this change.

In `@tests/regress/sql/resolutions_retention.sql`:
- Around line 94-106: Add resetting of the provider-side GUC
spock.resolutions_retention_days in the cleanup block so both nodes return to
defaults: after switching to the provider connection (currently via "\c
:provider_dsn") ensure you RESET spock.resolutions_retention_days and, if you
disable resolution saving on the provider, also reapply ALTER SYSTEM SET
spock.save_resolutions = off followed by SELECT pg_reload_conf() to mirror the
subscriber cleanup; update the cleanup sequence around spock.repset_remove_table
/ spock.replicate_ddl and the subsequent ALTER SYSTEM/pg_reload_conf calls to
include these provider-side resets.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f8490f1-c398-45d5-b8f8-42ed592f41ce

📥 Commits

Reviewing files that changed from the base of the PR and between 25feecc and e83a5ef.

⛔ Files ignored due to path filters (1)
  • tests/regress/expected/resolutions_retention.out is excluded by !**/*.out
📒 Files selected for processing (10)
  • Makefile
  • docs/configuring.md
  • docs/spock_functions/functions/spock_cleanup_resolutions.md
  • include/spock_conflict.h
  • sql/spock--5.0.6--6.0.0-devel.sql
  • sql/spock--6.0.0-devel.sql
  • src/spock.c
  • src/spock_apply.c
  • src/spock_conflict.c
  • tests/regress/sql/resolutions_retention.sql

@rasifr rasifr force-pushed the feature/SPOC-266/resolutions-table-cleanup branch from e83a5ef to 8247abd Compare April 9, 2026 11:33
rasifr and others added 3 commits April 9, 2026 17:26
Introduces spock.resolutions_retention_days (int, default 100, min 0)
to control how long rows are kept in spock.resolutions. Rows older
than the configured window are deleted automatically by the apply
worker at most once per day. Setting the GUC to 0 disables automatic
cleanup entirely.

Also adds spock.cleanup_resolutions(), a superuser-only SQL function
that returns the number of rows deleted, for manual invocation.

A log_time index is added to both the fresh-install and upgrade scripts
to keep the daily DELETE efficient on large resolutions tables.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ns()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rasifr rasifr force-pushed the feature/SPOC-266/resolutions-table-cleanup branch from 8247abd to b9adb1d Compare April 9, 2026 13:58
* own transaction and error handling.
*/
if (!IsTransactionState() &&
spock_resolutions_retention_days > 0)
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 docs mention it checks save_resolutions, too.

NULL,
&spock_resolutions_retention_days,
100, 0, INT_MAX,
PGC_SUSET,
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.

Should this use PGC_SIGHUP since it is used in the apply worker?

#define is_skipping_changes() (unlikely(!XLogRecPtrIsInvalid(skip_xact_finish_lsn)))

/* How often the apply worker runs spock_cleanup_resolutions() (milliseconds). */
#define RESOLUTIONS_CLEANUP_INTERVAL_MS (86400L * 1000L)
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.

We could leave this, but ms resolution seems like overkill when seconds would do?

@@ -0,0 +1,41 @@
## NAME

spock.cleanup_resolutions()
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.

well, since you created this, what might be nice is if takes an optional argument that overrides resolutions_retention_days. For example, maybe they set retention to 0, then periodically manually run something like SELECT spock.cleanup_resolutions(60) when they think it is getting too big.

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.

2 participants