Skip to content

feat(cluster): undo-block shared-storage physical data plane (spec-5.22b D2-2)#28

Open
sqlrush wants to merge 4 commits into
mainfrom
spec-5.22b-undo-gcs
Open

feat(cluster): undo-block shared-storage physical data plane (spec-5.22b D2-2)#28
sqlrush wants to merge 4 commits into
mainfrom
spec-5.22b-undo-gcs

Conversation

@sqlrush

@sqlrush sqlrush commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

D2-2 gives an undo segment an optional physical home on the shared cluster_fs root under GCS, breaking the DataDir-local hardcode in the undo I/O path. The migration is gated on a new GUC (cluster.undo_gcs_coherence, default off), so the default path is byte-for-byte the pre-D2-2 behaviour — this mirrors D1 / D2-1's land-then-wire shape.

Path intent (core contract):

  • ClusterUndoPathIntent {RUNTIME_SHARED, MATERIALIZED_LOCAL} names which home a segment resolves to. cluster_undo_intent_for_owner(owner) derives it — own-instance owner → RUNTIME_SHARED; a foreign owner (in D2 only ever a dead-origin copy recovery rebuilt in the local DataDir) → MATERIALIZED_LOCAL. Static inline in cluster_undo_alloc.h so the ~30 call sites add no link dependency.
  • cluster_undo_path_uses_shared_root(intent, peer_mode, coherence) is the single pure decision function: RUNTIME_SHARED resolves under the shared root only when peer-mode and coherence are on; MATERIALIZED_LOCAL always stays local — the dead-origin by-xid resolve path must never move to shared storage.

Threading + migration:

  • intent is a new first parameter on cluster_undo_path_resolve and the four undo smgr APIs (read/write_block, read/write_header_bytes), carried in the get_segment_fd fd-cache key.
  • the redo write surface (cluster_undo_xlog.c: build_undo_segment_path / redo_open_segment / redo_stamp_slot) delegates to the same cluster_undo_path_resolve so runtime and redo cannot split-brain an own-instance segment onto different roots.
  • the shared home resolves via cluster_shared_fs_undo_path_resolve / _instance_dir_resolve (owner-partitioned instance_<N> under cluster.shared_data_dir). Undo is outside the RelFileLocator namespace, so this is a path resolver, not a shared_fs vtable callback.
  • ensure-instance-subdir gains a dual branch (shared root → pg_mkdir_p; local → the existing mkdir).

Supporting:

  • new GUC cluster.undo_gcs_coherence (bool, default off, PGC_SIGHUP); check hook rejects turning it on while cluster.shared_data_dir is unset.
  • D4 tripwire: cluster_undo_path_resolve asserts (intent == RUNTIME_SHARED) == (owner == self).
  • drop a now-redundant local extern int cluster_undo_segments_max_per_instance (the new cluster_guc.h include already declares it) to clear a shadowed-declaration finding.

Stacking / merge order

This PR is stacked on #23 (D1, spec-5.22a-undo-resid) and includes D1 + D2-1 + D2-2 commits, since D1 is not yet merged. Intended merge order is D1 → D2-1 → D2-2; final merge is deferred to the substrate merge window.

Test plan

  • cluster_unit 159/159 (new: test_cluster_undo_gcs U3–U11 — shared-root branch per mode, materialized-never-migrates, intent derivation)
  • PG 219/219 (cassert)
  • cluster_regress 13/13
  • undo / recovery / CR TAP green with coherence off: t/018, t/070, t/213, t/215, t/247, t/248, t/253
  • clang-format (v18) 0 violations, comment-headers 0, SCN / GES / clog gates OK
  • linkdb fast-gate CI all 5 jobs green (this PR)

Spec: spec-5.22b-undo-block-gcs-integration.md

SqlRush added 4 commits July 8, 2026 12:37
Name an undo block (including the segment TT header block) as a
first-class cluster resource: (owner_node, undo_segment, block_no,
generation) encoded into the 16-byte ClusterResId wire format as
class 0xF9.  Undo is the first owner-as-master resid class: the
resource master IS the owning instance (cluster_undo_resid_master),
never a GRD shard-hash node.  The GRD hash-master lookup now fails
closed (Assert + new SQLSTATE 53R9Q) if an undo resid reaches it,
and the generation predicate (segment wrap_count) lets callers fail
closed on recycled-segment stale references.

