CommitFest 7265 Review: Logical Decoding Skip Unnecessary Snapshot Distribution

code-review · generated from canonical Markdown

Type
code-review
Source
postgresql-master
Slug
commitfest-7265-review
Tags
code-reviewcommitfest-7265patch-reviewpostgresql
Updated
2026-09-09T12:10:20.845636+00:00
Verdict / Summarywaiting-on-author
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

FieldValue
TargetCommitFest 7265
Patch"logical decoding: skip unnecessary snapshot distribution" (Boyu Yang, 2026-08-31)
Base/HEAD412ef97d925c54d6135afb4c22cceccb6421d8a2
Diff SHA7e697bfcf8dea06db874516919a8797af567d5eb1abf8d0e5834c39ec363c6fe
ScopeUncommitted staged diff
Changed filessrc/backend/replication/logical/decode.c, src/backend/replication/logical/snapbuild.c, src/include/replication/snapbuild.h
Changed functionsDecodeCommit, 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)
CommitFesthttps://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 StatusCount
Verified1
Unverified0

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

F1

— 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).

F2

— 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).

F3

— 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.

F4

— 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.

F5

— 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

FindingSeverityVerification StatusMethod
F1BLOCKERverified (benign)static-analysis
F2MAJORverifiedinspection
F3MINORverifiedstatic-analysis
F4MINORverifiedstatic-analysis
F5MINORverifiedstatic-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

FindingRepairValidation
F1Comment documenting parsed->msgs rationalepassed — build + all test suites
F2TAP test t/039_snapshot_inval_xdb.pl (4 scenarios)passed — 4/4 tests pass with and without patch
F3Comment explaining SMGR dead branchpassed — comment-only, builds clean
F4Comment explaining NULL guardpassed — comment-only, builds clean
F5Comment explaining relsync dbIdpassed — comment-only, builds clean

Repair patches:

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."

DateSenderKindSummary
2026-08-31Boyu YangpatchPatch submission with reproducer (19MB→0 spill)
2026-09-03Shlok KyalreviewRaised parsed->msgs vs ReorderBufferGetInvalidations divergence (F1)
2026-09-05Shihao ZhongreviewConfirmed reproducer, noted related "Lazy snapshot distribution" thread
2026-09-05Shihao ZhongotherCreated CommitFest entry 7265

Finding links:

Changed-Code Coverage

Test set executed

SuiteTestsResult
contrib/test_decoding14 regression + 1 TAPpassed
src/test/recovery55 TAP / 654 subtestspassed
src/test/subscription39 TAP / 601 subtestspassed

Fresh disposable coverage build with --coverage at frozen HEAD. No stale/mismatched counters.

Metrics

MetricValue
Changed executable lines21
Covered changed lines19
Uncovered changed lines2
Changed branches12
Covered changed branches10
Uncovered changed branches2
Line coverage90.5%
Branch coverage83.3%

Uncovered regions

  1. decode.c:1377-1378 — SMGR invalidation branch (2 lines, 1 branch)
  1. snapbuild.c:1088 branch 2 — NULL-guard when distribute == false (1 branch)

Whole-function diagnostics

FunctionFileCallsReturn %Blocks executed %
DecodeCommitdecode.c3552100%100%
InvalidationsTouchSharedCatalogdecode.c29100%80%
SnapBuildCommitTxnsnapbuild.c3552100%72%

Non-instrumented changes

Aggregated Test Plan

  1. Existing: contrib/test_decoding (14 regression + 1 TAP), src/test/recovery (55 TAP), src/test/subscription (39 TAP)
  2. 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
  3. Proposed (coverage): src/test/recovery/t/107_cross_db_snapbuild_null.pl — TAP test for NULL-guard branch coverage

Provenance

SeatModelTask
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