Skip to content

Avoid false 404s when residency holders have only stubs - #1112

Merged
kriszyp merged 11 commits into
mainfrom
fix/residency-not-resident-answer
Oct 23, 2026
Merged

kriszyp merged 11 commits into
mainfrom
fix/residency-not-resident-answer

Conversation

@kriszyp

@kriszyp kriszyp commented Oct 23, 2026 •

Copy link
Copy Markdown
Member
Stub holders point elsewhere while a stopped holder has the full record

⊙ Problem

A stale residency list can name only stub holders. Their empty GET_RECORD answers look like missing records, so a read returns 404 while full copies still exist elsewhere.

🤔 Versioned answers settle; empty replies need every holder

A versioned live record or deletion settles immediately. Otherwise, absence needs every non-self holder’s answer and at least one unversioned empty. This prevents false 404s during partial outages but can turn ID-derived misses into 502s.

💡 Solution

Peers advertise residencyNotResident. Capable requesters receive false for stubs; missing rows answer with an unversioned empty; deletions retain main’s versioned tombstone frame. For stubs and missing rows, incapable or unlearned requesters receive the legacy empty frame byte-for-byte. Exhausting only non-resident holders produces a descriptive 502.

⚖️ Alternatives

Framing-Verdict: better-alternative-exists, resolved: aggregate weak empties across all holders; accept versioned answers immediately.

🤔 Keep the compatibility gate

Legacy peers require identical frames, so the marker has its own capability. Retrying old empty replies cannot distinguish stubs from absence.

🤔 Retain sequential holder requests

Parallel GET_RECORD requests would also start parallel relocations, so requests stay sequential. Isolated main/HEAD loader measurements over loopback WebSockets with 10 ms delayed replies gave 11/21/31 ms for 1/2/3 empty holders, versus main’s 11 ms. A 100 ms second holder raised the miss to 111 ms; a first-holder deletion still took one request. These measure the mechanism, not production latency. Follow-up: #1114.

🤔 Keep the design wording

The notes describe terminal versioned answers, network-error causes and legacy empties; further stylistic rewrites would not change that contract.

🔧 Changes

Product and architecture tour

Responses and holder selection

Listed non-self holder answers Read result
Versioned live record Live record immediately
Versioned deletion Deletion immediately; no later holder requested
No versioned answer; every holder answers; at least one unversioned empty 404
Empty plus unreachable or disconnected 502
Every holder answers not resident 502 naming non-residency

The capability registry advertises and normalizes the new key without changing the protocol version. The WebSocket responder gates only the stub marker and keeps the existing versioned null tombstone. The receiver passes through strict false without decoding or relocation. Stored and ID-derived lists both try the next holder after negative answers. Network errors remain the final 502's cause.

The Pro design notes and root index state the response contract. The merged core companion qualifies legacy stub copy: peers lacking learned residencyStubCopy support receive put when listed or without a residency list; excluded peers follow normal routing. Its gitlink update is separate.

#1113 tracks the requirement that #1101 extend the gated answer to REPLICA rows and preserve the false relocation guard; this base has no such flag. #1092 overlaps the capability registry. Older responders still answer stubs the same as absence. Session/reconnect work (#1083–#1085) remains separate.

✅ Verification

Executed checks and baseline failures

PR QA: skipped @ dc21989 — this one-task dispatch prohibits sub-agent delegation

  • Forced rebuild reproduces the stopped-holder false 404 against the prior implementation. Base-code proofs also fail the capability and all-stub regressions.
  • Both engines: wire/loader tests and capability tests (98 total), plus seven four-node tests pass. Four new sender/receiver/loader regressions prove a versioned deletion settles before an older live or offline holder, without requesting the second holder; peer connections and relocation are mocked. The empty-plus-unreachable HTTP regression remains 502; the fixture control changes only the test reader’s holder list. Seven deletion-frame cases fail before repair. The explicit one-worker configuration also passes all seven cluster cases on both engines.
  • Targeted cache-source check: two cases pass on each engine; ordinary stored/ID-derived resident invalidations refill from the canonical source without GET_RECORD.
  • Full integration: 277 pass, 15 optional skips; full RocksDB units: 1,980 pass, two pending. Typecheck, required lint, formatting, and core DESIGN validation pass.
  • Full LMDB units: 1,967 pass, two pending, 13 pre-existing failures in copy/log-key and record-lock cases, all reproduced on freshly built main. Strict lint has 58 warnings with identical normalized signatures on main.

Refs #436.

Depends-on: HarperFast/harper#3475

🤖 Generated by OpenAI Codex; posted via @kriszyp.

Release target: v5.4 — follows the linked issue and residency fixes.

Related PRs: #1092 overlaps, #1101 overlaps, #1087 overlaps, 2 others independent
Complexity: complicated

Follow-ups #1113 and #1114 are open and pass the readiness check; the receipt has not yet linked #1114.

Deferred findings

  • 731730d933ac1561 minor performance — replication/replicator.ts — Sequential id-derived misses increase latency and fail when a listed holder is unavailable — NOT FILED
  • 2d93f19e8672063f nit documentation — replication/DESIGN.md — Clarify future REPLICA compatibility when integrating PR 1101 — filed Reconcile REPLICA not-resident documentation when #1101 integrates #1113

Dispatch: task hp-residency-not-resident-answer · queued by unknown · ran by codex/gpt-6-astra/xhigh · worker kzyp-xps-1

Review-Coverage: authored=codex; ran=gemini,claude; adjudicated=domain; declined=cursor-grok,cursor-composer,muse; rounds=4; full=4 @ dc21989

Review-Attention: study ~15m (critical: protocolCapabilities.ts, replicationConnection.ts +1; decisions: do-less-alternative, absence-evidence, terminal-versioned-answer, negative-read-policy, sequential-holder-requests) @ dc21989

kriszyp and others added 11 commits October 23, 2026 05:56
Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
…rtions

Use a separate single-holder missing key to establish a real empty reply, then repeat the stopped-holder read. Tighten strict-false receiver coverage and capability expectations, and describe the enduring request-timer limitation.

Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Retain main’s deletion frame for all requesters. Cover deletion before an older live or unreachable holder through the sender, receiver and loader for stored and ID-derived lists.

Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Dispatch-Task: hp-residency-not-resident-answer

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
@kriszyp kriszyp added this to the v5.4 milestone Oct 23, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new protocol capability, residencyNotResident, which allows the GET_RECORD replication request to distinguish between a "not resident" response (returning false) and an "absence" response (returning an empty response). This prevents a stale residency list or empty holder from incorrectly reporting absence when a record is actually present on an unreachable or offline node. The changes include updates to replication design documentation, capability negotiation, connection handling, replicator loading logic, and comprehensive integration and unit tests to verify the new behavior. No review comments were provided, so there is no feedback to evaluate.

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