Skip to content

Fix distributed snapshot race during commit - #1884

Open
hbswcsyzx wants to merge 2 commits into
apache:mainfrom
hbswcsyzx:fix/issue-1465-distributed-snapshot
Open

Fix distributed snapshot race during commit#1884
hbswcsyzx wants to merge 2 commits into
apache:mainfrom
hbswcsyzx:fix/issue-1465-distributed-snapshot

Conversation

@hbswcsyzx

@hbswcsyzx hbswcsyzx commented Aug 5, 2026

Copy link
Copy Markdown

Fixes #1465

What does this PR do?

Fixes a distributed snapshot visibility race during distributed transaction
commit.

The coordinator keeps the distributed GXID in the proc array through the
existing transaction cleanup sequence. Immediately before the first QE commit
notification, it marks the transaction as commitInProgress while holding
ProcArrayLock. When a QD creates a distributed snapshot, it checks these
markers while holding the same lock. If a marked transaction is found, the QD
waits on the existing GXID heavyweight lock, then reacquires the proc-array
lock and builds the snapshot.

This preserves the required ordering:

  • A snapshot that acquires the lock before the marker sees the GXID as in
    progress.
  • A snapshot that acquires the lock after the marker waits until the QE commit
    notifications and normal QD cleanup have completed.
  • ProcArrayEndGxact(), includeInCkpt, the FORGET record, and crash-recovery
    handling keep their existing lifecycle and ordering.

The one-phase path is marked before COMMIT ONE PHASE dispatch, and the
two-phase path is marked before COMMIT PREPARED dispatch, including retries.
WaitForDtxCommit() also reads the published GXID with pg_atomic_read_u64()
to match the existing atomic GXID protocol.

Type of Change

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature with breaking changes)
  • Documentation update

Breaking Changes

None.

Test Plan

  • Unit tests added/updated
  • Integration tests added/updated
  • Passed make installcheck
  • Passed make -C src/test installcheck-cbdb-parallel

Targeted validation was performed in Docker using a cluster built from this
branch:

  • issue1465_distributed_snapshot passed: 2/2 isolation2 tests.
  • The baseline reproduced the stale-version result; the fixed build returned
    the committed version for both reads using the same repeatable-read
    snapshot.
  • Partial two-phase commit and repeated phase-2 retry tests kept the reader
    blocked until the transaction completed, then returned the committed value.
  • One-phase commit success and injected QE failure/rollback paths passed.
  • A checkpoint/crash-recovery test recorded the committed GXID, intentionally
    crashed the QD during phase 2, and recovered by re-dispatching COMMIT PREPARED; committed data was retained and no prepared transactions remained
    on either QE.
  • A targeted workload with 16 writers and 8 repeatable-read readers completed
    2,700 writes and 18,948 reader transactions with correct results and no
    remaining prepared transactions.

Impact

Performance:

Snapshot creation can wait during the existing QE commit-notification window.
The wait uses the existing local GXID lock and adds no network round trip.
Snapshots outside that window are unchanged.

User-facing changes:

This prevents distributed snapshots from observing mixed committed and
uncommitted versions of the same distributed update. There are no syntax,
catalog, or API changes.

Dependencies:

None.

Checklist

Additional Context

The earlier approach that removed the GXID from the proc array before QE
notification was superseded. The current patch does not remove or advertise a
GXID early; it adds a commit-in-progress marker and waits at QD distributed
snapshot creation. The code change is limited to the DTX/proc-array paths and
one isolation2 regression test.

Copilot AI lite review requested due to automatic review settings August 5, 2026 05:57

@github-actions github-actions 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.

Hi, @hbswcsyzx welcome!🎊 Thanks for taking the effort to make our project better! 🙌 Keep making such awesome contributions!

Copilot AI 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.

Pull request overview

