[
https://issues.apache.org/jira/browse/HDDS-16951?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Andrey Yarovoy updated HDDS-16951:
----------------------------------
Description:
`BasicRootedOzoneFileSystem.rename` calls `adapterImpl.getBucket(ofsSrc,
false)`. That is an `InfoBucket`,
plus an `InfoVolume` and an `InfoBucket` per link hop. It then *{*}branches on
the layout{*}*:
* *{*}FSO{*}*: `renameFSO` → one `RenameKey`. OM renames a file or a whole
directory atomically. Total:
2 OM RPCs.
* *{*}LEGACY{*}*: two or three `getFileStatus` calls, a possible `listStatus`,
then `RenameIterator`, which
issues another `getBucket()` and one `RenameKey` per key under the source.
* *{*}OBJECT_STORE{*}*: `getBucket()` throws `IllegalArgumentException` from
`validateBucketLayout`.
So for the common case, an FSO bucket, half the RPCs of every `rename` exist
only to learn the layout. Hive, Spark and MapReduce commit by renaming, so
`rename` is one of the hottest file system write
operations.
This differs from the other Jiras under HDDS-16761. The fetch is not just a
rejection check. Its result chooses *which algorithm* the client runs. So
moving a check into OM is not enough. OM has to say "this is not FSO", and the
client then has to fall back.
*Why it has to be opt-in*
`RenameKey` is dual-use. `OMKeyRenameRequest` is registered for
`BucketLayout.OBJECT_STORE` (that
registration also serves LEGACY), and these callers send it on any layout:
* `cli-shell`'s `RenameKeyHandler`;
* `TrashOzoneFileSystem`;
* `KeyLifecycleService` (non-OBJECT_STORE);
* the public `OzoneBucket.renameKey`.
On LEGACY a single `RenameKey` renames one key, not a directory tree. So OM
cannot reject anything by default, and the client needs a request field:
{code:java}
message RenameKeyRequest {
required KeyArgs keyArgs = 1;
required string toKeyName = 2;
optional uint64 updateID = 3;
// Set by file system clients that rely on atomic directory rename: OM
// rejects the request with NOT_SUPPORTED_OPERATION unless the resolved
// bucket layout is FILE_SYSTEM_OPTIMIZED.
optional bool requireFileSystemOptimized = 4;
}
{code}
This is deliberately *{*}not{*}* `requireFileSystemLayout`, which only rejects
OBJECT_STORE.
Here LEGACY must also be rejected, because a single `RenameKey` there does not
implement the file system `rename`. The name says what it requires.
*Proposed change*
*OM.* Check on the leader before Ratis, in `OMKeyRenameRequest.preExecute` /
`OMKeyRenameRequestWithFSO.preExecute`
or right after `createClientRequest`. Use the link-resolved layout
`BucketLayoutAwareOMKeyRequestFactory`
already read. When the field is set and the layout is not
`FILE_SYSTEM_OPTIMIZED`, throw
`OMException(NOT_SUPPORTED_OPERATION)`. Neither rename class uses that code
today, so the client can treat it as unambiguous. Do not put the check in
`createRequest` itself, because that also runs at apply time.
*Client.* When `proxy.getOmVersion() >=` a new `OzoneManagerVersion` constant,
`BasicRootedOzoneFileSystem.rename`
first sends `RenameKey` with the field set, with no `getBucket()` before it.
Place the constant after
`LIST_STATUS_REJECTS_OBS(101)`, never below `ZDU(100)`; the number is assigned
at merge.
* Success, or `KEY_ALREADY_EXISTS` / `KEY_RENAME_ERROR` / `KEY_NOT_FOUND`:
same handling as today's
`renameFSO`.
* `NOT_SUPPORTED_OPERATION`: run today's code from `getBucket()` onward.
OBJECT_STORE raises
`IllegalArgumentException` and LEGACY takes the iterating path, both exactly as
today.
* `BUCKET_NOT_FOUND` / `VOLUME_NOT_FOUND`: same fallback, so the error a
caller sees for a missing bucket
or an orphan link is the one `getBucket()` gives today.
* Against any older OM, including one that reports ZDU, keep today's path.
Cost per `rename`:
* FSO: 2 → 1 RPC.
* LEGACY: one extra failed `RenameKey`. LEGACY is rare for ofs, and its rename
already costs many RPCs.
* OBJECT_STORE: one extra failed RPC before the same exception.
`adapterImpl.rename(OzoneBucket, …)` needs a variant that does not take a
fetched bucket. It can build
the handle locally with `OzoneBucket.newBuilder(...)`, or call
`ClientProtocol.renameKey` with the new
options.
*Compatibility*
| |Behavior|
|—|—|
|New client, new OM, FSO|1 RPC|
|New client, new OM, LEGACY / OBJECT_STORE|failed `RenameKey`, then today's
path|
|New client, older OM (incl. ZDU)|as today|
|Old client / shell / trash / lifecycle / `OzoneBucket.renameKey`, new OM|field
absent ⇒ unchanged|
There is one new optional proto field, no new result code and no new
configuration.
*Testing*
* `BasicRootedOzoneFileSystem` / adapter (Mockito):
* FSO: exactly one `RenameKey` with the field set and no `getBucket()`.
*`NOT_SUPPORTED_OPERATION` on a LEGACY bucket takes the iterating path.
*`NOT_SUPPORTED_OPERATION` on an OBJECT_STORE bucket raises
`IllegalArgumentException`.
* The `KEY_*` codes give `false` as today.
* An older OM, including one at `ZDU`, falls back.
* OM request tests:
* With the field set, FSO succeeds, and *{*}both LEGACY and OBJECT_STORE{*}*
get `NOT_SUPPORTED_OPERATION`
before Ratis.
* Without the field, LEGACY and OBJECT_STORE succeed, guarding the shell,
trash and lifecycle callers.
* A link to an FSO bucket succeeds.
* `AbstractRootedOzoneFileSystemTest` (run as `TestOFS` and `TestOFSWithFSO`):
the existing rename suite
passes unchanged on both layouts. That includes rename into an existing
directory, rename onto itself,
and rename across buckets (rejected client-side before any RPC). Count RPCs in
the adapter test, not
with OMMetrics deltas (HDDS-16513).
* Benchmark `TestOfsRenameRpcBenchmark`, same shape as
`TestOfsListStatusRpcBenchmark`:
* 200 FSO buckets, renaming one file back and forth, single-threaded and 10
threads.
* n=3 per arm, built and run strictly one after the other.
* Invariant: exactly 1 OM RPC per FSO `rename`, from 2.
was:
`BasicRootedOzoneFileSystem.rename` calls `adapterImpl.getBucket(ofsSrc,
false)`. That is an `InfoBucket`,
plus an `InfoVolume` and an `InfoBucket` per link hop. It then **branches on
the layout**:
* **FSO**: `renameFSO` → one `RenameKey`. OM renames a file or a whole
directory atomically. Total:
2 OM RPCs.
* **LEGACY**: two or three `getFileStatus` calls, a possible `listStatus`, then
`RenameIterator`, which
issues another `getBucket()` and one `RenameKey` per key under the source.
* **OBJECT_STORE**: `getBucket()` throws `IllegalArgumentException` from
`validateBucketLayout`.
So for the common case, an FSO bucket, half the RPCs of every `rename` exist
only to learn the layout. Hive, Spark and MapReduce commit by renaming, so
`rename` is one of the hottest file system write
operations.
This differs from the other Jiras under HDDS-16761. The fetch is not just a
rejection check. Its result chooses *which algorithm* the client runs. So
moving a check into OM is not enough. OM has to say "this is not FSO", and the
client then has to fall back.
### Why it has to be opt-in
`RenameKey` is dual-use. `OMKeyRenameRequest` is registered for
`BucketLayout.OBJECT_STORE` (that
registration also serves LEGACY), and these callers send it on any layout:
* `cli-shell`'s `RenameKeyHandler`;
* `TrashOzoneFileSystem`;
* `KeyLifecycleService` (non-OBJECT_STORE);
* the public `OzoneBucket.renameKey`.
On LEGACY a single `RenameKey` renames one key, not a directory tree. So OM
cannot reject anything by default, and the client needs a request field:
{code}
message RenameKeyRequest {
required KeyArgs keyArgs = 1;
required string toKeyName = 2;
optional uint64 updateID = 3;
// Set by file system clients that rely on atomic directory rename: OM
// rejects the request with NOT_SUPPORTED_OPERATION unless the resolved
// bucket layout is FILE_SYSTEM_OPTIMIZED.
optional bool requireFileSystemOptimized = 4;
}
{code}
This is deliberately **not** `requireFileSystemLayout`, which only rejects
OBJECT_STORE.
Here LEGACY must also be rejected, because a single `RenameKey` there does not
implement the file system `rename`. The name says what it requires.
### Proposed change
*OM.* Check on the leader before Ratis, in `OMKeyRenameRequest.preExecute` /
`OMKeyRenameRequestWithFSO.preExecute`
or right after `createClientRequest`. Use the link-resolved layout
`BucketLayoutAwareOMKeyRequestFactory`
already read. When the field is set and the layout is not
`FILE_SYSTEM_OPTIMIZED`, throw
`OMException(NOT_SUPPORTED_OPERATION)`. Neither rename class uses that code
today, so the client can treat it as unambiguous. Do not put the check in
`createRequest` itself, because that also runs at apply time.
*Client.* When `proxy.getOmVersion() >=` a new `OzoneManagerVersion` constant,
`BasicRootedOzoneFileSystem.rename`
first sends `RenameKey` with the field set, with no `getBucket()` before it.
Place the constant after
`LIST_STATUS_REJECTS_OBS(101)`, never below `ZDU(100)`; the number is assigned
at merge.
* Success, or `KEY_ALREADY_EXISTS` / `KEY_RENAME_ERROR` / `KEY_NOT_FOUND`: same
handling as today's
`renameFSO`.
* `NOT_SUPPORTED_OPERATION`: run today's code from `getBucket()` onward.
OBJECT_STORE raises
`IllegalArgumentException` and LEGACY takes the iterating path, both exactly as
today.
* `BUCKET_NOT_FOUND` / `VOLUME_NOT_FOUND`: same fallback, so the error a caller
sees for a missing bucket
or an orphan link is the one `getBucket()` gives today.
* Against any older OM, including one that reports ZDU, keep today's path.
Cost per `rename`:
* FSO: 2 → 1 RPC.
* LEGACY: one extra failed `RenameKey`. LEGACY is rare for ofs, and its rename
already costs many RPCs.
* OBJECT_STORE: one extra failed RPC before the same exception.
`adapterImpl.rename(OzoneBucket, …)` needs a variant that does not take a
fetched bucket. It can build
the handle locally with `OzoneBucket.newBuilder(...)`, or call
`ClientProtocol.renameKey` with the new
options.
### Compatibility
| | Behavior |
|---|---|
| New client, new OM, FSO | 1 RPC |
| New client, new OM, LEGACY / OBJECT_STORE | failed `RenameKey`, then today's
path |
| New client, older OM (incl. ZDU) | as today |
| Old client / shell / trash / lifecycle / `OzoneBucket.renameKey`, new OM |
field absent ⇒ unchanged |
There is one new optional proto field, no new result code and no new
configuration.
Points for review:
* The field's semantics are FSO-only, and they differ from the other HDDS-16761
fields. If reviewers
prefer one shape across Jiras, an enum `requiredBucketLayout` (FILE_SYSTEM |
FILE_SYSTEM_OPTIMIZED) is
the alternative. Decide together with the delete Jira.
* The BUCKET/READ ACL check from `getBucketDetails` is dropped on the FSO path.
`RenameKey` keeps its own
key ACL checks.
* The audit log shows a failed `RENAME_KEY` before `INFO_BUCKET` on LEGACY /
OBJECT_STORE.
## Testing
* `BasicRootedOzoneFileSystem` / adapter (Mockito):
* FSO: exactly one `RenameKey` with the field set and no `getBucket()`.
*`NOT_SUPPORTED_OPERATION` on a LEGACY bucket takes the iterating path.
*`NOT_SUPPORTED_OPERATION` on an OBJECT_STORE bucket raises
`IllegalArgumentException`.
* The `KEY_*` codes give `false` as today.
* An older OM, including one at `ZDU`, falls back.
* OM request tests:
* With the field set, FSO succeeds, and **both LEGACY and OBJECT_STORE** get
`NOT_SUPPORTED_OPERATION`
before Ratis.
* Without the field, LEGACY and OBJECT_STORE succeed, guarding the shell, trash
and lifecycle callers.
* A link to an FSO bucket succeeds.
* `AbstractRootedOzoneFileSystemTest` (run as `TestOFS` and `TestOFSWithFSO`):
the existing rename suite
passes unchanged on both layouts. That includes rename into an existing
directory, rename onto itself,
and rename across buckets (rejected client-side before any RPC). Count RPCs in
the adapter test, not
with OMMetrics deltas (HDDS-16513).
* Benchmark `TestOfsRenameRpcBenchmark`, same shape as
`TestOfsListStatusRpcBenchmark`:
* 200 FSO buckets, renaming one file back and forth, single-threaded and 10
threads.
* n=3 per arm, built and run strictly one after the other.
* Invariant: exactly 1 OM RPC per FSO `rename`, from 2.
> Remove the per-call InfoBucket from rooted-ofs rename on FSO buckets
> --------------------------------------------------------------------
>
> Key: HDDS-16951
> URL: https://issues.apache.org/jira/browse/HDDS-16951
> Project: Apache Ozone
> Issue Type: Improvement
> Components: Ozone Client
> Reporter: Andrey Yarovoy
> Assignee: Andrey Yarovoy
> Priority: Major
>
> `BasicRootedOzoneFileSystem.rename` calls `adapterImpl.getBucket(ofsSrc,
> false)`. That is an `InfoBucket`,
> plus an `InfoVolume` and an `InfoBucket` per link hop. It then *{*}branches
> on the layout{*}*:
> * *{*}FSO{*}*: `renameFSO` → one `RenameKey`. OM renames a file or a whole
> directory atomically. Total:
> 2 OM RPCs.
> * *{*}LEGACY{*}*: two or three `getFileStatus` calls, a possible
> `listStatus`, then `RenameIterator`, which
> issues another `getBucket()` and one `RenameKey` per key under the source.
> * *{*}OBJECT_STORE{*}*: `getBucket()` throws `IllegalArgumentException` from
> `validateBucketLayout`.
> So for the common case, an FSO bucket, half the RPCs of every `rename` exist
> only to learn the layout. Hive, Spark and MapReduce commit by renaming, so
> `rename` is one of the hottest file system write
> operations.
> This differs from the other Jiras under HDDS-16761. The fetch is not just a
> rejection check. Its result chooses *which algorithm* the client runs. So
> moving a check into OM is not enough. OM has to say "this is not FSO", and
> the client then has to fall back.
> *Why it has to be opt-in*
> `RenameKey` is dual-use. `OMKeyRenameRequest` is registered for
> `BucketLayout.OBJECT_STORE` (that
> registration also serves LEGACY), and these callers send it on any layout:
> * `cli-shell`'s `RenameKeyHandler`;
> * `TrashOzoneFileSystem`;
> * `KeyLifecycleService` (non-OBJECT_STORE);
> * the public `OzoneBucket.renameKey`.
> On LEGACY a single `RenameKey` renames one key, not a directory tree. So OM
> cannot reject anything by default, and the client needs a request field:
> {code:java}
> message RenameKeyRequest {
> required KeyArgs keyArgs = 1;
> required string toKeyName = 2;
> optional uint64 updateID = 3;
> // Set by file system clients that rely on atomic directory rename: OM
> // rejects the request with NOT_SUPPORTED_OPERATION unless the resolved
> // bucket layout is FILE_SYSTEM_OPTIMIZED.
> optional bool requireFileSystemOptimized = 4;
> }
> {code}
> This is deliberately *{*}not{*}* `requireFileSystemLayout`, which only
> rejects OBJECT_STORE.
> Here LEGACY must also be rejected, because a single `RenameKey` there does
> not implement the file system `rename`. The name says what it requires.
> *Proposed change*
> *OM.* Check on the leader before Ratis, in `OMKeyRenameRequest.preExecute` /
> `OMKeyRenameRequestWithFSO.preExecute`
> or right after `createClientRequest`. Use the link-resolved layout
> `BucketLayoutAwareOMKeyRequestFactory`
> already read. When the field is set and the layout is not
> `FILE_SYSTEM_OPTIMIZED`, throw
> `OMException(NOT_SUPPORTED_OPERATION)`. Neither rename class uses that code
> today, so the client can treat it as unambiguous. Do not put the check in
> `createRequest` itself, because that also runs at apply time.
> *Client.* When `proxy.getOmVersion() >=` a new `OzoneManagerVersion`
> constant, `BasicRootedOzoneFileSystem.rename`
> first sends `RenameKey` with the field set, with no `getBucket()` before it.
> Place the constant after
> `LIST_STATUS_REJECTS_OBS(101)`, never below `ZDU(100)`; the number is
> assigned at merge.
> * Success, or `KEY_ALREADY_EXISTS` / `KEY_RENAME_ERROR` / `KEY_NOT_FOUND`:
> same handling as today's
> `renameFSO`.
> * `NOT_SUPPORTED_OPERATION`: run today's code from `getBucket()` onward.
> OBJECT_STORE raises
> `IllegalArgumentException` and LEGACY takes the iterating path, both exactly
> as today.
> * `BUCKET_NOT_FOUND` / `VOLUME_NOT_FOUND`: same fallback, so the error a
> caller sees for a missing bucket
> or an orphan link is the one `getBucket()` gives today.
> * Against any older OM, including one that reports ZDU, keep today's path.
> Cost per `rename`:
> * FSO: 2 → 1 RPC.
> * LEGACY: one extra failed `RenameKey`. LEGACY is rare for ofs, and its
> rename already costs many RPCs.
> * OBJECT_STORE: one extra failed RPC before the same exception.
> `adapterImpl.rename(OzoneBucket, …)` needs a variant that does not take a
> fetched bucket. It can build
> the handle locally with `OzoneBucket.newBuilder(...)`, or call
> `ClientProtocol.renameKey` with the new
> options.
> *Compatibility*
> | |Behavior|
> |—|—|
> |New client, new OM, FSO|1 RPC|
> |New client, new OM, LEGACY / OBJECT_STORE|failed `RenameKey`, then today's
> path|
> |New client, older OM (incl. ZDU)|as today|
> |Old client / shell / trash / lifecycle / `OzoneBucket.renameKey`, new
> OM|field absent ⇒ unchanged|
> There is one new optional proto field, no new result code and no new
> configuration.
> *Testing*
> * `BasicRootedOzoneFileSystem` / adapter (Mockito):
> * FSO: exactly one `RenameKey` with the field set and no `getBucket()`.
> *`NOT_SUPPORTED_OPERATION` on a LEGACY bucket takes the iterating path.
> *`NOT_SUPPORTED_OPERATION` on an OBJECT_STORE bucket raises
> `IllegalArgumentException`.
> * The `KEY_*` codes give `false` as today.
> * An older OM, including one at `ZDU`, falls back.
> * OM request tests:
> * With the field set, FSO succeeds, and *{*}both LEGACY and OBJECT_STORE{*}*
> get `NOT_SUPPORTED_OPERATION`
> before Ratis.
> * Without the field, LEGACY and OBJECT_STORE succeed, guarding the shell,
> trash and lifecycle callers.
> * A link to an FSO bucket succeeds.
> * `AbstractRootedOzoneFileSystemTest` (run as `TestOFS` and
> `TestOFSWithFSO`): the existing rename suite
> passes unchanged on both layouts. That includes rename into an existing
> directory, rename onto itself,
> and rename across buckets (rejected client-side before any RPC). Count RPCs
> in the adapter test, not
> with OMMetrics deltas (HDDS-16513).
> * Benchmark `TestOfsRenameRpcBenchmark`, same shape as
> `TestOfsListStatusRpcBenchmark`:
> * 200 FSO buckets, renaming one file back and forth, single-threaded and 10
> threads.
> * n=3 per arm, built and run strictly one after the other.
> * Invariant: exactly 1 OM RPC per FSO `rename`, from 2.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]