CommitFest 7265 Review: Logical Decoding Skip Unnecessary Snapshot Distribution
code-review · generated from canonical Markdown
code-review
postgresql-master
commitfest-7265-review2026-09-09T12:10:20.845636+00:00
Front matter
schema: review-report-v2 target: commitfest-7265 source: postgresql-master slug: commitfest-7265-review head: 412ef97d925c54d6135afb4c22cceccb6421d8a2 base: 412ef97d925c54d6135afb4c22cceccb6421d8a2 diff_sha: 7e697bfcf8dea06db874516919a8797af567d5eb1abf8d0e5834c39ec363c6fe scope_identity: diff7e697bfc verdict: waiting-on-author blocker_counts: verified: 1 unverified: 0 date: 2026-09-09
CommitFest 7265 Review — Logical Decoding: Skip Unnecessary Snapshot Distribution
Scope
| Field | Value |
|---|---|
| Target | CommitFest 7265 |
| Patch | "logical decoding: skip unnecessary snapshot distribution" (Boyu Yang, 2026-08-31) |
| Base/HEAD | 412ef97d925c54d6135afb4c22cceccb6421d8a2 |
| Diff SHA | 7e697bfcf8dea06db874516919a8797af567d5eb1abf8d0e5834c39ec363c6fe |
| Scope | Uncommitted staged diff |
| Changed files | src/backend/replication/logical/decode.c, src/backend/replication/logical/snapbuild.c, src/include/replication/snapbuild.h |
| Changed functions | DecodeCommit, InvalidationsTouchSharedCatalog (new), SnapBuildCommitTxn |
| Thread | [pgsql-hackers thread 655705](https://www.postgresql.org/message-id/flat/312a1b4c-f341-40dc-b822-08e5b0cc1848.yangboyu.yby@alibaba-inc.com) |
| CommitFest | https://commitfest.postgresql.org/patch/7265/ |
Patch Summary
The patch optimizes logical decoding snapshot building. When a committed transaction has catalog changes in a different database than the slot's database, and those changes do not touch shared catalogs, the snapshot is not rebuilt and invalidations are not distributed. A new function InvalidationsTouchSharedCatalog() checks whether invalidation messages in the commit record concern shared catalogs by examining SHAREDINVALSMGR_ID messages (checking rlocator.dbOid == InvalidOid) and all other message types (checking cc.dbId == InvalidOid, since the dbId field is at the same union offset for all non-SMGR types).
Motivation: A single logical slot on db1 is unaffected by ANALYZE/catalog traffic in db2 that pins xmin via a long-running transaction, eliminating O(k²) reorder-buffer spill growth. The reproducer shows 19MB→0 spill reduction.
Verdict
waiting-on-author
| Blocker Status | Count |
|---|---|
| Verified | 1 |
| Unverified | 0 |
Rationale: One BLOCKER finding (F1) survives verification. The parsed->msgs vs ReorderBufferGetInvalidations divergence is real but benign in the demonstrated scenario — the patch's design is correct. However, the author has not responded to Shlok Kyal's on-list review raising this concern. The repair (documentation comment explaining why parsed->msgs is the correct basis) is validated. A MAJOR test gap (F2) also requires a TAP test before this can advance.
Findings
— BLOCKER (correctness) — src/backend/replication/logical/decode.c:712
Status: verified (benign)
Mechanism: distribute is computed from InvalidationsTouchSharedCatalog(parsed->nmsgs, parsed->msgs) — the commit record's transactional invalidation messages. But the actual distribution in SnapBuildDistributeSnapshotAndInval uses ReorderBufferGetInvalidations(builder->reorder, xid, &msgs), which returns invalidations accumulated in the reorder buffer from every XLOG_XACT_INVALIDATIONS record. These two sets can diverge when a subtransaction that touched a shared catalog is rolled back via ROLLBACK TO SAVEPOINT: AtEOSubXact_Inval(isCommit=false) discards those messages without propagating them to the parent, so they are absent from parsed->msgs but present in the grow-only reorder buffer.
Impact: The missed invalidations are from a rolled-back subtransaction, so no shared catalog row actually changed — not distributing them is benign. The patch correctly uses parsed->msgs (the commit record) as the decision basis: it contains exactly the invalidations of changes that actually committed. However, the author must document this design rationale and respond to the on-list review.
Thread status: Raised by Shlok Kyal (2026-09-03), author has not replied as of 2026-09-05.
Repair: Comment-only patch (repair_only.patch, SHA-256 032f11395a3dc4cdd046eab9888c90cf16692ad58506a8d51b7f5598bc32e855) documenting why parsed->msgs is the correct basis. Validation: passed (build + all test suites).
— MAJOR (tests) — no test added
Status: verified
The patch adds no test for a behavioral change to snapshot building and invalidation distribution. A TAP test (src/test/subscription/t/039_snapshot_inval_xdb.pl, 4 scenarios) is proposed and validated. Repair patch: test_add.patch (SHA-256 72e46a92b58c96630e7eb936bc8c2e491591f819c609d2cd52338a8b6b48a8d9).
— MINOR (maintainability) — src/backend/replication/logical/decode.c:1374
Status: verified
The SHAREDINVALSMGR_ID branch in InvalidationsTouchSharedCatalog is dead code for commit records (SMGR invalidations are non-transactional, never in commit WAL). Comment does not explain this. Repair: explanatory comment in repair_only.patch.
— MINOR (maintainability) — src/backend/replication/logical/snapbuild.c:1088
Status: verified
The || builder->snapshot == NULL guard is load-bearing (prevents NULL deref) but uncommented. Repair: explanatory comment in repair_only.patch.
— MINOR (maintainability) — src/backend/replication/logical/decode.c:1380
Status: verified
The comment "All the other message types carry the database ID (0 for a shared relation)" is imprecise for SHAREDINVALRELSYNC_ID which always carries MyDatabaseId. Repair: precise comment in repair_only.patch.
Verification Summary
| Finding | Severity | Verification Status | Method |
|---|---|---|---|
| F1 | BLOCKER | verified (benign) | static-analysis |
| F2 | MAJOR | verified | inspection |
| F3 | MINOR | verified | static-analysis |
| F4 | MINOR | verified | static-analysis |
| F5 | MINOR | verified | static-analysis |
All five findings verified. F1's divergence is real but the impact is benign — the patch's design is correct. The repair is documentation. F2's test gap is addressed by a validated TAP test. F3–F5 are comment improvements.
Repair Validation Summary
| Finding | Repair | Validation |
|---|---|---|
| F1 | Comment documenting parsed->msgs rationale | passed — build + all test suites |
| F2 | TAP test t/039_snapshot_inval_xdb.pl (4 scenarios) | passed — 4/4 tests pass with and without patch |
| F3 | Comment explaining SMGR dead branch | passed — comment-only, builds clean |
| F4 | Comment explaining NULL guard | passed — comment-only, builds clean |
| F5 | Comment explaining relsync dbId | passed — comment-only, builds clean |
Repair patches:
repair_only.patch(F1/F3/F4/F5): comment-only changes todecode.candsnapbuild.ctest_add.patch(F2): new TAP test + Makefile change
Both patches apply cleanly on top of the frozen patch. Full build and all test suites pass (contrib/test_decoding, src/test/recovery, src/test/subscription).
Security Review
No security vulnerability found. The shared-catalog detection invariant is complete and correct for all current SharedInvalidationMessage types. The union overlay is type-safe. The builder->snapshot == NULL guard prevents NULL-deref/UAF. No new trust-boundary, authorization, TOCTOU, or search-path surface is introduced.
pgsql-hackers Thread Ledger
Thread 655705: "logical decoding: skip unnecessary snapshot distribution."
| Date | Sender | Kind | Summary |
|---|---|---|---|
| 2026-08-31 | Boyu Yang | patch | Patch submission with reproducer (19MB→0 spill) |
| 2026-09-03 | Shlok Kyal | review | Raised parsed->msgs vs ReorderBufferGetInvalidations divergence (F1) |
| 2026-09-05 | Shihao Zhong | review | Confirmed reproducer, noted related "Lazy snapshot distribution" thread |
| 2026-09-05 | Shihao Zhong | other | Created CommitFest entry 7265 |
Finding links:
- F1:
already-raised— Shlok Kyal raised the divergence on-list; author has not replied - F2–F5:
new— harness findings not previously raised on-list
Changed-Code Coverage
Test set executed
| Suite | Tests | Result |
|---|---|---|
contrib/test_decoding | 14 regression + 1 TAP | passed |
src/test/recovery | 55 TAP / 654 subtests | passed |
src/test/subscription | 39 TAP / 601 subtests | passed |
Fresh disposable coverage build with --coverage at frozen HEAD. No stale/mismatched counters.
Metrics
| Metric | Value |
|---|---|
| Changed executable lines | 21 |
| Covered changed lines | 19 |
| Uncovered changed lines | 2 |
| Changed branches | 12 |
| Covered changed branches | 10 |
| Uncovered changed branches | 2 |
| Line coverage | 90.5% |
| Branch coverage | 83.3% |
Uncovered regions
decode.c:1377-1378— SMGR invalidation branch (2 lines, 1 branch)
- Classification:
infeasible-with-reason—SHAREDINVALSMGR_IDmessages are non-transactional and never appear in commit WAL records. Dead defensive code. Waived.
snapbuild.c:1088branch 2 — NULL-guard whendistribute == false(1 branch)
- Classification:
requires-special-harness-or-injection-point— requiresdistribute==falseANDbuilder->snapshot==NULLsimultaneously, which only occurs during slot initialization before any same-DB catalog commit. Test packet provided (TAP with cross-DB race or injection point).
Whole-function diagnostics
| Function | File | Calls | Return % | Blocks executed % |
|---|---|---|---|---|
DecodeCommit | decode.c | 3552 | 100% | 100% |
InvalidationsTouchSharedCatalog | decode.c | 29 | 100% | 80% |
SnapBuildCommitTxn | snapbuild.c | 3552 | 100% | 72% |
Non-instrumented changes
src/include/replication/snapbuild.h:SnapBuildCommitTxnsignature change (addedbool distribute). Header not instrumented by gcov; consumed by instrumenteddecode.candsnapbuild.c.
Aggregated Test Plan
- Existing:
contrib/test_decoding(14 regression + 1 TAP),src/test/recovery(55 TAP),src/test/subscription(39 TAP) - Proposed (F2):
src/test/subscription/t/039_snapshot_inval_xdb.pl— 4 scenarios covering cross-DB non-shared, cross-DB shared, rolled-back savepoint, and committed-with-rolled-back-sibling cases - Proposed (coverage):
src/test/recovery/t/107_cross_db_snapbuild_null.pl— TAP test for NULL-guard branch coverage
Provenance
| Seat | Model | Task |
|---|---|---|
| pg-patch-reviewer | (routed) | Concurrent review |
| pg-security-reviewer | (routed) | Security review |
| pg-test-author | (routed) | TAP test proposal |
| pg-finding-verifier | (routed) | Finding + repair verification |
| pg-change-coverage-reviewer | (routed) | Changed-code coverage |
Residual Risk
- The
parsed->msgsvsReorderBufferGetInvalidationsdivergence (F1) is benign in the current code but could become a real bug if invalidation/rollback handling changes in the future. The documentation repair mitigates this risk. - The NULL-guard branch (
snapbuild.c:1088branch 2) is uncovered by the current test set; it requires a cross-DB race during slot initialization or an injection point to exercise deterministically. - The SMGR branch in
InvalidationsTouchSharedCatalogis dead defensive code; future changes to sinval.h that add new message types with different layouts could break the union overlay assumption.