This PR addresses a distributed snapshot visibility race during distributed transaction commit by changing the ordering of GXID/proc-array cleanup vs. QE commit notifications, and adds an isolation2 regression test to reproduce/guard the behavior (issue #1465). In addition, the PR includes a broad set of build/dependency/licensing/documentation updates that are not described in the PR’s stated “fix is contained in …” scope.

Changes:

  • Adjust distributed transaction commit notification ordering so the coordinator clears the distributed GXID from the proc array before dispatching COMMIT/COMMIT PREPARED (retaining a local copy for retries/diagnostics).
  • Add a new isolation2 test + expected output and register it in the isolation2 schedule for issue #1465.
  • Update a variety of build scripts/configuration and add/adjust ASF licensing headers and related files.

Reviewed changes

Copilot reviewed 84 out of 98 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/backend/cdb/cdbtm.c Clears proc-array GXID earlier in commit paths; retains GXID locally for later DTX commands/diagnostics.
src/include/cdb/cdbtm.h Adds dtxGxid field to retain the QD GXID after proc-array cleanup.
src/test/isolation2/sql/issue1465_distributed_snapshot.sql New isolation test reproducing the distributed snapshot/proc-array race.
src/test/isolation2/expected/issue1465_distributed_snapshot.out Expected output for the new isolation test.
src/test/isolation2/isolation2_schedule Registers the new issue1465 isolation test in the schedule.
src/include/crypto/bufenc.h Adds ASF license header block to file header comment.
src/common/sm3.c Adds ASF license header block.
SECURITY.md Adds ASF license header comment block.
README.md Adds ASF license header comment block.
README.apache.md Adds ASF license header comment block.
CONTRIBUTING.md Adds ASF license header comment block.
CODE_OF_CONDUCT.md Adds ASF license header comment block.
deploy/README.md Adds ASF license header comment block.
deploy/build/README.Ubuntu.bash Deletes build helper script.
deploy/build/README.Rhel-Rocky.bash Deletes build helper script.
deploy/build/README.md Deletes build documentation.
deploy/build/README.macOS.md Deletes build documentation.
deploy/build/README.macOS.bash Deletes build helper script.
deploy/build/README.Linux.md Deletes build documentation.
deploy/build/README.CentOS.bash Deletes build helper script.
python-dependencies.txt Bumps PyYAML pinned version to 5.4.1.
gpMgmt/bin/Makefile Reworks python dependency handling to download sources at build time; adjusts pyyaml/mock build steps.
gpMgmt/bin/pythonSrc/ext/.gitkeep Adds placeholder for pip/curl-downloaded Python source archives.
gpMgmt/bin/.gitignore Ignores downloaded python source artifacts under pythonSrc/ext.
gpMgmt/test/README Updates doc text to reflect behave now installed via pip instead of vendored docs.
gpMgmt/bin/gpshrink Adds ASF license header block.
gpMgmt/bin/gpdemo Adds ASF license header block.
gpMgmt/bin/generate-greenplum-path.sh Adds “greenplum naming compatibility” notice block.
gpAux/gpdemo/gpdemo-defaults.sh Adds ASF license header block.
gpAux/client/scripts/greenplum_clients_path.sh Adds “greenplum naming compatibility” notice block.
gpAux/client/install/src/windows/CreatePackage.bat Switches PyYAML installation method to pip-based install.
configure.ac Updates version string; adds AX_COMPARE_VERSION; adds PAX and pythonsrc-ext dependency checks.
configure Regenerated configure script reflecting configure.ac changes (includes PAX dependency/version checks).
config/ax_compare_version.m4 Adds Autoconf Archive AX_COMPARE_VERSION macro file.
pom.xml Updates project version string; adjusts RAT exclude list organization/entries.
NOTICE Simplifies NOTICE contents (removes prior third-party attribution text).
LICENSE Reorganizes/updates third-party license attribution sections.
licenses/LICENSE-setuptools.txt Deletes bundled license text file.
licenses/LICENSE-pyyaml.txt Deletes bundled license text file.
licenses/LICENSE-pygresql.txt Deletes bundled license text file.
licenses/LICENSE-bsd3-psutil.txt Deletes bundled license text file.
licenses/LICENSE-bsd2-mock.txt Deletes bundled license text file.
licenses/LICENSE-bsd2-behave.txt Deletes bundled license text file.
.gitmodules Removes cpp-stub git submodule entry.
contrib/pgcrypto/sm4.h Adds ASF license header block.
contrib/pax_storage/tools/gen_sql.c Replaces // comment header with ASF-style block header.
contrib/pax_storage/src/cpp/storage/README_CTID_in_pax.md Adds ASF license header comment block.
contrib/pax_storage/src/cpp/storage/proto/pax.proto Adds ASF license header block comment.
contrib/pax_storage/src/cpp/storage/proto/micro_partition_stats.proto Adds ASF license header block comment.
contrib/pax_storage/src/cpp/storage/pax_test.cc Updates include path to new in-repo cpp-stub location.
contrib/pax_storage/src/cpp/storage/orc/orc_test.cc Updates include path to new in-repo cpp-stub location.
contrib/pax_storage/src/cpp/storage/file_system_test.cc Updates include path to new in-repo cpp-stub location.
contrib/pax_storage/src/cpp/pax_gtest.cc Updates include path to new in-repo cpp-stub location.
contrib/pax_storage/src/cpp/cpp-stub/src/stub.h Adds vendored cpp-stub header into repo tree.
contrib/pax_storage/src/cpp/cpp-stub/src/addr_pri.h Adds vendored cpp-stub header into repo tree.
contrib/pax_storage/src/cpp/cpp-stub/src_win/addr_any.h Adds vendored cpp-stub Windows header into repo tree.
contrib/pax_storage/src/cpp/cpp-stub/src_darwin/addr_any.h Adds vendored cpp-stub darwin placeholder header into repo tree.
contrib/pax_storage/src/cpp/cpp-stub/README.md Adds vendored cpp-stub README into repo tree.
contrib/pax_storage/src/cpp/cpp-stub/LICENSE Updates cpp-stub LICENSE file contents/header.
contrib/pax_storage/src/cpp/cpp-stub/.gitignore Adds cpp-stub local ignore patterns.
contrib/pax_storage/src/cpp/CMakeLists.txt Adds ASF license header block.
contrib/pax_storage/src/cpp/cmake/pax.cmake Adds ASF license header block.
contrib/pax_storage/src/cpp/cmake/pax_format.cmake Adds ASF license header block.
contrib/pax_storage/src/api/python3/test/paxpy_test.py Adds ASF license header block.
contrib/pax_storage/src/api/python3/setup.py Adds ASF license header block.
contrib/pax_storage/src/api/python3/setup-debug.py Adds ASF license header block.
contrib/pax_storage/src/api/python3/paxtype_cast.cc Adds ASF license header block.
contrib/pax_storage/src/api/python3/paxpy_types.h Adds ASF license header block.
contrib/pax_storage/src/api/python3/paxpy_modules.cc Adds ASF license header block.
contrib/pax_storage/src/api/python3/paxpy_comm.h Adds ASF license header block.
contrib/pax_storage/src/api/python3/paxfilereader_type.cc Adds ASF license header block.
contrib/pax_storage/src/api/python3/paxfile_type.cc Adds ASF license header block.
contrib/pax_storage/src/api/python3/CMakeLists.txt Adds ASF license header block.
contrib/pax_storage/Makefile Adds ASF license header block.
contrib/pax_storage/init_file Adds ASF license header block.
contrib/pax_storage/FindDependencies.cmake Adds ASF license header block.
contrib/pax_storage/doc/README.toast.md Adds ASF license header comment block.
contrib/pax_storage/doc/README.md Adds ASF license header comment block.
contrib/pax_storage/doc/README.format.md Adds ASF license header comment block.
contrib/pax_storage/doc/README.filter.md Adds ASF license header comment block.
contrib/pax_storage/doc/README.dev.md Adds ASF license header comment block.
contrib/pax_storage/doc/README.clustering.md Adds ASF license header comment block.
contrib/pax_storage/doc/README.catalog.md Adds ASF license header comment block.
contrib/pax_storage/doc/performance.md Adds ASF license header comment block.
contrib/pax_storage/CMakeLists.txt Adds ASF license header block.
.github/ISSUE_TEMPLATE/config.yml Adds ASF header block; changes blank_issues_enabled behavior.
.github/ISSUE_TEMPLATE/bug-report.yml Adds ASF header block.
.github/DISCUSSION_TEMPLATE/proposal.yml Adds ASF header block.
.github/DISCUSSION_TEMPLATE/ideas-feature-requests.yml Adds ASF header block.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread configure.ac
Comment on lines +955 to +957
AC_MSG_CHECKING([CMake version])
CMAKE_VERSION=`$CMAKE --version | head -n1 | sed 's/[^0-9]*\([0-9][0-9.]*\).*/\1/'`
AC_MSG_RESULT([$CMAKE_VERSION])
Comment thread configure
Comment on lines +9442 to +9446
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking CMake version" >&5
$as_echo_n "checking CMake version... " >&6; }
CMAKE_VERSION=`$CMAKE --version | head -n1 | sed 's/^0-9*\(0-90-9.*\).*/\1/'`
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CMAKE_VERSION" >&5
$as_echo "$CMAKE_VERSION" >&6; }
Comment on lines +14 to +15
REM Install PyYAML using pip instead of extracting from tarball
pip3 install --target=%GPDB_INSTALL_PATH%\lib\python PyYAML==5.4.1
Comment thread README.md
Comment on lines +1 to +5
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
@hbswcsyzx
hbswcsyzx force-pushed the fix/issue-1465-distributed-snapshot branch from 9440a55 to 8417815 Compare August 5, 2026 06:19
@zhangwenchao-123

