Skip to content

feat(cluster): GCS block dedup capacity + eager-GC (spec-7.2a)#32

Merged
sqlrush merged 7 commits into
mainfrom
spec-7.2a-dedup-capacity-gc
Jul 10, 2026
Merged

feat(cluster): GCS block dedup capacity + eager-GC (spec-7.2a)#32
sqlrush merged 7 commits into
mainfrom
spec-7.2a-dedup-capacity-gc

Conversation

@sqlrush

@sqlrush sqlrush commented Jul 10, 2026

Copy link
Copy Markdown
Owner

GCS block dedup HTAB capacity + pressure-driven eager reclaim, keeping the retransmit-dedup correctness contract fail-closed.

  • Eager reclaim on the MISS cap-full path: only entries proven reclaim-safe (aged past the 2x retransmit window; in-window whitelist empty) are removed before falling back to DENIED_DEDUP_FULL.
  • Capacity: default 1024 -> 4096, ceiling 16384 -> 65536, plus an auto-size floor of MaxConnections x declared node count (clamped at the ceiling), sourced from a pre-shmem pgrac.conf sniff.
  • Observability: dump_gcs gains dedup_entry_count / dedup_evict_count / dedup_max_entries; saturation LOG-once in the TTL sweep.
  • Count-based drop-reply injection (:skipn:N) + test-only relfilenode gate; plain :skip:N unchanged.
  • Tests: header-only reclaim-safety unit (R1-R12), real-HTAB behavioral unit (U1-U6 + auto-size legs), 2-node capacity TAP t/366 (own nightly shard stage7-gcs-dedup-capacity), gcs dump baseline sweep 85->88.

Local gates: full clean cluster_unit 165/165, cluster_regress 13/13, PG regression 219/219, TAP band (t/366, t/110-116, t/215) green, clang-format/cppcheck/comment-headers clean.

Spec: spec-7.2a-gcs-block-dedup-capacity-gc.md

🤖 Generated with Claude Code

SqlRush added 7 commits July 10, 2026 19:55
… tests

Add two pure, header-inline decision primitives that carry the eager-reclaim
correctness contract for GCS block dedup:

- GcsBlockReplyStatusIsReclaimIdempotent(status): in-window reclaim whitelist,
  default fail-closed (false).  Explicit switch so a status is admitted only
  by an added `case ...: return true;`.  Whitelist currently empty.
- GcsBlockDedupEntryIsReclaimSafe(entry, now, out_of_window_us): per-entry
  verdict.  in-flight -> never; completed & aged past the out-of-window (2x)
  -> safe for every status; still in-window -> safe only if the status is
  whitelisted idempotent.

New header-only unit test test_cluster_gcs_block_dedup_reclaim (12 checks,
R1-R12): whitelist fail-closed for every enum status, payload/storage-fallback/
pending-x/lost-write/read-image/forward-marker never in-window, in-flight never
safe, aged-out safe for all, in-window payload unsafe, strict boundary.
Behavioral reclaim path covered in cluster_tap (follow-on).

Registered in cluster_unit Makefile (non-simple, links cluster_version.o +
common/port srv libs; local ExceptionalCondition stub like siblings).

Spec: spec-7.2a-gcs-block-dedup-capacity-gc.md
Wire the eager-reclaim safety primitives into the master-side dedup HTAB so a
cap-full request tries to reclaim a reclaim-safe entry before failing closed,
and raise the capacity defaults.

cluster_gcs_block_dedup.c:
- Add evict_count atomic (init + accessor cluster_gcs_block_dedup_get_evict_count).
- dedup_reclaim_reclaimable_locked(now, want): bounded probe
  (GCS_BLOCK_DEDUP_RECLAIM_MAX_PROBE=256, not a full O(cap) scan under the
  exclusive lock) that removes up to `want` entries GcsBlockDedupEntryIsReclaimSafe
  approves; hash_seq_term on early break; accounts entry_count-- / evict_count++.
- lookup_or_register MISS path: on HASH_ENTER_NULL NULL, reclaim 1 then retry
  once; still full -> unchanged DENIED_DEDUP_FULL fail-closed (HC92).
- TTL sweep also folds removals into evict_count (D5).

cluster_guc.c: cluster.gcs_block_dedup_max_entries default 1024->4096,
ceiling 16384->65536; description refreshed (entry 8448B, ~34.7MB/~554MB,
eager reclaim, PGC_POSTMASTER restart).

Reclaim never removes an entry whose retransmitted duplicate could be re-served
incorrectly: with the whitelist empty it only removes entries aged past the 2x
out-of-window threshold (safe for every status), so it only brings the FULL
decision forward in time. Behavioral coverage (cap-full -> reclaim, evict_count,
retransmit-dedup correctness) lands in cluster_tap.

Spec: spec-7.2a-gcs-block-dedup-capacity-gc.md
…-GC LOG-once + count-based injection + 2-node capacity TAP