Pure identity layer only: no grant/PI/serving data plane, no write
path change, no on-disk format or wire ABI change (ClusterResId
stays 16 bytes; new class byte value only).

- new cluster_undo_resid.h: class byte 0xF9 + collision StaticAssert
  net (9 header-visible classes + LOCKTAG_LAST_TYPE) + field mapping
- new cluster_undo_resid.c: encode/decode/is_undo/master/
  generation_matches pure layer (standalone-linkable)
- cluster_grd.c: fail-closed undo-class guard at
  cluster_grd_lookup_master entry
- errcodes.txt: 53R9Q ERRCODE_CLUSTER_UNDO_RESID_HASH_ROUTED
- tests: test_cluster_undo_resid (8 cases: round-trip, class byte,
  discriminator, 16B wire lock, owner bounds, owner-as-master,
  generation); guard-fire leg in test_cluster_grd via an ereport
  trampoline; t/006 SQLSTATE spot-check line

Spec: spec-5.22a-undo-block-resource-identity.md
…2-1)

D2-1 wires the D1 undo-block resource identity into the GCS data plane's
routing half: two predicates that keep undo resources off the GRD/GCS
hash-master path.  Their authority lives at the owning instance, so the
master IS the encoded owner_node (cluster_undo_resid_master), never a
shard hash -- the D1-5 guard already fails closed (53R9Q) on any undo
resid that reaches cluster_grd_lookup_master / cluster_gcs_lookup_master.

- cluster_undo_gcs.{c,h} (new): cluster_undo_block_lookup_master returns
  the owner_node; cluster_undo_block_master_is_self is the local
  fast-path gate (owner_node == cluster_node_id).  The owner-incarnation
  epoch self-check that L364 requires before serving from the local fast
  path is deferred to the grant/acquire path (D2-3), where the
  co-sampled live-authority triple is available, so this routing layer
  stays pure (node-id only) and cluster_unit links it standalone.
- src/backend/cluster/Makefile: cluster_undo_gcs.o added to OBJS.
- test_cluster_undo_gcs (new, U1/U2): lookup_master returns owner (not a
  hash); master_is_self true for owner==self, false for a foreign owner.
  Links cluster_version.o + cluster_undo_resid.o + cluster_undo_gcs.o
  with a test-owned cluster_node_id stub.

Zero behaviour change: no consumer routes undo through these predicates
yet (undo_gcs_coherence data plane is D2-3+), so this mirrors D1's
land-then-wire shape.

Spec: spec-5.22b-undo-block-gcs-integration.md
…22b D2-2)

D2-2 gives an undo segment an optional physical home on the shared
cluster_fs root under GCS, breaking the DataDir-local hardcode in the
undo I/O path.  The migration is gated on a new GUC
(cluster.undo_gcs_coherence, default off), so with the default the
whole change is inert and every path resolves to the local DataDir
byte-for-byte as before -- mirroring D1/D2-1's land-then-wire shape.

Path intent (the core contract):
- ClusterUndoPathIntent {RUNTIME_SHARED, MATERIALIZED_LOCAL} names which
  home a segment resolves to.  cluster_undo_intent_for_owner(owner)
  derives it -- an own-instance owner is RUNTIME_SHARED, a foreign owner
  (in D2 only ever a dead-origin copy recovery rebuilt in the local
  DataDir) is MATERIALIZED_LOCAL.  Static inline in cluster_undo_alloc.h
  so the ~30 call sites add no link dependency.
- cluster_undo_path_uses_shared_root(intent, peer_mode, coherence) is
  the single pure decision function: RUNTIME_SHARED resolves under the
  shared root only when peer-mode AND coherence are on;
  MATERIALIZED_LOCAL always stays local (P1-3 hard contract -- the
  dead-origin by-xid resolve path must never move to shared storage).