Copy link
Copy Markdown
Contributor

Hi guy, thank you for your contribution, but the pr looks also have some problems as following:
1: It breaks distributed snapshot atomicity.

The current ordering maintains a critical invariant: if a distributed snapshot considers a gxid completed (gxid < xmax and not in inProgressXidArray), the transaction has already committed locally on every QE. This is what makes a distributed read atomic across segments.

With this patch, a snapshot taken after ProcArrayEndGxact() but during the COMMIT PREPARED broadcast treats the gxid as committed while some QEs have not yet processed COMMIT PREPARED. On a QE, the distributed verdict only applies after the local xid is committed (the DistributedLog mapping is written at local commit); on a lagging QE the tuple falls back to local visibility rules, where the xid is still a prepared, in-progress transaction. Consequences:

  • Cross-segment mixed reads: the same snapshot sees the new tuple version on QEs that already committed and the old version on QEs that haven't. The window is the entire phase-2 broadcast, including the retry loop (ResetAllGangs() + retries bounded by dtx_phase2_retry_second), which can last seconds or longer.
  • Non-repeatable reads within a single snapshot: a REPEATABLE READ transaction reading a lagging QE before the commit is applied sees the old version; re-reading with the same snapshot after the QE applies COMMIT PREPARED sees the new version, because the verdict switches from the local fallback to the distributed "committed" verdict.
  • The one-phase path is worse: doNotifyingOnePhaseCommit() runs from prepareDtxTransaction() before the QD writes its own commit record and before the QE has committed anything, so the gxid disappears from snapshots before the transaction is durable anywhere.

