Skip to content

HDDS-15925. Avoid redundant getBucketInfo RPC on OFS getFileStatus#10792

Open
jojochuang wants to merge 7 commits into
apache:masterfrom
jojochuang:HDDS-getfilestatus-single-rpc
Open

HDDS-15925. Avoid redundant getBucketInfo RPC on OFS getFileStatus#10792
jojochuang wants to merge 7 commits into
apache:masterfrom
jojochuang:HDDS-getfilestatus-single-rpc

Conversation

@jojochuang

@jojochuang jojochuang commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Rooted OFS getFileStatus() on normal bucket/key paths (non-snapshot) previously called getBucket() → InfoBucket, then bucket.getFileStatus(), which is already proxy.getOzoneFileStatus(). This PR skips the redundant InfoBucket RPC and calls GetFileStatus only via proxy.getOzoneFileStatus(..., headOp).

Snapshot indicator paths are unchanged: they still use getBucket() and synthetic status (no GetFileStatus).

Behavior change (accepted): Client-side validateBucketLayout() no longer runs on the stat path. OM accepts GetFileStatus for OBJECT_STORE buckets, so rooted getFileStatus may succeed or return FILE_NOT_FOUND instead of throwing IllegalArgumentException. Mutating OFS operations (mkdir, create, delete, etc.) still call getBucket() and reject OBJECT_STORE as before.

This is a client-side optimization for HDDS-15925. It is not the HDDS-11232 approach (no combined OM response / server-side merge of bucket info with file status). HDDS-15925 references HDDS-11232 for the same RPC-reduction theme.

What is the link to the Apache JIRA

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

How was this patch tested?

  • Unit: TestBasicRootedOzoneClientAdapterHeadOp (direct getOzoneFileStatus / headOp)
  • Integration: TestOFS#testGetFileStatusUsesSingleOmRpc (OM metrics: no InfoBucket increment, GetFileStatus +1 per stat)
  • Integration: missing-bucket stat expects FileNotFoundException (client mapping)
  • Smoketest: ozonefs-obs.robot updated for OBS stat vs mutating ops
  • CI on HDDS-15925. Avoid redundant getBucketInfo RPC on OFS getFileStatus #10792

Generated-by: Cursor (Composer)

Cache validated bucket layouts per volume/bucket and call GetFileStatus
directly after cache warm-up, preserving layout checks and headOp behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
Change-Id: Icddbc99fee0cf0e78e52919283f93d29a1986a1c
Copilot AI review requested due to automatic review settings July 17, 2026 20:10
@jojochuang jojochuang changed the title Avoid redundant getBucketInfo RPC on OFS getFileStatus HDDS-15801. Avoid redundant getBucketInfo RPC on OFS getFileStatus Jul 17, 2026

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.

Pull request overview

This PR optimizes ofs:// getFileStatus() by caching per-bucket layout validation in BasicRootedOzoneClientAdapterImpl, so repeated getFileStatus() calls can avoid an extra getBucketInfo/getBucketDetails round-trip and rely on a single OM GetFileStatus RPC after warm-up.

Changes:

  • Added a (volume, bucket)-scoped in-memory cache for validated bucket layouts and wired it into the bucket-resolution path.
  • Updated non-snapshot getFileStatus to validate bucket layout once (cached) and then call proxy.getOzoneFileStatus(...) directly.
  • Added integration tests asserting RPC-count behavior after cache warm-up and covering bucket-root stats, OBS-bucket layout rejection, and missing-file behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java Introduces cached bucket-layout validation and routes getFileStatus through a single OM RPC after warm-up.
hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractRootedOzoneFileSystemTest.java Adds integration coverage for the single-RPC behavior and additional getFileStatus edge cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +314 to +322
private void ensureBucketLayoutValid(OFSPath ofsPath) throws IOException {
String volumeStr = ofsPath.getVolumeName();
String bucketStr = ofsPath.getBucketName();
if (validatedBucketLayouts.containsKey(
bucketLayoutCacheKey(volumeStr, bucketStr))) {
return;
}
resolveAndValidateBucketLayout(volumeStr, bucketStr);
}
Comment on lines +1886 to +1888
} finally {
objectStore.deleteVolume(obsBucket.getVolumeName());
}
@jojochuang
jojochuang marked this pull request as draft July 17, 2026 20:20
Delete the bucket before removing the volume to avoid VOLUME_NOT_EMPTY.

