[
https://issues.apache.org/jira/browse/HDDS-16981?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-16981:
------------------------------
Attachment:
CR-6-do-not-close-block-filled-on-retry-inside-retry-handling.patch
> Ratis key write waits for its own retry handling when the data written on
> retry fills the replacement block
> -----------------------------------------------------------------------------------------------------------
>
> Key: HDDS-16981
> URL: https://issues.apache.org/jira/browse/HDDS-16981
> Project: Apache Ozone
> Issue Type: Bug
> Reporter: Siyao Meng
> Priority: Major
> Attachments:
> CR-6-do-not-close-block-filled-on-retry-inside-retry-handling.patch
>
>
> h3. Mechanism
> Since HDDS-11239 (2.0.0) the block that {{KeyOutputStream.handleRetry}}
> allocates to take over the unacknowledged data of a failed block is created
> in retry handling mode ({{BlockOutputStreamEntry.isHandlingRetry}}). Calls
> that find the flag set wait in {{waitForRetryHandling}} until
> {{KeyOutputStream.handleException}} has finished the handling of the failed
> block and calls {{finishRetryHandling}}.
> {{handleRetry}} writes the data again through {{handleWrite(retry=true)}}.
> When that data fills the new block, {{handleWrite}} calls
> {{handleFlushOrClose(StreamAction.FULL)}} to close it. {{handleFlushOrClose}}
> waits for the retry handling of the current block like any other call, but
> here the caller is the retry handling, so the thread waits for a signal that
> only it can send.
> The Javadoc of {{handleFlushOrClose}} assumes that a block written on retry
> "will be at max half full". That holds when the block is at least twice
> {{ozone.client.stream.buffer.max.size}}, and nothing enforces it.
> h3. Trigger
> # {{ozone.scm.block.size}} at or below
> {{ozone.client.stream.buffer.max.size}}. The defaults are 256MB and 32MB, so
> the default block size is not affected. Nothing validates the two settings
> against each other.
> # A block of a Ratis key fails while a full block of its data is
> unacknowledged, and a new block is allocated for the retry. If a block
> preallocated at key creation takes the retry, it is not in retry handling
> mode and the write returns.
> Run on unmodified source with mock datanodes and default buffer sizes (blocks
> of 1MB, 4MB, 16MB and 32MB wait, blocks of 64MB and 256MB return), and on a
> mini cluster with a block size of 1MB and no failure injected, where SCM
> closed the container of the first block before the block was sent.
> h3. Impact
> * {{write}} does not return. There is no exception and no timeout, and only
> an interrupt ends the wait. The stream is closed then and the key is not
> committed.
> * No committed data is affected.
> * Workaround: a block size above {{ozone.client.stream.buffer.max.size}}.
> h3. Reproduction
> No standalone reproduction is attached to this issue. The regression test in
> the patch, {{TestOzoneClient.testPutKeyRatisWhenRetriedDataFillsBlock}},
> covers it with mock datanodes: without the change five of its six cases run
> into their timeout, the sixth is a control with a half full block.
> h3. Patch
> [^CR-6-do-not-close-block-filled-on-retry-inside-retry-handling.patch],
> against 031e76aa2c9a60b67185390b5b33499cbbb82e43. It also applies to master
> at d0454e0cffd.
> {{KeyOutputStream.handleWrite}} no longer issues the {{FULL}} action when the
> write is the rewrite of a retry. The full block stays the current block and
> is closed once the handling of the failed block is finished, through the
> normal wait, by the action that was being retried, by the next {{write}} or
> by {{close}}.
> The change takes effect only when the data written again fills the block.
> Besides the case above that is a retry that lands on a preallocated block,
> which does not wait today: there the block is closed one step later with the
> same result (run with mock datanodes and on the mini cluster, with and
> without the patch: same block lengths, key reads back).
> Skipping the wait for {{FULL}} would also end the wait, but it is not safe
> with concurrent calls ({{ozone.client.key.write.concurrency}} other than 1):
> the writer closes the retry block and moves to a next block while the handoff
> of the failed block is still pending. In a mock datanode run of that variant
> with a pending {{hsync}}, {{close}} failed with "Expected offset: 2560
> expected len: 3072". With the attached patch the same run returns for both
> calls and the key reads back.
> The regression test checks the committed block lengths and reads the data
> back, for a key of exactly one block and a key of 2.5 blocks, with the
> failure noticed by the close of the full block or by the write that fills it,
> and with the replacement block failing as well. With the patch all tests of
> {{hadoop-ozone/client}} (165) pass, checkstyle is clean, and on a mini
> cluster {{TestHSync}} (42), {{TestBlockOutputStreamWithFailures}} (20),
> {{TestFailureHandlingByClient}} (6),
> {{TestFailureHandlingByClientFlushDelay}} (1),
> {{TestOzoneClientRetriesOnExceptions}} (2),
> {{TestOzoneClientRetriesOnExceptionFlushDelay}} (1) and
> {{TestMultiBlockWritesWithDnFailures}} (2) pass.
> The patch is independent of HDDS-16979 and HDDS-16980 and applies before or
> after their patches. It and the patch of HDDS-16975 both add imports and a
> test to {{TestOzoneClient}}, so the one merged second needs a rebase of that
> file.
> Found by code review of the Ratis key write path while validating fixes from
> the TLA+ verification effort under HDDS-15926, on commit
> 031e76aa2c9a60b67185390b5b33499cbbb82e43. Checked against HDDS issues and
> apache/ozone pull requests for duplicates before filing. The attached patch
> is a proposal for review. Generated with Specula (Claude Opus 5.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]