Skip to content

HDDS-16326. Cache the container chunks directory instead of stat-ing it on every chunk operation - #11151

Draft
rich7420 wants to merge 3 commits into
apache:masterfrom
rich7420:HDDS-16326
Draft

HDDS-16326. Cache the container chunks directory instead of stat-ing it on every chunk operation#11151
rich7420 wants to merge 3 commits into
apache:masterfrom
rich7420:HDDS-16326

Conversation

@rich7420

@rich7420 rich7420 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

ContainerUtils.getChunkDir built a new File(chunksPath) and called exists() on the container's chunks directory on every WriteChunk and ReadChunk (reached via ContainerLayoutVersion.getChunkFile). The chunks directory is fixed for the container's lifetime, so on the read path this stat is pure added work.

The read path now resolves the directory once and caches it: ContainerData.getChunksDirForRead() validates via getChunkDir on first use, stores the result in a transient volatile File field, and reuses it for the container's lifetime. ReadChunk in both layout strategies uses it.

Writes and every other caller keep going through getChunkDir, which still stats on every call. That per-op check is load-bearing: it is how a WriteChunk against a failed volume surfaces as UNABLE_TO_FIND_DATA_DIR so the container is marked UNHEALTHY (covered by TestContainerStateMachineFailures#testUnhealthyContainer). An earlier revision cached it away for writes too and broke that detection; keeping the stat on writes preserves it.

On the read path the eager probe is not needed: a directory that disappears after it was cached is caught by the read's own open(), and a failed read does not mark the container UNHEALTHY (read-side volume failures are handled out of band by the volume checker). setChunksPath invalidates the cache so create / import / move re-resolve, and the field is transient, so it is never serialized to the container YAML. The container scanner (KeyValueContainerCheck) stats the path independently and is unaffected.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-16326

How was this patch tested?

TestContainerUtils (read cache + write re-validation) and TestContainerStateMachineFailures#testUnhealthyContainer (storage-failure detection on write), plus a green fork CI run: https://github.com/rich7420/ozone/actions/runs/33411361078

…it on every chunk operation

ContainerUtils.getChunkDir built a new File and called exists() on the
container chunks directory on every chunk read/write. Resolve and validate it
once, cache the File on ContainerData, and reuse it on the data plane;
setChunksPath invalidates the cache. A chunks directory that disappears
mid-life is still caught by the subsequent open(), so volume-failure handling
is preserved. The container scanner stats the path independently and is
unaffected. Behavior unchanged.

Claude-Session: https://claude.ai/code/session_01FUpCUnmy6JzHPGvwMhyGzq
Copilot AI lite review requested due to automatic review settings August 29, 2026 03:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@rich7420
rich7420 marked this pull request as draft August 29, 2026 03:26
@rich7420

Copy link
Copy Markdown
Contributor Author

Quick JMH of a page-cache-hot ReadChunk (open + read) with the per-op getChunkDir stat (old) vs the cached dir (new), JDK 21 / macOS (5 forks x 5, n=25):

  chunk    old        new       saved
   4KB     11.2 us    9.35 us   1.85 us  (~16%)
   1MB     36.0 us    33.7 us   2.26 us  (~6%)

The removed work is ~one stat plus a File allocation (~2 us, roughly constant), so it is a meaningful fraction for small cached reads and shrinks as the read grows. On the read path the stat is pure added work (the read otherwise goes straight to open()). No fsync in this run, and macOS stat is pricier than Linux, so the fraction is an upper bound. Standalone JMH, not part of the PR.

@rich7420
rich7420 marked this pull request as ready for review August 29, 2026 08:10

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! @rich7420 I noticed that TestContainerStateMachineFailures fails, and I reproduced it locally. I think this change currently breaks storage-failure detection. Could you take a look? Thanks!

https://github.com/apache/ozone/actions/runs/33231158748/job/99045395282#step:13:5472

@rich7420
rich7420 marked this pull request as draft August 31, 2026 14:22
The first version cached the chunks directory in getChunkDir for every caller,
which dropped the per-op existence check that storage-failure detection relies
on. TestContainerStateMachineFailures#testUnhealthyContainer deletes the chunks
directory and expects the next write to mark the container UNHEALTHY; with the
cached directory the write no longer surfaced the missing volume, so the
container stayed healthy.

Restore getChunkDir to validate (stat) on every call so writes and other
operations detect a missing directory as before, and move the cache behind a
new getChunkDirForRead / getChunkFileForRead used only by the readChunk path.
Reads keep the per-op stat savings while writes keep the failure probe.
TestContainerUtils now covers both paths: getChunkDir re-detects a deleted
directory, getChunkDirForRead caches the resolved directory.

Claude-Session: https://claude.ai/code/session_01FUpCUnmy6JzHPGvwMhyGzq
@rich7420
rich7420 marked this pull request as ready for review August 31, 2026 15:03
@rich7420
rich7420 marked this pull request as draft August 31, 2026 15:03
Fold the read-path cache into a single ContainerData#getChunksDirForRead that
resolves once via ContainerUtils.getChunkDir and reuses the container's private
chunksDirFile field, and drop the parallel getChunkDirForRead and
getChunkFileForRead wrappers added earlier. readChunk now resolves through the
existing ContainerLayoutVersion#getChunkFile(File, BlockID, String) overload.

No behavior change: writes and other callers still validate on every
getChunkDir, reads still skip the repeated stat. The public getChunksDirFile /
setChunksDirFile pair is gone; the cache stays private to its owner.

Claude-Session: https://claude.ai/code/session_01FUpCUnmy6JzHPGvwMhyGzq
@rich7420

Copy link
Copy Markdown
Contributor Author

@chihsuan thanks for the reminder

@rich7420
rich7420 marked this pull request as ready for review August 31, 2026 16:26
@rich7420

rich7420 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @chihsuan. The getChunkDir stat doubled as the write-path failure probe, so caching it for all callers dropped the UNHEALTHY-on-write path. Fixed: validate on writes, cache only on reads (getChunksDirForRead). testUnhealthyContainer passes, CI green.

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update @rich7420 I left a few inline comments.

Also, would a case in CommonChunkManagerTestCases be worth it? The existing tests cover the helper methods, but not the changed read paths.

* @return the resolved chunks directory
* @throws StorageContainerException if the chunks directory cannot be resolved
*/
public File getChunksDirForRead() throws StorageContainerException {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this getter be @JsonIgnore?

// of the chunk file. Reads use the cached chunks directory to skip the
// per-read stat; writes keep validating so a missing directory is detected.
File finalChunkFile = FILE_PER_CHUNK.getChunkFile(
containerData.getChunksDirForRead(), blockID, info.getChunkName());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we still validate the directory at this path to preserve the previous error and logging behavior?

* Resolve and validate the chunk directory from the containerData. The
* directory's existence is checked on every call, so a chunk operation
* against a failed volume surfaces as a storage failure; write paths rely on
* this to mark the container unhealthy.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could this Javadoc stay about what the method does? It now describes what callers rely on, and the same note appears in other files.

@rich7420
rich7420 marked this pull request as draft September 2, 2026 04:15
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.

3 participants