[ 
https://issues.apache.org/jira/browse/HDDS-16953?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Andrey Yarovoy updated HDDS-16953:
----------------------------------
    Description: 
`BasicRootedOzoneClientAdapterImpl.createFile` and `createStreamFile` (behind 
`FileSystem.create` / `createNonRecursive`) call `getBucket(ofsPath, 
recursive)` and then `OzoneBucket.createFile`. So every
`create` sends at least 2 OM requests: an `InfoBucket` followed by a 
`CreateFile`, and a link bucket adds an `InfoVolume` and an `InfoBucket` for 
each hop. On write-heavy workloads such as ingest, ETL output and Hive inserts, 
`create` is the hottest write metadata operation.

The fetch does three jobs:

1. *{*}Layout check.{*}* `OzoneFSUtils.validateBucketLayout` rejects 
OBJECT_STORE. `OMFileCreateRequest` is registered for 
`BucketLayout.OBJECT_STORE`, so OM does not reject it.
2. *{*}Replication.{*}* The adapter passes `bucket.getReplicationConfig()` to
`OzoneClientUtils.resolveClientSideReplicationConfig`. *{*}If the bucket 
default is EC, the bucket wins{*}*, because "there is no way for file system 
interfaces to pass EC replication". Otherwise the FS-supplied factor wins, as 
RATIS or as the client-configured type adjusted to that factor.
3. *{*}Auto-create.{*}* With `recursive=true` (always set by `CopyCommands`), a 
missing volume or bucket is created.

*Proposal: opt-in fields that let OM apply the file system rule*
{code:java}
message CreateFileRequest {
required KeyArgs keyArgs = 1;
required bool isRecursive = 2;
required bool isOverwrite = 3;
optional uint64 clientID = 4;
// Set by file system clients: OM rejects the request with
// NOT_SUPPORTED_OPERATION if the resolved bucket layout is OBJECT_STORE.
optional bool requireFileSystemLayout = 5;
// Set by file system clients, which cannot express EC replication: if the
// bucket default replication is EC, OM uses it instead of the replication
// in keyArgs.
optional bool preferBucketEcReplication = 6;
}
{code}
The second field reproduces the client rule exactly. The non-EC branch of
`resolveClientSideReplicationConfig` does not depend on the bucket. So the 
client can compute it with
`bucketReplConfig = null` and send the result. Then:
 * if the field is set and the bucket default is EC, OM uses the bucket default;
 * otherwise OM uses the client value. If the client sent nothing, OM falls 
back to the bucket default,
then the server default, through the existing logic.

That is the same decision the client makes today, with the inputs on the other 
side of the wire.

*OM.* `OMFileCreateRequest.preExecute` already reads `bucketInfo` 
(`ozoneManager.getBucketInfo`) to feed
`resolveReplicationConfigPreference`, before it calls `allocateBlock`. Both 
checks go there, on the
leader, before Ratis, and *{*}before `allocateBlock`{*}*. Otherwise a rejected 
`create` would still allocate
SCM blocks. Things to settle in review:
 * whether `getBucketInfo` returns the *source* bucket's default replication 
for a link bucket (UNVERIFIED);
 * whether the layout check can use the factory's already-resolved layout 
instead of re-deriving it.
This adds no extra lookup either way. Do not put either check in
`BucketLayoutAwareOMKeyRequestFactory.createRequest`, because that also runs at 
apply time on every OM.

*Client.*
 * When `proxy.getOmVersion() >=` a new `OzoneManagerVersion` constant, the 
adapter sends `CreateFile` with
both fields set and does not call `getBucket()` first. Place the constant after
`LIST_STATUS_REJECTS_OBS(101)` and never below `ZDU(100)`; the number is 
assigned at merge. Against any
older OM, including ZDU, keep today's path.
 * `RpcClient.createFile` builds the output stream from the replication 
returned by the server. Check
that nothing between `createFile` and the stream relies on the client-side 
value. `RpcClient` rejects
EC client-side for OM < 2; that is irrelevant here, because such an OM takes 
the fallback.
 * Auto-create on the error path, as in the mkdirs Jira. On `VOLUME_NOT_FOUND` 
/ `BUCKET_NOT_FOUND` with
`recursive=true`, call `getBucket(ofsPath, true)` and retry once, using today's 
replication path
because the bucket is now in hand. With `recursive=false`, propagate as today.
 * Map `NOT_SUPPORTED_OPERATION` to `IllegalArgumentException`. 
`FILE_ALREADY_EXISTS` / `NOT_A_FILE` keep
mapping to `FileAlreadyExistsException`.

*Other callers*

`CreateFile` has no S3 Gateway caller (S3 uses `CreateKey`). `freon 
OmMetadataGenerator` calls it on any
layout. Neither sets the new fields, so their behavior is unchanged.

*Compatibility*
| |Behavior|
|—|—|
|New client, new OM, bucket exists|1 RPC; OBJECT_STORE rejected; EC bucket ⇒ 
EC, as today|
|New client, new OM, bucket missing (recursive)|failed `CreateFile` → 
`getBucket(…, true)` → `CreateFile`|
|New client, older OM (incl. ZDU)|as today|
|Old client / freon / direct `createFile`, new OM|fields absent ⇒ unchanged|

There are two new optional proto fields, no new result code and no new 
configuration.

*Testing*
 * *{*}Replication decision table.{*}* In both an OM request test and the 
adapter, cover these inputs:
 * bucket default: EC, RATIS, or none;
 * client-configured `ozone.replication`: unset, RATIS, or EC;
 * FS factor: 1, 3, or unsupported;
 * the field: set or unset.
For every combination, assert that the new path's effective replication equals 
the old client-side
path's. Keep a copy of `resolveClientSideReplicationConfig` in the test as the 
oracle.
 * Adapter (Mockito):
 * Existing bucket: exactly one `CreateFile` with both fields set, and no 
`getBucket()`.
 * Missing bucket with recursive: one failed call, then `getBucket(…, true)`, 
then a retry.
 * Missing bucket without recursive: the error propagates.
 * Older OMs (including `ZDU`) fall back.
 * OM request: with the field set, OBJECT_STORE is rejected *{*}without{*}* 
`allocateBlock` being called;
FSO and *{*}LEGACY{*}* succeed. Without the field, OBJECT_STORE still succeeds. 
A link to an EC bucket
resolves to EC.
 * `AbstractRootedOzoneFileSystemTest`:
 * create on an EC bucket writes EC;
 * create into a missing bucket auto-creates it;
 * create on an OBJECT_STORE bucket fails with `IllegalArgumentException`.
Count RPCs in the adapter test, not with OMMetrics deltas (HDDS-16513).
 * Benchmark `TestOfsCreateRpcBenchmark`, same shape as 
`TestOfsListStatusRpcBenchmark`:
 * 200 FSO buckets; create + close of empty files, single-threaded and 10 
threads;
 * n=3 per arm, built and run strictly one after the other;
 * invariant: one fewer OM RPC per `create` (CreateFile + CommitKey; no 
InfoBucket).

  was:
`BasicRootedOzoneClientAdapterImpl.createFile` and `createStreamFile` (behind 
`FileSystem.create` / `createNonRecursive`) call `getBucket(ofsPath, 
recursive)` and then `OzoneBucket.createFile`. So every
`create` sends at least 2 OM requests: an `InfoBucket` followed by a 
`CreateFile`, and a link bucket adds an `InfoVolume` and an `InfoBucket` for 
each hop. On write-heavy workloads such as ingest, ETL output and Hive inserts, 
`create` is the hottest write metadata operation.

The fetch does three jobs:

1. **Layout check.** `OzoneFSUtils.validateBucketLayout` rejects OBJECT_STORE. 
`OMFileCreateRequest` is registered for `BucketLayout.OBJECT_STORE`, so OM does 
not reject it.
2. **Replication.** The adapter passes `bucket.getReplicationConfig()` to
`OzoneClientUtils.resolveClientSideReplicationConfig`. **If the bucket default 
is EC, the bucket wins**, because "there is no way for file system interfaces 
to pass EC replication". Otherwise the FS-supplied factor wins, as RATIS or as 
the client-configured type adjusted to that factor.
3. **Auto-create.** With `recursive=true` (always set by `CopyCommands`), a 
missing volume or bucket is created.


### Proposal: opt-in fields that let OM apply the file system rule

{code}
message CreateFileRequest {
required KeyArgs keyArgs = 1;
required bool isRecursive = 2;
required bool isOverwrite = 3;
optional uint64 clientID = 4;
// Set by file system clients: OM rejects the request with
// NOT_SUPPORTED_OPERATION if the resolved bucket layout is OBJECT_STORE.
optional bool requireFileSystemLayout = 5;
// Set by file system clients, which cannot express EC replication: if the
// bucket default replication is EC, OM uses it instead of the replication
// in keyArgs.
optional bool preferBucketEcReplication = 6;
}
{code}

The second field reproduces the client rule exactly. The non-EC branch of
`resolveClientSideReplicationConfig` does not depend on the bucket. So the 
client can compute it with
`bucketReplConfig = null` and send the result. Then:

* if the field is set and the bucket default is EC, OM uses the bucket default;
* otherwise OM uses the client value. If the client sent nothing, OM falls back 
to the bucket default,
then the server default, through the existing logic.

That is the same decision the client makes today, with the inputs on the other 
side of the wire.

*OM.* `OMFileCreateRequest.preExecute` already reads `bucketInfo` 
(`ozoneManager.getBucketInfo`) to feed
`resolveReplicationConfigPreference`, before it calls `allocateBlock`. Both 
checks go there, on the
leader, before Ratis, and **before `allocateBlock`**. Otherwise a rejected 
`create` would still allocate
SCM blocks. Things to settle in review:
* whether `getBucketInfo` returns the *source* bucket's default replication for 
a link bucket (UNVERIFIED);
* whether the layout check can use the factory's already-resolved layout 
instead of re-deriving it.
This adds no extra lookup either way. Do not put either check in
`BucketLayoutAwareOMKeyRequestFactory.createRequest`, because that also runs at 
apply time on every OM.

*Client.*
* When `proxy.getOmVersion() >=` a new `OzoneManagerVersion` constant, the 
adapter sends `CreateFile` with
both fields set and does not call `getBucket()` first. Place the constant after
`LIST_STATUS_REJECTS_OBS(101)` and never below `ZDU(100)`; the number is 
assigned at merge. Against any
older OM, including ZDU, keep today's path.
* `RpcClient.createFile` builds the output stream from the replication returned 
by the server. Check
that nothing between `createFile` and the stream relies on the client-side 
value. `RpcClient` rejects
EC client-side for OM < 2; that is irrelevant here, because such an OM takes 
the fallback.
* Auto-create on the error path, as in the mkdirs Jira. On `VOLUME_NOT_FOUND` / 
`BUCKET_NOT_FOUND` with
`recursive=true`, call `getBucket(ofsPath, true)` and retry once, using today's 
replication path
because the bucket is now in hand. With `recursive=false`, propagate as today.
* Map `NOT_SUPPORTED_OPERATION` to `IllegalArgumentException`. 
`FILE_ALREADY_EXISTS` / `NOT_A_FILE` keep
mapping to `FileAlreadyExistsException`.

### Other callers

`CreateFile` has no S3 Gateway caller (S3 uses `CreateKey`). `freon 
OmMetadataGenerator` calls it on any
layout. Neither sets the new fields, so their behavior is unchanged.

### Compatibility

| | Behavior |
|---|---|
| New client, new OM, bucket exists | 1 RPC; OBJECT_STORE rejected; EC bucket ⇒ 
EC, as today |
| New client, new OM, bucket missing (recursive) | failed `CreateFile` → 
`getBucket(…, true)` → `CreateFile` |
| New client, older OM (incl. ZDU) | as today |
| Old client / freon / direct `createFile`, new OM | fields absent ⇒ unchanged |

There are two new optional proto fields, no new result code and no new 
configuration.

## Testing

* **Replication decision table.** In both an OM request test and the adapter, 
cover these inputs:
* bucket default: EC, RATIS, or none;
* client-configured `ozone.replication`: unset, RATIS, or EC;
* FS factor: 1, 3, or unsupported;
* the field: set or unset.
For every combination, assert that the new path's effective replication equals 
the old client-side
path's. Keep a copy of `resolveClientSideReplicationConfig` in the test as the 
oracle.
* Adapter (Mockito):
* Existing bucket: exactly one `CreateFile` with both fields set, and no 
`getBucket()`.
* Missing bucket with recursive: one failed call, then `getBucket(…, true)`, 
then a retry.
* Missing bucket without recursive: the error propagates.
* Older OMs (including `ZDU`) fall back.
* OM request: with the field set, OBJECT_STORE is rejected **without** 
`allocateBlock` being called;
FSO and **LEGACY** succeed. Without the field, OBJECT_STORE still succeeds. A 
link to an EC bucket
resolves to EC.
* `AbstractRootedOzoneFileSystemTest`:
* create on an EC bucket writes EC;
* create into a missing bucket auto-creates it;
* create on an OBJECT_STORE bucket fails with `IllegalArgumentException`.
Count RPCs in the adapter test, not with OMMetrics deltas (HDDS-16513).
* Benchmark `TestOfsCreateRpcBenchmark`, same shape as 
`TestOfsListStatusRpcBenchmark`:
* 200 FSO buckets; create + close of empty files, single-threaded and 10 
threads;
* n=3 per arm, built and run strictly one after the other;
* invariant: one fewer OM RPC per `create` (CreateFile + CommitKey; no 
InfoBucket).


> Remove the per-call InfoBucket from rooted-ofs create by resolving file 
> system replication in OM
> ------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16953
>                 URL: https://issues.apache.org/jira/browse/HDDS-16953
>             Project: Apache Ozone
>          Issue Type: Improvement
>          Components: Ozone Client
>            Reporter: Andrey Yarovoy
>            Assignee: Andrey Yarovoy
>            Priority: Major
>
> `BasicRootedOzoneClientAdapterImpl.createFile` and `createStreamFile` (behind 
> `FileSystem.create` / `createNonRecursive`) call `getBucket(ofsPath, 
> recursive)` and then `OzoneBucket.createFile`. So every
> `create` sends at least 2 OM requests: an `InfoBucket` followed by a 
> `CreateFile`, and a link bucket adds an `InfoVolume` and an `InfoBucket` for 
> each hop. On write-heavy workloads such as ingest, ETL output and Hive 
> inserts, `create` is the hottest write metadata operation.
> The fetch does three jobs:
> 1. *{*}Layout check.{*}* `OzoneFSUtils.validateBucketLayout` rejects 
> OBJECT_STORE. `OMFileCreateRequest` is registered for 
> `BucketLayout.OBJECT_STORE`, so OM does not reject it.
> 2. *{*}Replication.{*}* The adapter passes `bucket.getReplicationConfig()` to
> `OzoneClientUtils.resolveClientSideReplicationConfig`. *{*}If the bucket 
> default is EC, the bucket wins{*}*, because "there is no way for file system 
> interfaces to pass EC replication". Otherwise the FS-supplied factor wins, as 
> RATIS or as the client-configured type adjusted to that factor.
> 3. *{*}Auto-create.{*}* With `recursive=true` (always set by `CopyCommands`), 
> a missing volume or bucket is created.
> *Proposal: opt-in fields that let OM apply the file system rule*
> {code:java}
> message CreateFileRequest {
> required KeyArgs keyArgs = 1;
> required bool isRecursive = 2;
> required bool isOverwrite = 3;
> optional uint64 clientID = 4;
> // Set by file system clients: OM rejects the request with
> // NOT_SUPPORTED_OPERATION if the resolved bucket layout is OBJECT_STORE.
> optional bool requireFileSystemLayout = 5;
> // Set by file system clients, which cannot express EC replication: if the
> // bucket default replication is EC, OM uses it instead of the replication
> // in keyArgs.
> optional bool preferBucketEcReplication = 6;
> }
> {code}
> The second field reproduces the client rule exactly. The non-EC branch of
> `resolveClientSideReplicationConfig` does not depend on the bucket. So the 
> client can compute it with
> `bucketReplConfig = null` and send the result. Then:
>  * if the field is set and the bucket default is EC, OM uses the bucket 
> default;
>  * otherwise OM uses the client value. If the client sent nothing, OM falls 
> back to the bucket default,
> then the server default, through the existing logic.
> That is the same decision the client makes today, with the inputs on the 
> other side of the wire.
> *OM.* `OMFileCreateRequest.preExecute` already reads `bucketInfo` 
> (`ozoneManager.getBucketInfo`) to feed
> `resolveReplicationConfigPreference`, before it calls `allocateBlock`. Both 
> checks go there, on the
> leader, before Ratis, and *{*}before `allocateBlock`{*}*. Otherwise a 
> rejected `create` would still allocate
> SCM blocks. Things to settle in review:
>  * whether `getBucketInfo` returns the *source* bucket's default replication 
> for a link bucket (UNVERIFIED);
>  * whether the layout check can use the factory's already-resolved layout 
> instead of re-deriving it.
> This adds no extra lookup either way. Do not put either check in
> `BucketLayoutAwareOMKeyRequestFactory.createRequest`, because that also runs 
> at apply time on every OM.
> *Client.*
>  * When `proxy.getOmVersion() >=` a new `OzoneManagerVersion` constant, the 
> adapter sends `CreateFile` with
> both fields set and does not call `getBucket()` first. Place the constant 
> after
> `LIST_STATUS_REJECTS_OBS(101)` and never below `ZDU(100)`; the number is 
> assigned at merge. Against any
> older OM, including ZDU, keep today's path.
>  * `RpcClient.createFile` builds the output stream from the replication 
> returned by the server. Check
> that nothing between `createFile` and the stream relies on the client-side 
> value. `RpcClient` rejects
> EC client-side for OM < 2; that is irrelevant here, because such an OM takes 
> the fallback.
>  * Auto-create on the error path, as in the mkdirs Jira. On 
> `VOLUME_NOT_FOUND` / `BUCKET_NOT_FOUND` with
> `recursive=true`, call `getBucket(ofsPath, true)` and retry once, using 
> today's replication path
> because the bucket is now in hand. With `recursive=false`, propagate as today.
>  * Map `NOT_SUPPORTED_OPERATION` to `IllegalArgumentException`. 
> `FILE_ALREADY_EXISTS` / `NOT_A_FILE` keep
> mapping to `FileAlreadyExistsException`.
> *Other callers*
> `CreateFile` has no S3 Gateway caller (S3 uses `CreateKey`). `freon 
> OmMetadataGenerator` calls it on any
> layout. Neither sets the new fields, so their behavior is unchanged.
> *Compatibility*
> | |Behavior|
> |—|—|
> |New client, new OM, bucket exists|1 RPC; OBJECT_STORE rejected; EC bucket ⇒ 
> EC, as today|
> |New client, new OM, bucket missing (recursive)|failed `CreateFile` → 
> `getBucket(…, true)` → `CreateFile`|
> |New client, older OM (incl. ZDU)|as today|
> |Old client / freon / direct `createFile`, new OM|fields absent ⇒ unchanged|
> There are two new optional proto fields, no new result code and no new 
> configuration.
> *Testing*
>  * *{*}Replication decision table.{*}* In both an OM request test and the 
> adapter, cover these inputs:
>  * bucket default: EC, RATIS, or none;
>  * client-configured `ozone.replication`: unset, RATIS, or EC;
>  * FS factor: 1, 3, or unsupported;
>  * the field: set or unset.
> For every combination, assert that the new path's effective replication 
> equals the old client-side
> path's. Keep a copy of `resolveClientSideReplicationConfig` in the test as 
> the oracle.
>  * Adapter (Mockito):
>  * Existing bucket: exactly one `CreateFile` with both fields set, and no 
> `getBucket()`.
>  * Missing bucket with recursive: one failed call, then `getBucket(…, true)`, 
> then a retry.
>  * Missing bucket without recursive: the error propagates.
>  * Older OMs (including `ZDU`) fall back.
>  * OM request: with the field set, OBJECT_STORE is rejected *{*}without{*}* 
> `allocateBlock` being called;
> FSO and *{*}LEGACY{*}* succeed. Without the field, OBJECT_STORE still 
> succeeds. A link to an EC bucket
> resolves to EC.
>  * `AbstractRootedOzoneFileSystemTest`:
>  * create on an EC bucket writes EC;
>  * create into a missing bucket auto-creates it;
>  * create on an OBJECT_STORE bucket fails with `IllegalArgumentException`.
> Count RPCs in the adapter test, not with OMMetrics deltas (HDDS-16513).
>  * Benchmark `TestOfsCreateRpcBenchmark`, same shape as 
> `TestOfsListStatusRpcBenchmark`:
>  * 200 FSO buckets; create + close of empty files, single-threaded and 10 
> threads;
>  * n=3 per arm, built and run strictly one after the other;
>  * invariant: one fewer OM RPC per `create` (CreateFile + CommitKey; no 
> InfoBucket).



--
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