[refactor](storage) Unify BE and Recycler object clients - #66350
[refactor](storage) Unify BE and Recycler object clients#66350sollhui wants to merge 21 commits into
Conversation
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
|
run buildall |
|
/review |
f30abce to
dc25d3e
Compare
|
Codex automated review failed and did not complete. Error: Codex completed, but no new pull request review was submitted for the current head SHA. Please inspect the workflow logs and rerun the review after the underlying issue is resolved. |
|
run buildall |
|
/review |
|
run buildall |
|
/review |
|
Codex automated review failed and did not complete. Error: Codex completed, but no new pull request review was submitted for the current head SHA. Please inspect the workflow logs and rerun the review after the underlying issue is resolved. |
|
Codex automated review failed and did not complete. Error: Review step was failure (possibly timeout or cancelled) Please inspect the workflow logs and rerun the review after the underlying issue is resolved. |
FE UT Coverage ReportIncrement line coverage |
1fab2ee to
4ea09ca
Compare
|
/review |
|
run buildall |
There was a problem hiding this comment.
Static-only review of the full authoritative diff found six issues that should be addressed before merge (five P1, one P2).
Review-cycle status: incomplete after the three-round cap. Both normal agents returned NO_NEW_VALUABLE_FINDINGS in Round 3, but the risk-focused agent found a final FE-side scope correction that was independently verified and merged into the token round-trip comment; the review contract does not permit a fourth round. All currently known candidates are nevertheless adjudicated and included below.
Critical checkpoint conclusions:
- Data correctness: failed. Session-token credentials are dropped/staled across FE DDL and meta-service paths, and Recycler exists status mapping can turn real provider failures into false not-found results.
- Concurrency and lifecycle: delete-task ownership, executor waiting, batch clamping, and error propagation are sound; request admission during recursive deletion is not.
- Configuration and dynamic behavior: Recycler rate limiting and PUT fault injection are bypassed for the actual recursive-delete SDK requests; AWS provider precedence, refresh-capable providers, and client cache identity otherwise remain compatible.
- Compatibility and rolling behavior: the optional protobuf field is wire-compatible, but the Recycler 0/1/negative adapter contract and GCS iterator migration are broken.
- Parallel paths: BE/Recycler and S3/Azure/GCS paths were traced; the GCS path has an unconditional compile failure and Recycler differs from the preserved BE admission behavior.
- Tests and validation: no builds or tests were run, as required by the review prompt. Existing S3 accessor tests still require 1 for not-found, and there is no end-to-end token persistence/redaction/rotation coverage; the GCS compile error is statically evident.
- Observability and security: the session token lacks SK-equivalent encryption/log/display handling, and successful S3 writes now log at INFO on the hot path. This is credential-secret handling within authenticated control paths; no unsupported cross-tenant vulnerability claim is made.
- Persistence and recovery: token-bearing vault/stage records can either lose the token or retain it plaintext, so persistence round trips are not safe.
- Performance: recursive deletion can evade Recycler request controls, while per-write INFO logging adds log I/O proportional to storage QPS; page and provider batch limits themselves are sound.
User focus: review_focus.txt supplied no additional focus, so the entire PR was reviewed without narrowing scope.
| optional CredProviderTypePB cred_provider_type = 17; | ||
| optional string role_arn = 18; // aws assumed role's arn | ||
| optional string external_id = 19; // aws assumed role's external_id if configure | ||
| optional string token = 20; // optional session token paired with ak/sk |
There was a problem hiding this comment.
[P1] Give the token the same secret lifecycle as SK
This field is currently left plaintext by create_object_info_with_encrypt and external-stage creation while SK is encrypted. It can then be emitted by full-instance INFO logs that do not call hide_token, returned by the list-all external-stage display path, and serialized by SHOW STORAGE VAULT for any USAGE grantee because that converter masks only SK. Please encrypt/decrypt the token like other credential secrets and explicitly strip or mask it from every log and display response.
| *res = std::make_unique<S3ListIterator>( | ||
| obj_client_->list_objects({.bucket = conf_.bucket, .key = get_key(path_prefix)}), | ||
| prefix_length); | ||
| obj_client_, |
There was a problem hiding this comment.
[P1] Preserve Recycler's exists return contract
The common response uses positive Thrift codes (NOT_FOUND is 31, while internal/auth errors are also positive), but StorageVaultAccessor::exists promises 0 for present, 1 for absent, and a negative value for errors. checker.cpp consequently treats an S3 permission or server failure as a missing file, and the existing accessor tests still require 1 for a 404. Map OK to 0, NOT_FOUND to 1, and every other status to a negative error at this adapter boundary.
There was a problem hiding this comment.
Anchor correction: the affected S3Accessor::exists return is the new .resp.status.code at line 600.
| // BE behavior of charging it as one logical PUT call in this refactor. Move recursive | ||
| // orchestration above ObjStorageClient in a follow-up, using the one-page list API and a | ||
| // one-batch delete API so every client call maps to one admission and one SDK request. | ||
| auto rate_limit = acquire(ObjStorageRequestType::PUT); |
There was a problem hiding this comment.
[P1] Keep Recycler recursive deletes under request-level admission
After this single PUT admission, every list page and delete batch calls the provider directly, so RecyclerObjStorageRateLimitPolicy is never invoked for the actual SDK requests. The deleted Recycler implementations admitted every GET page and PUT batch; a large prefix can now issue unbounded pages and concurrent deletes after one token and one fault-injection decision. Route each page/batch through the policy-bearing facade, or make BE's one-logical-operation behavior an explicit BE-only mode.
| .request_id = outcome.GetError().GetRequestId()}; | ||
| } | ||
|
|
||
| LOG(INFO) << "request_id = " << request_id << ", bucket = " << opts.bucket |
There was a problem hiding this comment.
[P2] Avoid INFO logging every successful object write
This unconditional success log turns normal PutObject traffic into one INFO record per object; create and complete multipart have the same pattern. The deleted BE implementation logged these only when they exceeded the 5-second threshold, and Recycler logged PutObject failures only, so ingest/file-cache traffic can now generate log I/O proportional to storage QPS. Restore slow-request-only logging or lower these success messages to VLOG while keeping latency bvars for aggregate observability.
| } | ||
|
|
||
| int S3Accessor::get_life_cycle(int64_t* expiration_days) { | ||
| return obj_client_->get_life_cycle(conf_.bucket, expiration_days).ret; |
There was a problem hiding this comment.
[P1] Finish converting the GCS iterator to value semantics
This declaration changes iter from a pointer to a stack ObjectListIterator, and the new loop correctly calls iter.next(), but the post-loop check at line 678 still calls iter->is_valid(). The common iterator defines no operator->, so the Cloud target cannot compile regardless of which provider is selected at runtime. Change that remaining check to iter.is_valid().
There was a problem hiding this comment.
Anchor correction: the value-semantic iterator is declared at line 636, and the stale pointer-style use is at line 678.
FE UT Coverage ReportIncrement line coverage |
Move the shared S3/Azure clients, credential factories, pagination, and recursive deletion orchestration into common/client. Keep BE and Recycler policies in runtime hooks and adapters. Reuse the CPU-aware limiter manager and token-bucket fixes needed from apache#65420, but apply admission before each real provider SDK request instead of wrapping logical operations. Test: Not run (per request).
Issue Number: None Related PR: None Problem Summary: Move rate limiting out of the S3 and Azure provider clients into one common logical-call decorator used by both BE and Recycler. Lazy list iteration now fetches at most 1000 objects per page and charges once per fetched page. Batch deletion is split by each provider's request limit before admission, so one charged batch maps to one SDK request. Recursive deletion intentionally keeps the previous BE one-call accounting and records a TODO for a follow-up change. None - Test: Not run at the user's request; clang-format and git diff checks passed - Behavior changed: Yes, BE and Recycler now share logical-call rate-limiting semantics - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: None Problem Summary: The standalone rate-limiting decorator and provider implementations shared the same client interface, so a raw provider could be used where the complete object-storage client was expected and bypass runtime policy. Refactor the shared object-storage layer into an ObjStorageClient facade composed from an environment-specific ObjStorageRateLimitPolicy and an S3 or Azure ObjStorageProvider. Keep pagination, provider-sized delete batching, credential handling, and the existing recursive-delete admission behavior unchanged. ### Release note None ### Check List (For Author) - Test: Not run per request; clang-format 16, stale-reference checks, and git diff --check passed - Behavior changed: No - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: None Problem Summary: The shared list API returned a lazy provider iterator, so one ObjStorageClient list call could issue multiple SDK requests. Change the Client and Provider contract to return one fixed-size ObjectStorageListPage per call, and move continuation-token iteration above ObjStorageClient. Each Client list call now corresponds to one rate-limit admission and one SDK request while BE and Recycler retain lazy consumption. Recursive delete keeps its existing single logical PUT admission and remains a follow-up item for request-level accounting. ### Release note None ### Check List (For Author) - Test: Not run per request; clang-format 16, stale-reference checks, and git diff --check passed - Behavior changed: No (internal list pagination and admission boundaries only) - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: None Problem Summary: Direct batch deletion and recursive deletion can each issue multiple SDK requests from one public ObjStorageClient call. Record follow-up work to move batching and recursive orchestration above the facade, so a future one-batch delete API can map each Client call to one rate-limit admission and one SDK request. ### Release note None ### Check List (For Author) - Test: Not run per request; clang-format 16 and git diff --check passed - Behavior changed: No - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: None Problem Summary: ObjStorageClient is the complete facade used by upper layers, while the S3 and Azure implementations only supply storage mechanics behind that facade. Rename ObjStorageProvider and its implementations to ObjStorageBackend, S3ObjStorageBackend, and AzureObjStorageBackend so the type names express those architectural roles and do not imply that a raw backend is a complete client. Update file names, factories, build references, synchronization points, and tests consistently. ### Release note None ### Check List (For Author) - Test: Not run per request; clang-format 16, stale-name checks, and git diff --check passed - Behavior changed: No - Does this need documentation: No
92a3812 to
3f0e01e
Compare
### What problem does this PR solve? Issue Number: N/A Related PR: None Problem Summary: S3ClientFactory and Recycler credential helpers reduced AwsCredentialFactory failures to a null shared pointer after logging internally, so callers could not inspect the failure reason and Kinesis could attempt construction with an empty provider. Return AwsCredentialResult from a single version-aware helper, handle its error at each client-construction boundary, and pass the provider to the SDK only after successful creation. This also removes duplicated v1/v2 helper implementations while preserving their credential compatibility behavior. ### Release note None ### Check List (For Author) - Test: Not run per request; added credential error propagation coverage, and clang-format 16 plus git diff --check passed - Behavior changed: Yes (invalid credential-provider construction now returns its explicit error at the caller boundary) - Does this need documentation: No
|
run buildall |
1. What does this PR do?
BE and Cloud Recycler previously maintained separate object-storage abstractions and separate S3/Azure implementations. Although both sides called the same cloud-provider SDKs, credential construction, error conversion, metrics, pagination, batch deletion, and compatibility behavior were duplicated and could evolve differently.
This PR consolidates the implementation under
common/cpp/clientand exposes oneObjStorageClientfacade to upper layers:ObjStorageClientowns backend-independent orchestration and is the only complete client used by BE and Recycler call sites.ObjStorageRateLimitPolicykeeps BE- and Recycler-specific admission behavior injectable without coupling common code to either environment.ObjStorageBackendis the storage implementation boundary, implemented byS3ObjStorageBackendandAzureObjStorageBackend.ObjectStoreInfoPBcarries the optional AWS session token through the same credential path; token values are redacted from logs and debug output.Before the refactor, BE and Recycler reached the cloud SDKs through parallel stacks:
After the refactor, BE and Recycler stay on the left and right while the shared facade and backend components are centered below them:
This boundary prevents a raw backend from being used as the complete client and accidentally bypassing runtime policy. Backend code only implements cloud mechanics; common orchestration and policy dispatch remain in the facade.
2. How are the different behaviors unified?
doris::io::ObjStorageClientand eager list resultsdoris::ObjStorageClientfacade and one set of request/response types.doris::ioaliases keep BE call sites source-compatible; Recycler adapters preserve its integer-facing API.S3ObjStorageBackendandAzureObjStorageBackendare shared by both callers.ObjStorageRateLimitPolicy; the facade performs admission before dispatching backend work. Backend implementations do not depend on BE or Recycler configuration.ObjectStorageResponseconsistently carries a Doris status code, HTTP code, and request ID, while adapters preserve caller-facing behavior.ObjStorageClient::list_objectsreturns one fixed-sizeObjectStorageListPage. One Client call performs one admission, one Backend call, and one SDK request. The upperObjectListIteratorowns the continuation token and requests the next page only after its cached page is consumed.END_OF_FILEis an internal upper-iterator sentinel andnext()converts it to a successful empty result. BackendNOT_FOUNDremains a real error.NoSuchKeyhandling existed in the BE pathNoSuchKey-as-empty behavior once for both callers.1000for S3,256for Azure), performs one admission per backend-sized batch, and backends keep defensive bounds.AwsCredentialFactoryimplements static AK/SK/session-token credentials, provider chains, role ARN, and external ID once. Callers explicitly retain their prior empty-credential behavior.AzureAuthFactorycreates the container client and shared-key credential for both; BE TLS diagnostic context remains attached to Azure errors.S3ClientConfhad a token, but the storage-vault protobuf path did notObjectStoreInfoPBObjectStoreInfoPB -> S3Conf -> AwsCredentialFactorycarries AK/SK/token consistently, and token values are cleared or masked before logging.client_bvar::ScopedLatencytimer and common failure metrics. Public stopwatch utilities are unchanged.3. Design boundaries and follow-ups
std::shared_ptr<ObjStorageClient>; they do not storeObjStorageBackenddirectly.ObjectListIteratorperforms lazy iteration by repeatedly calling the one-page Client API. Therefore each requested page has exactly one facade admission and one SDK request; reading objects already cached in that page performs no network request.delete_objectscall may still split into multiple backend-sized SDK requests. A follow-up will move batching above the facade and introduce a one-batch Client API, so each Client call maps to one admission and one SDK request.git diff --check.