Siyao Meng created HDDS-16736:
---------------------------------

             Summary: CommitKey is not idempotent and the S3 gRPC path cannot 
use the Ratis retry cache, so a retry reports KEY_NOT_FOUND for a durable write
                 Key: HDDS-16736
                 URL: https://issues.apache.org/jira/browse/HDDS-16736
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng


h3. Mechanism
The first successful commit deliberately destroys the evidence that it 
happened: {{OMKeyCommitRequest.java:385-390}} tombstones the open key with the 
comment "So that this key can't be committed again", and {{:257-262}} throws 
{{KEY_NOT_FOUND}} when a re-executed commit cannot find it. {{CommitKey}} is 
therefore non idempotent by construction, and the only thing hiding that is the 
Ratis retry cache.

That dedup is unavailable on the S3 path. 
{{OzoneManagerServiceGrpc.java:90-97}} builds a fresh {{Server.Call}} per 
request with {{getClientId()}} returning {{UUIDUtil.randomUUIDBytes()}} 
({{:120}}) and a per request {{callCount}} {{AtomicInteger}}, so every retry 
presents a new {{(ClientId, callId)}} pair and can never hit the cache. The 
code comment at {{:97}} names the random {{ClientId}} as intentional.

Cache expiry, OM restart and leader change are three further routes to the same 
loss.

h3. Trigger
An ordinary RPC timeout retry on the gRPC OM transport. Nothing above re-issues 
the whole PUT, since {{KeyOutputStream}} retries at block level without 
re-issuing {{openKey}}.

h3. Impact
The write becomes durable while the client is told {{KEY_NOT_FOUND}}, surfacing 
as 404 or 412 to an S3 caller and as a final stream close failure to a native 
client. Under If-None-Match: * every contender, including the actual winner, 
concludes it lost.

Bounded rather than destructive: the data is durable rather than corrupted, and 
the externally visible harm needs the gRPC OM transport, because the default 
Hadoop RPC retry cache masks it.

h3. Reproduction
PASS. Artifacts under {{confirmation/MC-4/}} and {{repro/}}, driven from the 
counterexample {{spec/output/r3_MC_hunt_s2.out}} against invariant 
{{DurableWriteIsAcknowledged}}.

h3. Suggested fix
Make {{CommitKey}} idempotent instead of relying on RPC layer dedup: when the 
open key is absent, consult the key table for an entry whose {{updateID}} or 
metadata identifies this writer's client id and the same content, and return OK 
rather than {{KEY_NOT_FOUND}}.

Independently, derive a stable {{(ClientId, callId)}} for the S3 gateway gRPC 
endpoint from the inbound {{OMRequest}} so {{checkRetryCache()}} can dedup 
there too. That also closes the cache expiry, restart and leader change routes, 
which the first change only partially covers.

h3. No patch attached
Unlike the other four findings in this batch, this one is filed as analysis 
only, because both halves of the fix are decisions for reviewers rather than 
mechanical changes.

Making {{CommitKey}} idempotent needs a writer identity to match on, and the 
committed record deliberately does not keep one: the non hsync path strips 
{{HSYNC_CLIENT_ID}} from the record before it is written, with the comment that 
hsync metadata must not reach the final key. So "consult the key table for an 
entry that identifies this writer" requires adding a persisted marker to the 
committed record, a change to the on disk format.

Deriving a stable {{(ClientId, callId)}} for the gRPC endpoint has the same 
problem one layer down. {{OMRequest}} carries a {{clientId}}, but it is a per 
client instance value held in a final field of 
{{OzoneManagerProtocolClientSideTranslatorPB}}, so it is the same for every 
request that client makes, and {{OMRequest}} carries no call id at all. A 
correct retry cache key on that path therefore needs a new protocol field, not 
a different reading of the existing ones.

Both are small changes once the shape is agreed. Neither should be chosen by 
the reporter.

Found by TLA+ model checking and code review of the OM conditional create, 
overwrite and delete paths under HDDS-15926, on commit 
20e6a1c0f6039248373efd3eb1d7f2afcf7f1535. Checked against open HDDS issues for 
duplicates before filing, in addition to the repository's merged git history. 
Generated with Specula (Claude Opus 5).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to