2: It breaks checkpoint / crash recovery.

ProcArrayEndGxact() also clears tmGxact->gxid and tmGxact->includeInCkpt. getDtxCheckPointInfo() relies on scanning the proc array for entries with includeInCkpt set to include committed-but-not-yet-forgotten distributed transactions in the checkpoint record, so that crash recovery (recoverTM()) can re-dispatch COMMIT PREPARED to the QEs (see the protocol comment near getDtxCheckPointInfo() in CreateCheckPoint()). Today this flag is cleared only after the FORGET record(doInsertForgetCommitted()).

With this patch, if a checkpoint runs during the broadcast/retry window and the QD then crashes before the FORGET record is written, the committed transaction is missing from the checkpoint's committed-gxid list. Recovery will not re-deliver COMMIT PREPARED, and the prepared transactions on the QEs are treated as in-doubt and rolled back — a durably committed distributed transaction gets partially rolled back on the segments. Committed data is lost.

@hbswcsyzx
hbswcsyzx force-pushed the fix/issue-1465-distributed-snapshot branch 3 times, most recently from 8af710c to daf0cef Compare August 5, 2026 16:37
Keep the QD GXID in the proc array through normal transaction cleanup and mark DTX commits while QE notifications are in progress. QD snapshot creation waits on the existing GXID lock until the coordinator and all QEs have a coherent commit view.

Retain the existing checkpoint and FORGET COMMITTED lifecycle, and add deterministic isolation coverage for two-phase and one-phase commit paths.

Fixes apache#1465
@hbswcsyzx
hbswcsyzx force-pushed the fix/issue-1465-distributed-snapshot branch from daf0cef to 8967aa4 Compare August 6, 2026 03:46
Use pg_atomic_read_u64() when WaitForDtxCommit() inspects a DTX.
The GXID is published through atomic_gxid, and a plain 64-bit read
can tear on platforms where 64-bit accesses are not naturally atomic.
Keep the snapshot wait aligned with the existing atomic GXID protocol.

See: Issue#1465 <apache#1465>
@hbswcsyzx

Copy link
Copy Markdown
Author

Thanks for identifying these issues. They exposed a real flaw in the earlier
approach, which removed the GXID from the proc array before QE commit
notification. That approach has been superseded by the current head
4dc2724.

The current fix preserves the original ordering. Before the first QE commit
notification, the QD sets a commitInProgress marker under exclusive
ProcArrayLock. A QD distributed snapshot either acquires the lock before the
marker and records the GXID as in progress, or acquires it after the marker and
waits on the existing GXID heavyweight lock. The wait finishes only after the
QE notifications return and normal QD cleanup reaches the existing proc-array
and lock-release points.

Therefore ProcArrayEndGxact() is not moved, includeInCkpt remains set until
the existing FORGET path, and checkpoint/crash recovery retains its original
GXID list behavior. The one-phase path sets the same marker before
COMMIT ONE PHASE, and the phase-2 marker covers the complete broadcast and
retry loop.

I also changed the wait path to use pg_atomic_read_u64() for atomic_gxid.
Targeted Docker validation covered the isolation2 regression, partial and
retrying phase-2 commits, one-phase commit, checkpoint/crash recovery, and a
16-writer/8-reader repeatable-read workload. The isolation2 test passed 2/2;
the recovery test retained committed data and left no prepared transactions.

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.

[Bug] Concurrent update cause select data error

3 participants