Observability (D5):
- dump_gcs: 3 NEW dedup rows (dedup_entry_count / dedup_evict_count /
  dedup_max_entries) via cluster_gcs_get_block_dedup_* wrappers
- dedup TTL sweep: saturation LOG-once when DENIED_DEDUP_FULL grows past a
  threshold, lock-free, no hot-path flood

Injection framework:
- new CLUSTER_FAULT_SKIP_N (skipn:N) count-based skip — drops exactly N times
  then stops so the sender's retransmit lands; existing skip:N stays unlimited
  (zero regression, verified by t/215)
- test-only GUC cluster.gcs_block_drop_target_relfilenode gates the
  drop-reply injection to one relation (default 0 = any)

Tests:
- new t/365_gcs_block_dedup_capacity_2node.pl (L1-L5, shared_catalog 2-node):
  default cap, cross-node distinct-read dedup_full=0, retransmit path exercised
  with correct re-serve, reclaim eviction, dump rows
- baseline sweep: pg_cluster_state gcs category 85->88 across t/110-116;
  cluster.gcs_block_dedup_max_entries default 1024->4096 (t/112)
- nightly.yml: stage7-gcs-dedup-capacity shard (t/365)

Spec: spec-7.2a-gcs-block-dedup-capacity-gc.md
Implement the Q4=A auto-size lower bound: the effective dedup HTAB
capacity is raised to MaxConnections x declared node count (clamped at
the GUC ceiling) so an undersized cluster.gcs_block_dedup_max_entries
cannot saturate under distinct-read pressure.

The declared node count cannot come from cluster_conf_node_count():
cluster_conf_load() runs only after every shmem region is initialised,
so it still reads 0 at size_fn/init_fn time.  Hoist the pre-shmem
[node.N] sniff (previously a static helper in the shared-catalog
bootstrap) into cluster_conf.c as
cluster_conf_declared_node_count_early() with a per-process cache, and
consume it from cluster_gcs_block_dedup_effective_entries().

Stamp the effective capacity into the dedup shmem header at init so
the D5 observability accessor and the saturation LOG report the size
the HTAB was actually built with in every process.  Also refresh the
stale GUC doc block (1024/16384/8312B -> 4096/65536/8448B) and hoist
the ceiling into CLUSTER_GCS_BLOCK_DEDUP_MAX_ENTRIES_CEILING shared by
the GUC registration and the floor clamp.

Spec: spec-7.2a-gcs-block-dedup-capacity-gc.md
… unit (spec-7.2a review F1/F2/F4)

t/365 collides with the write-write TAP already on main under the same
number; the dedup capacity test moves to t/366 (nightly shard range,
banner self-references, and the unit-side t/NNN placeholders follow).

test_cluster_debug gains the 3 missing stubs for the new dedup
capacity/occupancy accessors so a clean full cluster_unit build links
again.

New test_cluster_gcs_block_dedup_htab links the real
cluster_gcs_block_dedup.o against a bounded fake HTAB fixture (with
HASH_REMOVE + hole reuse and a controlled clock) and drives
lookup_or_register to cap: fill-to-cap FULL, in-window retention,
out-of-window eager reclaim + re-register conservation, reclaimed-key
re-registration, remove accounting, and the D4 auto-size floor legs.
The TTL sweep is never called, so evictions are attributable to the
eager path alone.

Spec: spec-7.2a-gcs-block-dedup-capacity-gc.md
… (spec-7.2a review P3)

- skip_remaining field comment said :skip:N; the count-based syntax is
  :skipn:N (0 now documented as exhausted-or-not-armed).
- t/116 L9 label and section header still said 67/48 gcs keys; the
  assertion is 88.
- The drop-reply relfilenode gate comment and GUC description now state
  that current TAP coverage uses target=0 only (shared_catalog remaps
  the physical relNumber away from SQL view) and the non-zero filter is
  reserved for non-shared-catalog rigs.

Spec: spec-7.2a-gcs-block-dedup-capacity-gc.md
…84 (spec-7.2a D4)

The spec-7.2a S1 perf gate requires the shipped default (no hand bump)
to survive the 4-node distinct-read scenario with dedup_full_count = 0
and zero client errors.  Measured on the 4-node RACvsRAC harness at the
shipped default: 4096 and 8192 both saturate during the post-formation
cold storm (DENIED_DEDUP_FULL -> retransmit exhaustion plus downstream
GCS control timeouts); 16384 is green across 3 consecutive runs on all
4 nodes (errors 0, dedup_full_count 0, ~230k qps/node).  Raise the
default to that floor (ceiling 65536 unchanged; ~138MB shmem per
serving node at 8448B/entry) and sweep the default assertions in t/112
and t/366.

Spec: spec-7.2a-gcs-block-dedup-capacity-gc.md
@sqlrush sqlrush merged commit 774f088 into main Jul 10, 2026
5 checks passed
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