Threading + migration:
- intent is a new first parameter on cluster_undo_path_resolve and the
  four undo smgr APIs (read/write_block, read/write_header_bytes) and is
  carried in the get_segment_fd fd-cache key.
- the redo write surface (cluster_undo_xlog.c build_undo_segment_path /
  redo_open_segment / redo_stamp_slot) delegates to the SAME
  cluster_undo_path_resolve, so runtime and redo cannot split-brain an
  own-instance segment onto different roots.
- the shared home resolves via cluster_shared_fs_undo_path_resolve /
  _instance_dir_resolve (owner-partitioned instance_<N> under
  cluster.shared_data_dir).  Undo is outside the RelFileLocator
  namespace, so this is a path resolver, not a shared_fs vtable callback.
- ensure-instance-subdir gains a dual branch (shared root -> pg_mkdir_p
  on the shared tree; local -> the existing mkdir) so a coherence-on
  tree is physically complete.

Supporting:
- new GUC cluster.undo_gcs_coherence (bool, default off, PGC_SIGHUP);
  check hook rejects turning it on while cluster.shared_data_dir is
  unset (GUC_check_errdetail).
- D4 tripwire: cluster_undo_path_resolve asserts
  (intent == RUNTIME_SHARED) == (owner == self), guarding the future
  dead-owner foreign-runtime-shared serve case.
- drop a now-redundant local extern of
  cluster_undo_segments_max_per_instance (the new cluster_guc.h include
  already declares it) to clear a shadowed-declaration finding.
- cluster_undo_alloc.h now includes cluster/cluster_scn.h (latent).

Default path (coherence off) is byte-for-byte the pre-D2-2 behaviour.

Tests: test_cluster_undo_gcs U3-U11 (shared-root branch per mode,
materialized-never-migrates, intent derivation); intent parameter
threaded through test_cluster_undo_buf / test_cluster_tt_durable stubs.
cluster_unit 159/159, PG 219/219, cluster_regress 13/13, and the
undo/recovery/CR TAP set (018/070/213/215/247/248/253) all green with
coherence off.

Spec: spec-5.22b-undo-block-gcs-integration.md
…c-5.22b D2-3)

Add cluster_undo_block_acquire_shared: a peer acquires a coherent shared
view of an owning instance's undo block through owner-as-master routing.
The owner (authority and holder) ships the current image over the reused
undo-TT fetch wire; the requesting peer consumes the shipped image and
never opens the foreign undo file. Admission is fail-closed on two ANDed
dimensions -- segment-generation anti-ABA and owner-incarnation epoch plus
durable coverage -- so any miss/DENIED/doubt leaves the caller's existing
fail-closed visibility boundary untouched.

Structure follows the pure-policy split so cluster_unit links the decision
core standalone:
  - cluster_undo_gcs.c gains the pure predicates cluster_undo_grant_armed
    (coherence x peer-mode gate), cluster_undo_grant_admissible (reuses
    cluster_vis_live_authority_covers_policy and the D1 generation check),
    and cluster_undo_grant_reader_pcm_mode/_transition (S via N->S).
  - cluster_undo_gcs_grant.c (new) holds the runtime wrapper with its
    heavy fetch/epoch/GUC dependencies, registered in the backend OBJS.

Everything is gated behind cluster.undo_gcs_coherence (default off), so the
default path is byte-for-byte unchanged. The master==self local fast path
is fail-closed in this increment pending its owner-incarnation self-check;
the live-owner peer read path is fully implemented.

test_cluster_undo_gcs gains U4/U6/U7/U9 (reader lock contract, epoch and
generation admissibility, coherence gate); its recipe links the pure
live-authority policy object. Runtime wrapper is covered by the later TAP
legs.

Local gates (coherence off): cluster_unit 159/159 (undo_gcs 9/9), PG
219/219, cluster_regress 13/13, undo/CR/recovery TAP 6 files/160 tests,
clang-format-18 clean, comment-headers clean.

Spec: spec-5.22b-undo-block-gcs-integration.md
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.

1 participant