Co-authored-by: Cursor <cursoragent@cursor.com>
Change-Id: Iddfefe48d716e77cb58501514c919a44194cdae7
Copilot AI review requested due to automatic review settings July 17, 2026 20:57

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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment on lines +314 to +322
private void ensureBucketLayoutValid(OFSPath ofsPath) throws IOException {
String volumeStr = ofsPath.getVolumeName();
String bucketStr = ofsPath.getBucketName();
if (validatedBucketLayouts.containsKey(
bucketLayoutCacheKey(volumeStr, bucketStr))) {
return;
}
resolveAndValidateBucketLayout(volumeStr, bucketStr);
}
Comment on lines +136 to +140
/**
* Cache of successfully validated bucket layouts for read paths.
*/
private final ConcurrentHashMap<String, BucketLayout> validatedBucketLayouts =
new ConcurrentHashMap<>();

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.

it might be the case for HBase, so I'd agree with Copilot that we need TTL/eviction policy and size limit for the cache.

@jojochuang
jojochuang marked this pull request as ready for review July 17, 2026 21:32
jojochuang and others added 2 commits July 17, 2026 14:32
Co-authored-by: Cursor <cursoragent@cursor.com>
Change-Id: Ifc177a61594da562cc3cbc13b6f622fada55971d
Mock ClientProtocol and warm the layout cache after getFileStatus stopped
calling OzoneBucket.getFileStatus for non-snapshot paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
Change-Id: I34e369396f682c096fa23b0f5df7b8b165feab6f
Copilot AI review requested due to automatic review settings July 17, 2026 22:09

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java:756

  • validatedBucketLayouts is never invalidated. If a bucket is deleted and later re-created (potentially with a different layout), a cached entry can cause future getFileStatus() calls to skip layout validation and bypass OBJECT_STORE rejection. At minimum, clear the cache entry when OM reports BUCKET_NOT_FOUND so the next call revalidates against the current bucket definition.
    } catch (OMException e) {
      if (e.getResult() == OMException.ResultCodes.FILE_NOT_FOUND) {
        throw new FileNotFoundException(key + ": No such file or directory!");
      } else if (e.getResult() == OMException.ResultCodes.BUCKET_NOT_FOUND) {
        throw new FileNotFoundException(key + ": Bucket doesn't exist!");

Comment on lines +100 to +107
Field cacheField =
BasicRootedOzoneClientAdapterImpl.class.getDeclaredField(
"validatedBucketLayouts");
cacheField.setAccessible(true);
ConcurrentHashMap<String, BucketLayout> cache =
new ConcurrentHashMap<>();
cache.put(volumeName + "/" + bucketName, BucketLayout.FILE_SYSTEM_OPTIMIZED);
cacheField.set(adapter, cache);
@jojochuang jojochuang changed the title HDDS-15801. Avoid redundant getBucketInfo RPC on OFS getFileStatus HDDS-11232. Avoid redundant getBucketInfo RPC on OFS getFileStatus Jul 18, 2026
@jojochuang

Copy link
Copy Markdown
Contributor Author

@yandrey321 this PR aims to remove the extra RPC call in most cases.

@jojochuang jojochuang changed the title HDDS-11232. Avoid redundant getBucketInfo RPC on OFS getFileStatus HDDS-15925. Avoid redundant getBucketInfo RPC on OFS getFileStatus Jul 21, 2026
@yandrey321

Copy link
Copy Markdown
Contributor

this is a good fix for reducing number of RPC calls for metadata, we can extend this fix or file a separate JIRA to route getFileChecksum, listStatus, getVolumeInfo and getBucket through the same cache.

Call proxy.getOzoneFileStatus directly on non-snapshot paths and drop the
client-side layout cache. Document accepted OBJECT_STORE stat behavior change.

Co-authored-by: Cursor <cursoragent@cursor.com>
Change-Id: I5c9df439cb636c7a0a2f0660f6bedce41b2b4419
Copilot AI review requested due to automatic review settings July 23, 2026 20:57

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment on lines +699 to 717
*
* <p>Non-snapshot paths call OM GetFileStatus directly (HDDS-15925) without a
* prior InfoBucket RPC. OBJECT_STORE buckets are not rejected on this path;
* mutating OFS operations still validate layout via {@link #getBucket(OFSPath, boolean)}.
*/
private FileStatusAdapter getFileStatusForKeyOrSnapshot(
OFSPath ofsPath, URI uri, Path qualifiedPath, String userName,
boolean headOp) throws IOException {
String key = ofsPath.getKeyName();
try {
OzoneBucket bucket = getBucket(ofsPath, false);
if (ofsPath.isSnapshotPath()) {
OzoneBucket bucket = getBucket(ofsPath, false);
OzoneVolume volume = objectStore.getVolume(ofsPath.getVolumeName());
return getFileStatusAdapterWithSnapshotIndicator(
volume, bucket, uri);
} else {
OzoneFileStatus status = bucket.getFileStatus(key, headOp);
OzoneFileStatus status = proxy.getOzoneFileStatus(ofsPath.getVolumeName(),
ofsPath.getBucketName(), key, headOp);
return toFileStatusAdapter(status, userName, uri, qualifiedPath,
…atus.

After skipping InfoBucket, OM BUCKET_NOT_FOUND is mapped in the adapter as today
for GetFileStatus paths. Use try/finally so volume cleanup runs on assertion.

Co-authored-by: Cursor <cursoragent@cursor.com>
Change-Id: I2408951221c741ae24b823c69847ac905d1bd9f8
Copilot AI review requested due to automatic review settings July 23, 2026 21:41

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java:702

  • The Javadoc says non-snapshot getFileStatus no longer rejects OBJECT_STORE buckets, but the PR description explicitly says OBJECT_STORE rejection and link-bucket layout validation are unchanged. Since the non-snapshot path now bypasses getBucket(..), any layout validation in getBucket() will not run here. Either update the PR description to reflect the intended behavior change, or restore layout validation for this path (ideally without reintroducing an extra InfoBucket RPC).
   *
   * <p>Non-snapshot paths call OM GetFileStatus directly (HDDS-15925) without a
   * prior InfoBucket RPC. OBJECT_STORE buckets are not rejected on this path;
   * mutating OFS operations still validate layout via {@link #getBucket(OFSPath, boolean)}.

hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java:716

  • Non-snapshot getFileStatus now calls proxy.getOzoneFileStatus(...) directly without first resolving link-bucket layout or validating bucket layout (OBJECT_STORE rejection) as getBucket(...) does. This changes behavior: getFileStatus can succeed (or fail differently) on OBJECT_STORE/link buckets where OFS previously rejected file-system semantics early via OzoneFSUtils.validateBucketLayout(). Consider validating the bucket layout at least once per (volume,bucket) (eg. a small bounded cache) before calling GetFileStatus, so the optimization doesn’t silently drop this semantics check.
      } else {
        OzoneFileStatus status = proxy.getOzoneFileStatus(ofsPath.getVolumeName(),
            ofsPath.getBucketName(), key, headOp);

Rooted OFS getFileStatus on keys no longer rejects OBJECT_STORE layout; ls on
an OBS key succeeds while mutating operations still fail as before.

Co-authored-by: Cursor <cursoragent@cursor.com>
Change-Id: Icfacbc5e8d61b32a9c548a7ed95188dee3cc2925
Copilot AI review requested due to automatic review settings July 23, 2026 22:53

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.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java:718

  • getFileStatusForKeyOrSnapshot() now calls proxy.getOzoneFileStatus() for bucket paths (where keyName is empty) as well as key paths, which bypasses the bucket-layout validation enforced by getBucket(). This can make OBJECT_STORE buckets appear as normal directories via getFileStatus/exists(), even though other OFS operations still reject OBJECT_STORE. Consider handling bucket paths separately: use getBucket(ofsPath,false) to validate layout and return a bucket FileStatusAdapter, while keeping the direct GetFileStatus RPC optimization only for key paths.
      } else {
        OzoneFileStatus status = proxy.getOzoneFileStatus(ofsPath.getVolumeName(),
            ofsPath.getBucketName(), key, headOp);
        return toFileStatusAdapter(status, userName, uri, qualifiedPath,
            ofsPath.getNonKeyPath());

Comment on lines +700 to +702
* <p>Non-snapshot paths call OM GetFileStatus directly (HDDS-15925) without a
* prior InfoBucket RPC. OBJECT_STORE buckets are not rejected on this path;
* mutating OFS operations still validate layout via {@link #getBucket(OFSPath, boolean)}.
Verify ls succeeds on OBS bucket key
${url} = Format FS URL ${SCHEME} ${volume} ${bucket} testfile
${result} = Execute and checkrc ozone fs -ls ${url} 255
Should contain ${result} Bucket: ${bucket} has layout: OBJECT_STORE, which does not support file system semantics. Bucket Layout must be FILE_SYSTEM_OPTIMIZED or LEGACY.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is a breaking change. Interestingly, OM does not reject getFileStatus() call.

Without breaking this behavior, it would require a client side cache to store bucket info. But that's more complex.

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