Skip to content

[refactor](be) Build object storage clients from typed, per-backend configuration - #69605

Open
xylaaaaa wants to merge 21 commits into
apache:masterfrom
xylaaaaa:refactor/be-obj-client-config
Open

xylaaaaa wants to merge 21 commits into
apache:masterfrom
xylaaaaa:refactor/be-obj-client-config

Conversation

@xylaaaaa

@xylaaaaa xylaaaaa commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #69082

Problem Summary: Every BE object storage entry (FE properties, cold-storage resources, storage vaults) produced one flat S3Conf / S3ClientConf that carried all providers in a single shape: Azure reused ak/sk/bucket as account/key/container, while region, role_arn and the credential provider type only meant something for S3. S3ClientFactory::create then branched on the provider to build an S3 or an Azure client, and ObjClientHolder merged credential refreshes field by field into that flat struct. A backend-specific field could leak into the other backend's identity or validation (for example, an Azure configuration still needed a region), and adding an Azure authentication method would have meant more flat fields that S3 must ignore.

This is the first step of splitting #69082 (BE object storage refactor and native Azure SAS / OAuth2 access). It only restructures the BE; no FE change, no Thrift change, and Azure still supports only SharedKey.

  • Typed configuration (io/obj_storage/obj_storage_config.{h,cpp}). io::ObjClientConfig holds the shared connection options, the client usage (internal storage / external access) and exactly one backend, std::variant<S3ClientConfig, AzureClientConfig>. The Azure authentication is the AzureAuthConfig shared with Recycler (see below). io::ObjStorageConfig (bucket, prefix, client) replaces S3Conf; S3Conf and S3ClientConf are removed. The rules of the typed configuration live in this layer, written per backend and per authentication through std::visit, so a missing branch fails to compile: validate_obj_client_config (whether a configuration is valid) and merge_obj_client_config (how a refreshed resource or vault configuration merges into the current one, with the same field rules as the flat merge).
  • Conversion of the flat sources. ObjStorageConfigResolver::from_properties (formerly S3ClientFactory::convert_properties_to_s3_conf), from_storage_param and from_object_store_info (formerly S3Conf::get_s3_conf) turn FE properties, TS3StorageParam and ObjectStoreInfoPB into the typed configuration, and all three return Result. The flat field mapping is ported unchanged. A provider-native credential next to provider AZURE, which only the S3 backend has a field for, is rejected with InvalidArgument for all three sources. The resolver keeps only the rule of its own input (FE properties need AWS_REGION) and otherwise relies on the config layer. AI embed, Kinesis and the file cache microbench build the typed configuration themselves.
  • Client construction by backend. ObjClientFactory validates every request with validate_obj_client_config (the checks S3ClientFactory::create applied), owns the test hook set_client_creator_for_test, and dispatches through std::visit. S3ClientProvider and AzureClientProvider build and cache their clients outside the cache lock. The BE rate limit policy is its own module (obj_client_rate_limit) shared by both providers. S3ClientFactory keeps only the AWS SDK lifetime and the AWS helpers (client configuration, credentials provider, CA path), which Kinesis also uses.
  • Azure authentication shared with Recycler. BE and Recycler already built Azure clients through the common AzureAuthFactory, but BE kept its own authentication variant and converted it back into the flat AzureCredentialOptions (a type enum plus fields). The model now lives once in common, next to the GCP credential, under cpp/obj-client/auth/azure/: azure_auth.h (SDK-free, always built) defines AzureSharedKeyConfig and the AzureAuthConfig variant, with the rules every method shares (validate_azure_auth for required fields, hash_combine_azure_auth, azure_auth_type_name without secrets). AzureAuthFactory takes AzureAuthConfig directly, with one std::visit branch per method, and AzureCredentialType / AzureCredentialOptions are removed; transport, CA, retries and policies still come from each process through BlobClientOptions. The storage account name stays outside the variant, because it names the storage target. BE's AzureClientConfig holds the common config, BE has no Azure authentication logic of its own, and BE checks its inputs with validate_azure_auth. Recycler builds an AzureSharedKeyConfig from its storage metadata and passes it to the same factory; it does not call the check, because S3Conf::from_obj_store_info loads the ak/sk pair complete or not at all (the meta service requires the pair when an Azure entry is created, but not on every later update), so Recycler builds the client exactly as before. A new method (SAS, OAuth2, Managed Identity) is then added once in common, and each entry only adapts its input or rejects the method.
  • Holder. ObjClientHolder moves to io/obj_storage/obj_client_holder.{h,cpp} and owns an immutable ObjClientState (config, client, generation) that it replaces as a whole, so a caller that reads both from one state() snapshot never pairs a new config with an old client. A reset merges with merge_obj_client_config and builds through ObjClientFactory.
  • Locations. ObjStorageUri::parse_s3 replaces S3URI with the same rules. FileFactory resolves FILE_S3 locations through one function for the reader, create_fs and create_file_writer.
  • GCP IAM credentials ([feature](storage) Support native GCP IAM authentication #68540) move with the S3 fields: S3AuthConfig::credential carries the provider-native CredentialConfig, the resolver parses and converts it for all three sources, ObjClientFactory validates it on every request, and S3ClientProvider builds the client through make_s3_client with the GCS V4 signed URL generator. A reset takes the vendor and credential from the update, as the flat merge does.
  • The AWS SDK RFC 3986 encoding flag is set through SDKOptions::httpOptions at SDK initialization instead of in every reader and writer constructor. The reader no longer uses AWS types for HTTP 429.

Behavior changes:

  1. A holder reset that overlaps another one returns Aborted instead of publishing on top of a state it was not merged with, so overlapping resets cannot both win; the caller retries with its next refresh. The check detects overlap, not freshness: a reset carrying an older configuration can still win, as before, and the next refresh replaces it.
  2. ObjClientHolder::reset no longer accepts another backend (InvalidArgument), since its merge keeps the storage target. FE still allows ALTER RESOURCE to change the provider of a cold-storage resource, also under a storage policy, and S3FileSystem::refresh_resource handles that: while the bucket and prefix stay, it rebinds the existing holder to a client built from the new configuration (ObjClientHolder::rebind), and a rowset that already resolved the file system follows, including after a revert or a key rotation. This matches the merge base under a storage policy, where FE freezes the endpoint, region, bucket and root path: reads fail on access and recover when the change is reverted. Without a storage policy the new configuration applies as it is, the same result as after a restart, where the merge base kept the old endpoint and failed; a moved bucket or prefix builds a new file system with the same id. Each change of backend logs a WARNING with the resource id and both configurations, masked. It is not a supported way to move a resource; rejecting it in FE is a separate follow-up.
  3. The client factory no longer requires a region for an Azure configuration, so Azure configurations converted from TS3StorageParam / ObjectStoreInfoPB metadata no longer need one. Property conversion (from_properties) keeps its existing AWS_REGION requirement. The Azure account name and key are checked as a pair without the role_arn exemption of the flat ak/sk rule, because Azure has no role authentication; such a configuration could not authenticate before either, and now fails when the client is built.
  4. A storage vault cannot change its backend through ALTER, but the meta service can hand the id of a dropped vault to a new one (drop_hdfs_vault accepts an object vault, and a new id is the largest existing one plus one). When a vault sync finds another backend under an id, S3FileSystem::refresh_vault takes the holder out of service for good (ObjClientHolder::invalidate): readers, writers and PackedFileManager, which all share it, fail until a BE restart builds the new vault's file system, as at the merge base, instead of writing into the dropped vault's bucket. Limits: a call already sent is not recalled, so a PutObject that took the client may still succeed; until the next vault sync (60s by default) a BE cannot know the id was reused; and a reused id on the same backend, or between HDFS and S3, is not detected. Those need the fix in the meta service, which is separate from this PR.

Release note

None

Check List (For Author)

  • Test: Unit Test (new obj_client_factory_test and obj_storage_uri_test, the latter porting the S3URI cases; existing users, including the GCP IAM cases of s3_client_factory_test, migrated, with test configs built through testutil/obj_storage_test_util.h). Not run locally; run in CI.
  • Behavior changed: Yes, as listed above
  • Does this need documentation: No

@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@xylaaaaa

Copy link
Copy Markdown
Contributor Author

run buildall

2 similar comments
@shuke987

Copy link
Copy Markdown
Contributor

run buildall

@xylaaaaa

Copy link
Copy Markdown
Contributor Author

run buildall

@xylaaaaa
xylaaaaa marked this pull request as ready for review October 26, 2026 07:11
Copilot AI balanced review requested due to automatic review settings October 26, 2026 07:11

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 insufficient remaining quota.

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28494 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 719de6ac81f0abe4889f8b43d8752edc4f927ebd, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17576	5093	5284	5093
q2	2141	320	269	269
q3	10196	1396	818	818
q4	4688	465	339	339
q5	7449	804	530	530
q6	189	207	152	152
q7	767	802	610	610
q8	9252	1322	1130	1130
q9	5549	4188	4188	4188
q10	6841	1360	1013	1013
q11	427	244	229	229
q12	634	407	283	283
q13	18098	3669	3006	3006
q14	277	282	239	239
q15	q16	736	706	657	657
q17	1217	750	583	583
q18	7013	6215	6119	6119
q19	1164	969	636	636
q20	373	335	222	222
q21	5253	2123	2316	2123
q22	357	297	255	255
Total cold run time: 100197 ms
Total hot run time: 28494 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5614	5617	5600	5600
q2	697	597	537	537
q3	5227	5417	4885	4885
q4	1981	2135	1542	1542
q5	5303	5127	5711	5127
q6	257	199	153	153
q7	2330	2109	1921	1921
q8	3102	2901	2609	2609
q9	8064	7953	8009	7953
q10	3734	3676	3251	3251
q11	612	448	405	405
q12	697	724	524	524
q13	3553	3907	3233	3233
q14	317	309	294	294
q15	q16	689	708	632	632
q17	8214	7492	7279	7279
q18	12942	12185	12893	12185
q19	900	774	782	774
q20	2134	2142	1974	1974
q21	5525	4747	4639	4639
q22	477	467	438	438
Total cold run time: 72369 ms
Total hot run time: 65955 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 151321 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 719de6ac81f0abe4889f8b43d8752edc4f927ebd, data reload: false

query5	4311	607	455	455
query6	436	211	193	193
query7	4800	440	241	241
query8	321	165	151	151
query9	8796	3113	3132	3113
query10	487	303	262	262
query11	5987	3856	3570	3570
query12	144	91	86	86
query13	1266	459	351	351
query14	6129	3947	3646	3646
query14_1	3433	3394	3450	3394
query15	208	208	184	184
query16	957	441	411	411
query17	855	666	538	538
query18	2416	421	308	308
query19	195	166	138	138
query20	86	85	85	85
query21	215	128	107	107
query22	13010	12950	12828	12828
query23	13456	12767	12377	12377
query23_1	12503	12571	12558	12558
query24	7377	838	476	476
query24_1	478	482	490	482
query25	571	393	355	355
query26	1261	257	137	137
query27	2770	430	273	273
query28	4613	1943	1940	1940
query29	1524	577	422	422
query30	299	216	186	186
query31	1004	908	822	822
query32	143	97	90	90
query33	516	282	235	235
query34	906	835	486	486
query35	726	744	634	634
query36	819	793	754	754
query37	128	98	81	81
query38	1796	1718	1649	1649
query39	801	803	747	747
query39_1	727	728	738	728
query40	212	113	91	91
query41	66	65	65	65
query42	86	83	84	83
query43	341	354	307	307
query44	1328	693	697	693
query45	173	185	175	175
query46	836	955	559	559
query47	2707	3025	2813	2813
query48	291	305	211	211
query49	595	394	290	290
query50	693	276	212	212
query51	10712	10699	10579	10579
query52	81	82	70	70
query53	190	210	154	154
query54	258	190	178	178
query55	70	65	70	65
query56	250	236	212	212
query57	1657	1607	1553	1553
query58	280	274	259	259
query59	2158	2157	1999	1999
query60	297	225	222	222
query61	172	146	147	146
query62	383	327	282	282
query63	185	155	160	155
query64	2720	934	741	741
query65	3324	3253	3315	3253
query66	1768	409	301	301
query67	20213	20119	20098	20098
query68	3005	1006	605	605
query69	391	285	241	241
query70	834	772	768	768
query71	293	239	238	238
query72	2686	2589	2433	2433
query73	518	522	305	305
query74	4645	4490	4351	4351
query75	2341	2289	1912	1912
query76	2336	1003	601	601
query77	352	375	269	269
query78	9732	9687	9220	9220
query79	982	861	530	530
query80	562	452	362	362
query81	536	324	274	274
query82	841	137	109	109
query83	363	194	182	182
query84	311	123	109	109
query85	822	422	373	373
query86	323	236	232	232
query87	1997	1934	1808	1808
query88	3655	2665	2658	2658
query89	329	281	235	235
query90	1890	168	174	168
query91	164	155	127	127
query92	99	85	82	82
query93	999	1011	599	599
query94	445	297	269	269
query95	582	331	314	314
query96	661	480	234	234
query97	2435	2426	2310	2310
query98	162	171	149	149
query99	765	766	638	638
Total cold run time: 234342 ms
Total hot run time: 151321 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.36 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 719de6ac81f0abe4889f8b43d8752edc4f927ebd, data reload: false

query1	0.01	0.00	0.01
query2	0.09	0.05	0.05
query3	0.26	0.09	0.09
query4	1.61	0.12	0.08
query5	0.23	0.24	0.24
query6	1.18	0.68	0.68
query7	0.04	0.00	0.00
query8	0.06	0.04	0.04
query9	0.39	0.35	0.35
query10	0.61	0.60	0.62
query11	0.19	0.14	0.14
query12	0.18	0.15	0.15
query13	0.51	0.49	0.50
query14	1.04	1.01	1.01
query15	0.64	0.61	0.60
query16	0.33	0.31	0.34
query17	1.13	1.16	1.12
query18	0.21	0.20	0.19
query19	2.12	2.02	1.94
query20	0.01	0.01	0.02
query21	15.49	0.21	0.14
query22	4.81	0.06	0.05
query23	16.26	0.32	0.12
query24	2.91	0.58	0.40
query25	0.10	0.06	0.05
query26	0.86	0.22	0.15
query27	0.06	0.05	0.03
query28	2.75	0.75	0.36
query29	12.50	4.13	3.21
query30	0.29	0.13	0.10
query31	2.77	0.56	0.33
query32	3.22	0.60	0.51
query33	3.19	3.33	3.32
query34	15.43	3.94	3.38
query35	3.35	3.34	3.36
query36	0.55	0.45	0.42
query37	0.10	0.07	0.06
query38	0.05	0.04	0.04
query39	0.03	0.03	0.03
query40	0.18	0.15	0.14
query41	0.09	0.03	0.02
query42	0.03	0.04	0.04
query43	0.04	0.03	0.04
Total cold run time: 95.9 s
Total hot run time: 24.36 s

@morningman

Copy link
Copy Markdown
Contributor

Review of commit 719de6ac81f0abe4889f8b43d8752edc4f927ebd: REQUEST_CHANGES (1 Blocker, 1 Minor).

F-01 · Blocker: the new holder tests call an undefined helper

At obj_client_factory_test.cpp:281-287, and again at line 314, the tests call secret_key_of(...), but the helper has no declaration or definition. Repository symbol search finds only these three calls. The recursive source collection in be/test/CMakeLists.txt includes this file in the normal BE UT target, so this causes a compilation failure before any test can run.

Please add the missing helper in the test's anonymous namespace, or access the typed S3 configuration directly. For example:

const std::string& secret_key_of(const ObjClientConfig& config) {
    return std::get<S3ClientConfig>(config.backend).auth.secret_key;
}

This is a functional-bug regression: the merge base contains neither the new test file nor the unresolved calls. Please build and run the affected BE unit tests after fixing it.

F-02 · Minor: narrow the Azure region-removal claim in the PR description

Behavior-change item 4 suggests that a flat Azure configuration no longer needs a region. However, ObjStorageConfigResolver::from_properties still rejects an empty region before selecting the Azure backend. The new test at lines 386-388 explicitly expects an Azure property map without AWS_REGION to fail.

This preserves the existing property-path behavior, so it is a wording issue rather than a runtime regression. Please limit the description to the paths that implement the relaxation, for example:

The typed Azure client factory no longer requires a region. Azure configurations converted from Thrift/PB metadata inherit this relaxation; property conversion retains its existing AWS_REGION requirement.

Validation: source review covered all 58 changed files and converged after two rounds. No builds or tests were run locally; the compilation finding is based on symbol lookup and the CMake source-collection rules.

@morningman

Copy link
Copy Markdown
Contributor

Architecture follow-up for commit 719de6ac81f0abe4889f8b43d8752edc4f927ebd. The typed configuration, centralized input conversion, and holder snapshots improve the structure. The following suggestions focus on making the result clearer, simpler, and easier to extend.

Main relationships after the refactor

properties / Thrift / PB
          |
       Resolver
          |
   ObjStorageConfig
     +-- bucket / prefix: storage target
     +-- ObjClientConfig: configuration needed to create a client
                |
             Factory
                |
        S3 / Azure Provider
                |
           Cache / SDK
                |
        ObjStorageClient

Configuration refresh -> merge update -> Holder publishes configuration and client together

1. Consolidate the common creation flow and keep providers focused on SDK construction.

The S3 provider and Azure provider both follow essentially the same sequence:

cache lookup -> build -> rate-limit wrapping -> cache publication

They also call the factory's rate-limit helper, creating a dependency that goes Factory -> Provider -> Factory.

Consider having the factory own the common creation flow and cache, while each provider only builds its backend client. The existing cache key already includes the backend, so it can distinguish clients for different backends. This would reduce duplication and make the dependency direction more direct.

The tradeoff is sharing one short cache-lock critical section across the two backends. If separate caches are preferable, extracting a small common creation helper would still remove the duplicated sequence. A complex provider inheritance hierarchy is unnecessary here.

2. Keep one implementation of typed configuration validation.

The resolver's validate_flat_fields and the factory's S3 validation repeat checks for the endpoint, region, credential combinations, and access/secret keys.

It would help to distinguish:

  • Input handling: parsing strings, extracting URI/bucket information, and preserving legacy properties requirements.
  • Configuration semantics: whether a typed S3ClientConfig or AzureClientConfig is valid.

The second group should have one implementation, always called by the factory and reused by the resolver when early validation is needed. Adding an authentication mode would then be less likely to leave two sets of rules inconsistent.

Preserve existing compatibility rules during this cleanup. For example, Azure properties still require a region today; reorganizing validation should not silently change that behavior.

3. Extend compile-time variant checking to configuration merging and authentication.

The factory's backend dispatch already uses std::visit, which is useful. Some other paths still assume there are only two alternatives:

Adding a third backend or an Azure OAuth alternative would not necessarily expose every missing case at compile time. Some omissions could instead become a runtime std::bad_variant_access.

Consider explicit per-alternative visitor overloads for these paths too. Pure configuration merging could also live in the configuration layer, so the holder's refresh logic does not need to depend on the input resolver. Both changes would make extension points more explicit.

4. Make the scope of shared connection options match their actual effect.

ObjStorageConnectionOptions appears to apply to every backend. However, Azure construction receives only AzureClientConfig: the three connection options currently have no effect there, while still participating in cache identity.

This inherits the old behavior and is not a new regression. The new model nevertheless gives callers a broader impression of support than the implementation provides.

A small first step would be to document that these options currently apply only to S3. A later change could move them into S3 configuration or implement explicit Azure equivalents. Adding Azure support should be treated as a behavior change with corresponding tests.

5. As a lower-priority cleanup, align the remaining S3ClientFactory name with its responsibilities.

The retained S3ClientFactory no longer creates object-storage clients. Its main responsibilities are SDK lifetime, AWS helpers, and CA handling; Azure still obtains CA information through it.

At minimum, a more accurate name would avoid suggesting that the code now has two client factories. If CA handling is later extracted into a backend-neutral utility, keep SDK lifetime ownership explicit and preserve the guarantee that clients are destroyed before SDK shutdown.

I would retain the typed configuration, centralized input conversion, and independent holder snapshots. Prioritizing points 1-3 would further reduce duplicated rules and dependency cycles, while concentrating the changes required to add a backend and making omissions easier for the compiler to detect.

Validation was source-based; no builds or tests were run for this architecture analysis.

xylaaaaa added a commit to xylaaaaa/doris that referenced this pull request Oct 26, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: The holder tests in obj_client_factory_test call secret_key_of, but the helper was dropped together with an adjacent test when this PR was split from apache#69082, so the BE unit tests did not compile. The helper is restored.

### Release note

None

### Check List (For Author)

- Test: Unit Test (compiled and run in CI)
- Behavior changed: No
- Does this need documentation: No
@xylaaaaa

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28906 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 7d30bee1d9d149b76841f08d635320254e866bee, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17600	5229	5064	5064
q2	2118	349	252	252
q3	10214	1409	845	845
q4	4678	468	342	342
q5	7437	808	539	539
q6	182	187	156	156
q7	753	814	612	612
q8	9262	1350	1152	1152
q9	5553	4211	4196	4196
q10	6826	1293	1024	1024
q11	429	259	234	234
q12	628	395	289	289
q13	18127	3693	3044	3044
q14	274	273	253	253
q15	q16	742	719	680	680
q17	1199	744	574	574
q18	7039	6162	6109	6109
q19	1257	966	603	603
q20	388	337	227	227
q21	6063	2425	2613	2425
q22	389	339	286	286
Total cold run time: 101158 ms
Total hot run time: 28906 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5926	5926	6202	5926
q2	717	560	591	560
q3	5592	5747	5336	5336
q4	2025	2144	1555	1555
q5	5838	5770	5667	5667
q6	237	183	143	143
q7	2285	2036	1863	1863
q8	2976	2610	2645	2610
q9	8000	7617	7465	7465
q10	3604	3584	3100	3100
q11	579	419	382	382
q12	671	656	502	502
q13	3313	3611	3026	3026
q14	284	286	261	261
q15	q16	657	672	598	598
q17	7835	7005	6886	6886
q18	12985	12206	12923	12206
q19	904	782	784	782
q20	2158	2138	1908	1908
q21	5733	4669	4840	4669
q22	514	443	413	413
Total cold run time: 72833 ms
Total hot run time: 65858 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 151230 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 7d30bee1d9d149b76841f08d635320254e866bee, data reload: false

query5	4335	619	476	476
query6	429	213	186	186
query7	4809	487	231	231
query8	326	170	155	155
query9	8777	3067	3022	3022
query10	496	311	258	258
query11	6049	3871	3586	3586
query12	159	91	87	87
query13	1273	456	320	320
query14	6127	3929	3622	3622
query14_1	3437	3456	3416	3416
query15	209	203	182	182
query16	946	449	356	356
query17	850	685	537	537
query18	2420	434	313	313
query19	208	177	145	145
query20	85	94	84	84
query21	213	137	111	111
query22	12950	13414	12828	12828
query23	13509	12856	12514	12514
query23_1	12496	12541	12615	12541
query24	7397	867	474	474
query24_1	487	492	488	488
query25	568	412	342	342
query26	1256	270	139	139
query27	2759	462	269	269
query28	4613	1948	1921	1921
query29	1533	624	427	427
query30	299	222	181	181
query31	1013	936	814	814
query32	137	95	91	91
query33	512	301	237	237
query34	907	908	494	494
query35	754	769	652	652
query36	786	793	734	734
query37	128	97	83	83
query38	1781	1747	1647	1647
query39	786	801	751	751
query39_1	723	728	750	728
query40	238	130	97	97
query41	69	65	64	64
query42	86	89	82	82
query43	353	376	313	313
query44	1383	685	704	685
query45	193	177	164	164
query46	917	1032	580	580
query47	2733	2905	2906	2905
query48	308	321	219	219
query49	573	418	296	296
query50	728	286	212	212
query51	10452	10560	10492	10492
query52	88	87	69	69
query53	193	221	150	150
query54	253	210	173	173
query55	80	69	64	64
query56	228	209	214	209
query57	1639	1613	1572	1572
query58	292	259	264	259
query59	2135	2132	1937	1937
query60	290	247	227	227
query61	147	146	147	146
query62	394	337	286	286
query63	201	170	157	157
query64	2726	960	775	775
query65	3327	3250	3254	3250
query66	1796	448	308	308
query67	20129	19990	20010	19990
query68	3224	1032	620	620
query69	394	300	253	253
query70	875	809	757	757
query71	296	241	201	201
query72	2762	2721	2460	2460
query73	553	564	298	298
query74	4681	4543	4325	4325
query75	2483	2361	1912	1912
query76	2412	1133	614	614
query77	358	407	288	288
query78	9752	9641	9199	9199
query79	1002	913	509	509
query80	563	497	435	435
query81	529	327	268	268
query82	574	136	111	111
query83	299	199	183	183
query84	305	122	97	97
query85	807	445	365	365
query86	323	239	235	235
query87	2000	1954	1807	1807
query88	3693	2646	2634	2634
query89	358	290	242	242
query90	1892	173	172	172
query91	165	153	128	128
query92	92	89	88	88
query93	1043	1034	590	590
query94	436	286	260	260
query95	583	390	299	299
query96	673	509	231	231
query97	2446	2432	2316	2316
query98	163	155	148	148
query99	769	759	653	653
Total cold run time: 234713 ms
Total hot run time: 151230 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.33 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 7d30bee1d9d149b76841f08d635320254e866bee, data reload: false

query1	0.01	0.01	0.01
query2	0.09	0.05	0.05
query3	0.26	0.09	0.08
query4	1.61	0.11	0.11
query5	0.23	0.24	0.24
query6	1.17	0.67	0.66
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.40	0.34	0.35
query10	0.58	0.60	0.56
query11	0.20	0.14	0.14
query12	0.18	0.15	0.16
query13	0.50	0.49	0.49
query14	1.02	1.00	1.02
query15	0.63	0.61	0.62
query16	0.36	0.35	0.36
query17	1.17	1.12	1.12
query18	0.21	0.19	0.20
query19	2.04	2.00	1.99
query20	0.02	0.02	0.01
query21	15.50	0.20	0.13
query22	4.89	0.05	0.05
query23	16.35	0.33	0.12
query24	3.01	0.57	0.30
query25	0.12	0.07	0.06
query26	0.95	0.22	0.16
query27	0.04	0.04	0.03
query28	3.52	0.77	0.36
query29	12.51	4.13	3.29
query30	0.27	0.13	0.10
query31	2.77	0.56	0.32
query32	3.22	0.60	0.50
query33	3.14	3.26	3.30
query34	15.41	4.02	3.40
query35	3.35	3.34	3.35
query36	0.56	0.43	0.42
query37	0.09	0.06	0.06
query38	0.05	0.04	0.03
query39	0.04	0.03	0.03
query40	0.17	0.14	0.15
query41	0.08	0.03	0.02
query42	0.04	0.03	0.03
query43	0.04	0.03	0.03
Total cold run time: 96.9 s
Total hot run time: 24.33 s

xylaaaaa added a commit to xylaaaaa/doris that referenced this pull request Oct 26, 2026
…jClientFactory

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: apply_rate_limit_policy was declared in obj_client_factory.h and defined in obj_client_factory.cpp. ObjClientFactory holds the client providers, and both providers called back into the factory to wrap a new client with the BE rate limiter, so the factory and the providers depended on each other. The policy (BeObjStorageRateLimitPolicy and apply_rate_limit_policy) moves to io/obj_storage/obj_client_rate_limit.{h,cpp}, and the providers include that instead. The dependency is now one way: factory -> providers -> rate limit policy. No behavior change.

### Release note

None

### Check List (For Author)

- Test: No need to test (code moved without change; existing rate limit tests in s3_client_factory_test cover it, run in CI)
- Behavior changed: No
- Does this need documentation: No
@xylaaaaa

Copy link
Copy Markdown
Contributor Author

run buildall

xylaaaaa added a commit to xylaaaaa/doris that referenced this pull request Oct 26, 2026
…jClientFactory

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: Same change as in apache#69605. apply_rate_limit_policy lived in ObjClientFactory while both client providers, which the factory holds, called back into it, so the factory and the providers depended on each other. The policy moves to io/obj_storage/obj_client_rate_limit.{h,cpp}; the dependency is now factory -> providers -> rate limit policy. No behavior change.

### Release note

None

### Check List (For Author)

- Test: No need to test (code moved without change; run in CI)
- Behavior changed: No
- Does this need documentation: No
xylaaaaa added a commit to xylaaaaa/doris that referenced this pull request Oct 26, 2026
…layer

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: The typed configuration rules were split by where the old code lived rather than by which module owns them. Validation was implemented twice, as `validate_flat_fields` in the resolver and `validate_obj_client_backend` in the factory. The refresh merge `merge_update` lived in the resolver, so the holder depended on the input adapter, and it used `else` to mean Azure. Azure validation and construction read `AzureSharedKeyConfig` with `std::get`, so a new authentication alternative would fail at runtime with `bad_variant_access` instead of at compile time.

- `validate_obj_client_config` and `merge_obj_client_config` are now part of the config layer (`obj_storage_config.{h,cpp}`) and are written as one overload per backend and per Azure authentication alternative, so a missing case fails to compile. The factory validates every request with it; the holder merges with it and no longer depends on the resolver.
- The resolver keeps only the rules of FE properties that the typed configuration does not cover: a region for every provider, Azure included, and no provider-native credential for Azure. It then checks the converted configuration with the same `validate_obj_client_config`.
- `AzureClientProvider` builds the credential options and the endpoint info through per-alternative overloads.
- `ObjStorageConnectionOptions` documents that only S3 clients apply it today.

Behavior is unchanged except that an invalid FE property map now fails at conversion instead of at the first client request, and when several fields are missing the reported one may differ.

### Release note

None

### Check List (For Author)

- Test: Unit Test (obj_client_factory_test covers typed Azure validation and the Azure provider-native credential rule on the property path; existing merge tests moved to merge_obj_client_config). Run in CI.
- Behavior changed: No (see above)
- Does this need documentation: No
@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.33 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit f58598d426a9d97a110dba1f1e85f6279f0607b9, data reload: false

query1	0.01	0.01	0.00
query2	0.09	0.05	0.05
query3	0.26	0.10	0.08
query4	1.61	0.11	0.10
query5	0.23	0.24	0.25
query6	1.17	0.68	0.69
query7	0.04	0.01	0.01
query8	0.05	0.04	0.03
query9	0.40	0.34	0.34
query10	0.62	0.58	0.59
query11	0.19	0.15	0.14
query12	0.19	0.15	0.15
query13	0.51	0.50	0.48
query14	1.04	1.01	1.01
query15	0.63	0.61	0.60
query16	0.35	0.35	0.34
query17	1.16	1.08	1.07
query18	0.20	0.18	0.19
query19	2.06	1.98	1.96
query20	0.02	0.01	0.01
query21	15.49	0.18	0.13
query22	4.90	0.05	0.05
query23	16.30	0.33	0.12
query24	3.09	0.43	0.52
query25	0.10	0.05	0.05
query26	1.31	0.22	0.14
query27	0.06	0.05	0.05
query28	3.97	0.76	0.33
query29	12.51	4.20	3.32
query30	0.27	0.12	0.09
query31	2.78	0.55	0.33
query32	3.22	0.60	0.53
query33	3.17	3.28	3.21
query34	15.42	3.99	3.39
query35	3.35	3.35	3.32
query36	0.56	0.46	0.43
query37	0.10	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.15	0.14
query41	0.09	0.03	0.04
query42	0.04	0.03	0.03
query43	0.04	0.03	0.03
Total cold run time: 97.86 s
Total hot run time: 24.33 s

@morningman

Copy link
Copy Markdown
Contributor

Review at head f58598d426a9. No build or test was run locally; see "Coverage and limits" at the end. This is not an approval.

Code Review — PR #69605: refactor Build object storage clients from typed, per-backend configuration

PR #69605 (state: open)
PR head f58598d426a9d97a110dba1f1e85f6279f0607b9 on refactor/be-obj-client-config of xylaaaaa/doris
Diff range 7acfd9bfc27f01f0b953e8ac0cdc3482a46e3c59...f58598d426a9d97a110dba1f1e85f6279f0607b9 (three-dot, merge base 7acfd9bfc27f01f0b953e8ac0cdc3482a46e3c59)
Scope 69 files, +3541/-1724 lines, 14 commits (the head is a merge of master)
Verdict REQUEST_CHANGES
Rounds 2 of max 3; converged (verdict stable since round 1; round 2 added no finding and refined F-01 and F-02)
Builds on 719de6a (2026-10-26) and af4964b (2026-10-31)

Summary

The PR replaces the flat S3Conf / S3ClientConf with a typed configuration that holds exactly one backend, and builds S3 and Azure clients through per-backend providers. This is the third review. Since af4964b the author has fixed all 20 findings of the previous review. The BE no longer aborts on an Azure resource that carries a native credential. The gitleaks gate is green, and so is BE UT with master merged. The connection-limit scope is declared. Each test gap the previous review named now has a test that fails on the named mutation. Every TeamCity gate passed at this head.

Two Majors remain, and both are in the new code that applies a backend change. The fix for the old F-04 makes S3FileSystem::refresh build a second file system. Rowsets that already resolved the first one keep it until BE restart, so a revert or a later key rotation never reaches them (F-01). The cloud vault refresh did not get that fix and still only resets. A vault id that is handed out again for the other provider therefore keeps the dropped vault's target, and loads write there (F-02). The rest is a false rationale in two comments (F-03) and three test gaps (F-04 to F-06).

Findings at a glance

ID Severity Regression Finding
F-01 Major yes A provider change across S3 and Azure leaves already-bound rowsets on the old file system, so a revert or a key rotation no longer reaches them
F-02 Major yes A vault id that is handed out again for another provider keeps the dropped vault's file system, and loads write into the old bucket
F-03 Minor no Two new comments give a false reason why Recycler does not check the Azure key pair
F-04 Minor no No test that runs in CI pins which value becomes the Azure SharedKey account and which the key
F-05 Nit no ObjClientFactoryTest counts builds through a process-wide cache and cannot be repeated in one process
F-06 Nit no The refresh test does not pin a vendor change inside the S3 family

Status of the previous review's findings

The previous review is this comment.

Prior (af4964b) Severity At this head
F-01 Azure plus a native credential aborts the BE Blocker Fixed. The resolvers return Result, and the Azure mapping returns InvalidArgument for all three sources (obj_storage_config_resolver.cpp:111-120), tested at obj_client_factory_test.cpp:202-236
F-02 BE UT breaks once merged into master Blocker Fixed. The CSV, JSON and S3 reader tests were migrated and BE UT 1090345 is green; master tip 7866f5a36c0 adds no user of a removed symbol
F-03 required Check for secrets red Blocker Fixed. Commit 1 was rewritten and the check is green
F-04 a running BE ignores a provider-family ALTER Major Fixed for the registry by S3FileSystem::refresh. That fix causes F-01 below, and the vault path is F-02
F-05 connection-limit scope undeclared Major Fixed. Item 1, the release note and obj_storage_config.h:49-51
F-06 to F-09 Minor Fixed: the vault sync logs the failing status, and the reset comment, item 2, the role_arn wording and the resolver header now match the code
F-10 to F-13 Minor Fixed. Each mutation the previous review named now fails a test that ran in BE UT 1090345
F-14 to F-20 Nit Fixed

Findings

F-01 · A provider change across S3 and Azure leaves already-bound rowsets on the old file system, so a revert or a key rotation no longer reaches them

  • Severity: Major
  • Category: functional-bug (lifecycle)
  • Regression: yes
  • Regression evidence: git show 7acfd9bfc27:be/src/io/fs/s3_file_system.cpp lines 83-123 apply every update, provider included, to the one holder of a resource, and the base update_s3_resource keeps existed_fs. Item 3 promises "the same result as after a restart" and says that a policy-referenced resource "still fails on access, as before". It does not say that rowsets which already resolved the resource stay on the old backend.
  • Where: be/src/io/fs/s3_file_system.cpp:78-86
Result<std::shared_ptr<S3FileSystem>> S3FileSystem::refresh(std::shared_ptr<S3FileSystem> fs,
                                                            ObjStorageConfig config) {
    const auto& holder = fs->client_holder();
    if (holder->state()->config.backend.index() != config.client.backend.index()) {
        return create(std::move(config), fs->id());
    }
    RETURN_IF_ERROR_RESULT(holder->reset(config.client));
    return fs;
}

What is wrong. For another backend, refresh builds a second file system with the same id, and update_s3_resource registers it (task_worker_pool.cpp:1809-1828). Nothing re-points the objects that already hold the first one. A remote rowset binds its storage resource once, lazily on first use or eagerly at cooldown, and never resolves it again (rowset_meta.cpp:199-201, rowset_meta.cpp:220-223). Its segments open through that file system (beta_rowset.cpp:278) and read through that file system's holder (s3_file_system.cpp:136, s3_file_reader.cpp:134). Later pushes refresh only the registered object (task_worker_pool.cpp:1872-1878). The holder exists so that cached readers follow every update (obj_client_holder.h:46-48), and at the merge base each resource had exactly one.

Why it happens. Commit b343fc6 assumes that only in-flight requests hold the old file system ("requests that hold the old one finish on it"). The new test asserts the split as intended (obj_client_factory_test.cpp:529-530). On the FE side, a resource referenced by a storage policy keeps its endpoint, region, root path and bucket frozen, but not its provider (S3Resource.java:250-263). The ping is skipped when s3_validity_check is false (S3Resource.java:284-297). When the ping runs, it goes through the first filesystem plugin that accepts the properties. Plugins load in directory-name order, so Azure comes first (DirectoryPluginRuntimeManager.java:213-216, FileSystemPluginManager.java:203-209). The Azure plugin accepts a map that says provider=azure or that names an Azure blob endpoint (AzureFileSystemProvider.java:63-80). For an S3 resource, the switch to azure therefore fails the ping and needs s3_validity_check=false. For an Azure resource, both steps of a round trip through provider=S3 ping the unchanged Azure target and pass with every setting at its default.

When it bites. Either of two cases triggers it on a non-cloud cluster. One is an Azure resource with default settings that is switched to S3 and back. The other is an S3 resource with s3_validity_check=false, a setting the regression suites use 47 times across 40 files, that is switched to azure and back. The table shows the S3 case, for a resource r that is referenced by a storage policy and has cooled rowsets.

Step Merge base This head
1. ALTER RESOURCE r PROPERTIES ("provider"="azure") by mistake The one holder becomes an Azure client on the frozen S3 endpoint, and every read of cooled data fails A second, Azure file system is registered. Rowsets bound before the ALTER keep reading S3. Rowsets first opened now bind to the Azure one and fail
2. ALTER RESOURCE r PROPERTIES ("provider"="S3") to revert The holder goes back to S3 and every reader recovers A third, S3 file system is registered. The rowsets bound in step 1 keep failing until BE restart
3. Rotate s3.access_key / s3.secret_key, then revoke the old key Every reader uses the new key Only the third file system gets it. Rowsets bound before step 1 fail with 403 until BE restart

FE shows the corrected resource and the BE logs "successfully update s3 resource", so nothing points at the stale binding. Rowsets can also bind during the window of step 1 without any user query. Cold data compaction on the cooldown replica reads its inputs every 1800 seconds, and rowsets loaded at a restart or followed from another replica start unbound. No data is lost: the objects stay where they were. This was traced in the code and was not run on a cluster.

Severity rationale. It is a regression in functional-bug, so it is at least Major. It is not a Blocker. No data is lost and no result is wrong, because reads fail with an error. A BE restart heals it, and the trigger needs ADMIN. In one cell this head is better than the merge base. During the mistaken window, rowsets bound before step 1 keep working, where the base failed all of them. The flip side is that the broken resource stays hidden until the next restart.

Suggested fix. Keep one holder for the readers as long as the storage target stays. The backend may change while the bucket and the normalized prefix stay the same; under a storage policy that is always the case, because FE freezes both. Then rebind the existing holder in place to a client built from config.client as it is, without merging. Validate it through ObjClientFactory, publish it with the existing generation check, and return fs. Build a new file system only when the bucket or the prefix moves, which is the case the old F-04 needs. A sketch, with illustrative helper names:

     const auto& holder = fs->client_holder();
-    if (holder->state()->config.backend.index() != config.client.backend.index()) {
-        return create(std::move(config), fs->id());
-    }
-    RETURN_IF_ERROR_RESULT(holder->reset(config.client));
+    if (holder->state()->config.backend.index() == config.client.backend.index()) {
+        RETURN_IF_ERROR_RESULT(holder->reset(config.client));
+        return fs;
+    }
+    // Rowsets that already resolved this resource keep `fs` and its holder until BE restart,
+    // so keep the holder while the target stays (FE freezes bucket and root path under a
+    // storage policy). Only a moved target needs another file system.
+    if (config.bucket != fs->bucket() || normalized_prefix(config.prefix) != fs->prefix()) {
+        return create(std::move(config), fs->id());
+    }
+    RETURN_IF_ERROR_RESULT(holder->rebind(config.client));
     return fs;

Add a test that keeps auto reader = fs->client_holder(); across S3, then Azure, then S3 again, with the same bucket and root path. Rotate the key through the registered file system and assert that reader has the rotated key. If the split is meant to stay, say so in item 3 and in s3_file_system.h:50-53: already-bound rowsets stay on the old backend until BE restart. As an FE follow-up, reject a provider change between Azure and the S3-compatible providers under a storage policy.

F-02 · A vault id that is handed out again for another provider keeps the dropped vault's file system, and loads write into the old bucket

  • Severity: Major
  • Category: functional-loss (cloud storage vault refresh)
  • Regression: yes
  • Regression evidence: git show 7acfd9bfc27:be/src/cloud/cloud_storage_engine.cpp calls the same reset. The base reset merges provider into the kept target (git show 7acfd9bfc27:be/src/io/fs/s3_file_system.cpp lines 83-123). The client therefore switched to the new provider against the old endpoint, and every request failed: git show 7acfd9bfc27:be/src/util/s3_util.cpp lines 315-319 build the Azure URI from the kept endpoint. Item 3 says "Storage vaults cannot change their provider", which holds for ALTER but not for a reused id.
  • Where: be/src/cloud/cloud_storage_engine.cpp:204
        const auto& s3_conf = resolved.value();
        DCHECK_EQ(fs->type(), io::FileSystemType::S3) << id;
        auto s3_fs = std::static_pointer_cast<io::S3FileSystem>(fs);
        auto client_holder = s3_fs->client_holder();
        auto st = client_holder->reset(s3_conf.client);
        if (!st.ok()) {
            LOG(WARNING) << "failed to update s3 fs, resource_id=" << id << ": " << st;
        }
        return st;

What is wrong. The fix for the old F-04 went into update_s3_resource only. The vault refresh still calls reset, which now refuses another backend (obj_storage_config.cpp:207-212). The BE keeps the file system it registered for the id, and no other path replaces it.

Why it happens. ALTER cannot change the provider of a vault (meta_service_resource.cpp:1089-1096), but a drop can free its id. The MS HTTP API drop_hdfs_vault removes any vault by name (http_helper.cpp:524, meta_service_resource.cpp:1699-1704, meta_service_resource.cpp:892-915), and it is the only drop the MS accepts (meta_service_resource.cpp:1744-1751). A new vault gets the largest existing id plus one (meta_service_resource.cpp:673-697), which is the dropped id when that vault held the largest. The BE never unregisters a vault: delete_storage_resource (storage_policy.cpp:127) has no caller. Sync therefore finds the old file system under the id (cloud_storage_engine.cpp:496-502), and loads resolve it (cloud_rowset_builder.cpp:115).

When it bites. A cloud cluster in vault mode:

Step Merge base This head
1. CREATE STORAGE VAULT s3v (S3, endpoint E1, bucket b1) gets id N BEs register an S3 file system under N Same
2. An operator drops s3v with drop_hdfs_vault and creates azv (Azure, endpoint E2, container c2), which gets N again Sync resets the holder to an Azure client on E1, and every load into azv fails Sync logs a WARNING and keeps the S3 file system. Loads into azv succeed, into b1 with the old keys
3. BE restart azv is built on E2/c2 and nothing is misplaced azv is built on E2/c2. The rowsets of step 2 are not there, and queries on them fail

One natural way to get here is to fix a vault created with the wrong provider by dropping it and creating it again. Only the cross-family cell changed: a same-family reuse misbinds the same way at the merge base and at this head. This was traced in the code and was not run on a cluster.

Severity rationale. It is rated at the floor, for the same reason as the old F-04: the loud failure at the merge base protected the data, and at this head the data lands in the old bucket. It is not a Blocker for two reasons. The objects exist and can be copied by hand. And the trigger needs an operator to drop an object vault through an API named for HDFS vaults, a gap in the meta service that predates this PR.

Suggested fix. The root fix belongs in the meta service: never hand out a dropped vault id again, and make remove_hdfs_storage_vault refuse an object vault. A BE change alone is not enough. Registering a new file system from RefreshFSVaultVisitor, through S3FileSystem::refresh and put_storage_resource(id, {fs, path_format}, 0), would fix the registry. But PackedFileManager keeps its own file system per vault id for the life of the process (packed_file_manager.cpp:292-296), so the packed small files of new loads would still go to the old bucket. A vault id reused from an HDFS vault also reaches this visitor with an HdfsFileSystem, so a BE fix must check fs->type() before the cast at cloud_storage_engine.cpp:202; that cast is the same at the merge base. If the BE side changes in this PR, either evict that cache entry too, or make loads for an id whose backend changed fail instead of keeping the old target. Then qualify item 3.

F-03 · Two new comments give a false reason why Recycler does not check the Azure key pair

        // Storage metadata carries the SharedKey pair as ak/sk, which the meta service requires
        // together for Azure when it stores them; the SDK credential comes from the
        // authentication shared with BE.

What is wrong. The meta service requires the pair only when an Azure entry is created. ALTER STORAGE VAULT checks only that both keys are sent, not that both are non-empty (meta_service_resource.cpp:1180-1194). The MS HTTP calls alter_obj_info, legacy_update_ak_sk and update_ak_sk do the same. So ALTER STORAGE VAULT azv PROPERTIES ("type"="S3", "s3.access_key"="acct", "s3.secret_key"="") stores an Azure vault with half a pair.

Why it happens. Commit 0225126 removed the check again, rightly, and gave this reason. The real reason is that S3Conf::from_obj_store_info loads ak and sk only as a pair (s3_accessor.cpp:276-292), so Recycler never sees half a pair. A failed accessor init would also fail the instance's recycler init.

When it bites. The header is where the next authentication methods (SAS, OAuth2, Managed Identity) are added. Someone who trusts it will skip a check on the assumption that the meta service already validated the input.

Suggested fix. In s3_accessor.cpp:399-401 write that S3Conf::from_obj_store_info loads the pair complete or not at all. In azure_auth.h:49-51 drop "that the meta service validated when it stored it". In the PR body, say that the meta service requires the pair when an Azure entry is created.

F-04 · No test that runs in CI pins which value becomes the Azure SharedKey account and which the key

  • Severity: Minor
  • Category: test-coverage (Azure SharedKey wiring)
  • Regression: no
  • Regression evidence: git show 7acfd9bfc27:common/cpp/obj-client/auth/azure_auth_factory.cpp built the same credential, and its only test was the same empty case. The signature changed from named struct fields to adjacent positional std::string_view arguments, so a swap no longer stands out.
  • Where: be/test/io/fs/azure_obj_storage_client_test.cpp:85-90
TEST(AzureAuthFactoryTest, AllowsEmptySharedKeyCredentials) {
    auto result = AzureAuthFactory::create("https://account.blob.core.windows.net/container", "",
                                           AzureSharedKeyConfig {.account_key = ""}, {});

    EXPECT_TRUE(result);
}

What is wrong. Three swaps would pass both BE UT and Cloud UT: the account and the key in azure_auth_factory.cpp:30-31, conf_.ak and conf_.sk at s3_accessor.cpp:402-403, or the container for the account at azure_client_provider.cpp:113-114. Every Azure request of BE and Recycler would then fail authentication.

Why it happens. The only direct test passes empty values. The BE tests that build Azure clients check identity, the rate-limit wrapper or file-system fields. The suites that would authenticate are skipped in CI: in Cloud UT 1090347 S3AccessorTest.azure and the four S3AccessorClientTest.Azure* cases did not run, and neither did the three AzureObjStorageClientTest cases in BE UT 1090345. Commit 0225126 still names "cloud UT in CI" as the test of the Recycler change.

When it bites. The next change to this factory is the SAS / OAuth2 work the PR body announces.

Suggested fix. Give AzureAuthFactoryTest a non-empty account and key. Then assert result.shared_key_credential->AccountName, the key (BE UT is built without access control), and result.container_client->GetUrl(). Add one Recycler case that creates an Azure S3Accessor from fake ak/sk and checks the credential's account name; it needs no network.

F-05 · ObjClientFactoryTest counts builds through a process-wide cache and cannot be repeated in one process

        _sync_point->set_call_back(
                "s3_client_factory::create",
                [this](auto&& args) {
                    ++_s3_builds;
                    if (_on_s3_build) {
                        // Run once: a nested build must not re-enter the hook.

What is wrong. The hook runs only on a miss in the provider cache of the process-wide ObjClientFactory (s3_client_provider.cpp:42-59), and nothing clears that cache. With GTEST_REPEAT=2, the second iteration fails S3ProviderCachesByFullConfig, HolderRejectsResetMergedOntoStaleState and FactoryValidatesBeforeBuilding. A single run is fine, because no other test uses these endpoints, and BE UT 1090345 ran all 16 cases.

Suggested fix. Add a BE_TEST-only cache reset next to set_client_creator_for_test and call it in SetUp, or make the counted endpoints unique per test instance.

F-06 · The refresh test does not pin a vendor change inside the S3 family

TEST_F(ObjClientFactoryTest, RefreshResetsTheSameBackendAndRebuildsAnotherOne) {
    auto param = make_s3_param("refresh-s3.example.com");

What is wrong. The test covers AWS to AWS and AWS to Azure. If s3_file_system.cpp:81 compared provider() instead of backend.index(), every suite would pass. Every ALTER from AWS to OSS, COS or GCP would then build a new file system instead of resetting the existing one in place, and F-01 would apply to those too.

Suggested fix. Refresh with the same parameter under provider OSS and another endpoint. Assert that the same fs comes back, that the endpoint is kept, and that the vendor is OSS.

Critical checkpoints
Checkpoint Conclusion
Goal achieved, proven by a test Mostly. The typed configuration, the per-backend clients and the Azure model shared with Recycler work and are tested. In BE UT 1090345 at this head ObjClientFactoryTest passed 16 of 16, ObjStorageUriS3RulesTest 9 of 9 and S3ClientFactoryTest 39 of 39. The fix for the old F-04 achieves only the registry half: F-01, F-02
Change is minimal, clear, focused Large, but mostly a move. Commits 5 to 13 are small, and each answers one earlier finding. 2885407 adds a Recycler check and 0225126 takes it out again, which leaves only a comment (F-03)
Concurrency: threads, shared state, locks, lock order update_s3_resource and S3FileSystem::refresh run on the single PUSH_STORAGE_POLICY worker (agent_server.cpp:145-146). The registry is under its mutex, and the holder's generation check handles overlapping vault resets (item 2). No new lock and no new lock order
Lifecycle / static init order A backend change now publishes a second file system for one resource id, and readers that hold the first keep it until restart: F-01. No new static and no cross-TU initializer
New config: dynamic? observed without restart? n/a, no option added
Incompatible change: rolling-upgrade path No wire, persisted or config change, and gensrc is untouched. The AzureAuthFactory signature changed inside common/cpp, which BE and Recycler each link into their own binary; they exchange no Azure object
Parallel code paths updated Only two paths reset a holder: s3_file_system.cpp:84 and cloud_storage_engine.cpp:204. The fix went into the first only: F-02. A sweep of every file-system binding site in both modes found no third. The Azure plus credential rejection is now the same on all three flat sources
Special conditionals explained The backend-index branch of refresh is commented, but its premise ("as the next BE start would build it") does not hold for bound rowsets: F-01. The has_value() check in vault_process_error handles a real input (cloud_storage_engine.cpp:134)
Test coverage (e2e, negative, unit) The new tests close every test gap of the previous review. Still open: F-04, F-05, F-06. No test keeps a reader across a backend change (F-01) or refreshes a vault with another backend (F-02). No pipeline reaches Azure, so unit tests are the only gate there
Test results added/modified and correct n/a, no .out or groovy file changed
Observability (logs, VLOG, metrics) The resource WARNING now names the id and the name, and the vault WARNING carries the failing status. Nothing logs that a resource was split, and the BE logs success: F-01
Transaction / persistence / EditLog replay n/a. The meta service and FE are not changed
Data write atomicity, crash safety The writer logic is untouched. F-02 can place vault data in a dropped vault's bucket
New FE↔BE variables on every send path n/a, none added
Performance and anti-patterns No hot-path change. refresh adds one snapshot per push, and the include trimming in be/src/util/s3_util.cpp only reduces compile work
Anything else The CI /review run failed on model capacity (issue comment 6601595557), so the code-review status is still pending. The build hygiene gate passes at this head, run as a text scan
Considered and dismissed
# Concern Where Why it is not an issue
D-01 A rejected vault configuration changes which file system becomes latest_fs, or drops the vault cloud_storage_engine.cpp:510 The entry keeps its place (cloud_meta_mgr.cpp:1913-1916). Create returns the error and get_filesystem(id) stays null; refresh returns it and keeps the old file system. Both match a failed create or reset at the merge base, whose S3Conf::get_s3_conf never failed. StorageVaultInfos has no other consumer
D-02 The RFC 3986 flag set through SDKOptions behaves differently s3_util.cpp:49-50 aws-sdk-cpp 1.11.221, the pinned version (vars.sh:391-394), applies httpOptions.compliantRfc3986Encoding inside InitAPI (Aws.cpp, line 157). This is the only InitAPI in be/src, and S3ClientFactoryTest.InitializesSdkWithRfc3986KeyEncoding ran
D-03 Recycler builds its Azure client differently s3_accessor.cpp:402-403 Same account, key and options as git show 7acfd9bfc27:cloud/src/recycler/s3_accessor.cpp. The init-time check of 2885407 is gone again (0225126), and the branch is under #ifdef USE_AZURE
D-04 The Azure cache hash moved from crc32 to std::hash azure_auth.cpp:61 It is in-process only, and the client caches compare the full configuration (obj_client_cache.h:53)
D-05 The merge resolution, or the 12 later master commits, break compile or hygiene s3_util.cpp:20-34 Include lines only. COMPILE 1090348 and BE UT 1090345 are green. The later master commits touch no header and no object-storage file, the trial merge with 7866f5a36c0 is clean, and the removed symbols occur only in comments
D-06 An unknown TObjStorageType or TCredProviderType reaches LOG_FATAL / __builtin_unreachable obj_storage_config_resolver.cpp:403 Identical at the merge base (git show 7acfd9bfc27:be/src/util/s3_util.cpp lines 595-600, 658-660, 727-729). Not this PR
D-07 Unity collisions of the new internal names in common/cpp CMakeLists.txt:27 CommonCPP sets no UNITY_BUILD property; only the BE pilot targets do
D-08 "Storage vaults cannot change their provider" is false even for ALTER meta_service_resource.cpp:1093 ALTER rejects provider, and FE does not allow it in ALTER STORAGE VAULT. Only a reused id reaches the BE: F-02
D-09 The split of F-01 misplaces cold data tablet.cpp:2143 Cooldown, the output of cold data compaction and remote GC resolve the registry each time. Schema change writes through the input rowset's binding, whose endpoint and bucket are frozen under a storage policy. F-01 makes reads fail; it does not move data
D-10 S3FileWriterTest releases its S3 client after the SDK shuts down s3_file_writer_test.cpp:332 The upload pool drains first, and ExecEnv gets its old FileCacheFactory back before the owned one is destroyed. The ObjClientFactory cache that also holds the client is destroyed before ShutdownAPI. The s3_fs.reset() keeps the namespace-scope static from holding the last reference
D-11 Callers keep an if (!built) branch that can no longer fire azure_client_provider.cpp:115 Harmless, and it becomes live with the next authentication method
D-12 A vault id reused across HDFS and S3 casts to the wrong file system type cloud_storage_engine.cpp:202 Identical at the merge base. Not this PR; it matters for the fix of F-02
D-13 The Azure role_arn plus half-pair cell is also reachable from the meta service obj_storage_config.cpp:143-151 True: ADD_OBJ_INFO, then update_ak_sk. Item 4 declares it: such a configuration could not authenticate before either
D-14 The first-sync connectivity check of a vault never runs cloud_storage_engine.cpp:488 first_sync_storage_vault starts at true and the compare-and-swap expects false. This is the same at the merge base and not this PR; it is worth a separate issue
D-15 Earlier dismissals D-01 to D-17 of the af4964b review s3_file_system.cpp:398 Re-checked at this head. D-02 is now at s3_file_system.cpp:398, D-08 at agent_server.cpp:145-146 and D-15 at azure_client_provider.cpp:119-120. The others name byte-identical code. D-16 (check_coverage red) is obsolete, because it is green
D-16 The reset log prints the update rather than the merged configuration that is published obj_client_holder.cpp:55 The same at the merge base (git show 7acfd9bfc27:be/src/io/fs/s3_file_system.cpp line 114 prints conf). Credentials are masked in both
Coverage and limits
  • Files reviewed in depth: the 31 files whose PR hunk changed since af4964b or is new (22 changed, 9 new) were read in full, or every hunk with its function and callers. Also read: the storage-side holders of a resource file system, the meta-service vault writers, and the FE resource, vault and filesystem-plugin code the findings depend on. None of these is changed by the PR.
  • Carried forward: 35 files are blob-identical to what the af4964b review read in full, at both bases and at both heads. Three more test files have the same PR hunk on a moved base and were checked against master's changes.
  • Not verified locally: nothing was built, no test was run, and nothing was executed on a cluster. The sequences in F-01 and F-02 were traced in the code. The only command run against the tree was the build hygiene gate, which is a text scan; the worktree was unchanged afterwards. CI evidence comes from TeamCity at this head: BE UT 1090345, Cloud UT 1090347, COMPILE 1090348 and the regression gates.
  • Evidence taken from outside the repository: aws-sdk-cpp tag 1.11.221 sources (Aws.cpp, URI.cpp, URI.h) fetched through the GitHub API; Azure SDK headers under the installed thirdparty; the TeamCity test lists of BE UT 1090345 and Cloud UT 1090347.
  • Inherited from earlier runs: 719de6a (2026-10-26) and af4964b (2026-10-31). All 20 findings of af4964b are fixed at this head. Of its 17 dismissals, 16 were re-confirmed and carried forward, and one is obsolete.
  • Convergence: converged after 2 rounds. The verdict has been stable since round 1. Round 1 produced both Majors, so it did not converge on its own. Round 2 produced no new finding and only made the trigger of F-01 and the fix of F-02 more precise.

… writes to a reused vault id

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: Two regressions in how a BE applies a change of backend (S3 family to Azure
or back), plus the review's comment and test follow-ups.

A cold-storage resource: FE allows `ALTER RESOURCE` to change the provider even under a storage
policy, and `S3FileSystem::refresh` built a new file system for it. A remote rowset resolves its
file system once and keeps it, so rowsets bound before the change stayed on the old holder: after
a revert or a key rotation they kept failing with the old backend or the old key until BE
restart, while FE and the BE log reported success. `refresh` now keeps the file system while the
bucket and prefix stay, and rebinds its holder to a client built from the new configuration
(`ObjClientHolder::rebind`), so every rowset follows, as before the refactor. It builds a new file
system only when the bucket or prefix moves, which FE only allows without a storage policy. Every
backend change logs a WARNING with the resource id and both configurations, masked.

A storage vault: a vault's backend cannot be altered, but `drop_hdfs_vault` can drop an object
vault and the meta service can hand its id to the next vault. The vault refresh only reset the
holder, which refuses another backend, so loads for the new vault kept writing into the dropped
vault's bucket; before the refactor they failed. `S3FileSystem::refresh_vault` now takes the
holder out of service in that case (`ObjClientHolder::invalidate`). Every user shares the holder,
including `PackedFileManager`, so all of them fail with their existing "no client" errors until a
BE restart builds the new vault's file system. The invalidation is permanent: `reset`, `rebind`
and `init` return its reason, and `_publish` checks it under the lock before the generation, so
a refresh carrying an older configuration cannot bring the client back. The shared SDK client is
left alone, since other holders may use it. Because the client can now become null at runtime:
`S3FileWriter::_complete` takes it after waiting for the uploads, so that it no longer returns with
upload callbacks still running and sees an invalidation during the wait, and
`generate_presigned_url` checks it before its OSS branch, which would build a client of its own.

Limits, accepted here: a call already sent cannot be recalled, so a PutObject that took the client
may still succeed. Until the next vault sync (60s by default) a BE cannot know the id was reused.
A reused id on the same backend or between HDFS and S3 is not detected. These need the fix in the
meta service, which is handled separately.

Review follow-ups: the comments on why Recycler does not check the Azure key pair now give the
real reason (`S3Conf::from_obj_store_info` loads the pair complete or not at all; the meta service
only requires it when an entry is created). New tests pin which value becomes the Azure account
and which the key, in `AzureAuthFactory`, `AzureClientProvider` and Recycler's `S3Accessor`, without
an account. `ObjClientFactoryTest` cases that count builds use unique endpoints, so they can be
repeated in one process.

### Release note

None

### Check List (For Author)

- Test: Unit Test (holder rebind across S3, Azure and back with a kept reader, vendor change inside
  the S3 family, invalidation and overlapping reset, vault refresh, presigned URL, writer timing
  with sync points, Azure credential wiring in BE and Recycler; BE UT and cloud UT in CI)
- Behavior changed: Yes. See the PR description, items 3 and 5.
- Does this need documentation: No
@xylaaaaa

xylaaaaa commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

xylaaaaa added a commit to xylaaaaa/doris that referenced this pull request Nov 4, 2026
… writes to a reused vault id

Issue Number: None

Related PR: apache#69605

Problem Summary: Two regressions in how a BE applies a change of backend (S3 family to Azure
or back), plus the review's comment and test follow-ups.

A cold-storage resource: FE allows `ALTER RESOURCE` to change the provider even under a storage
policy, and `S3FileSystem::refresh` built a new file system for it. A remote rowset resolves its
file system once and keeps it, so rowsets bound before the change stayed on the old holder: after
a revert or a key rotation they kept failing with the old backend or the old key until BE
restart, while FE and the BE log reported success. `refresh` now keeps the file system while the
bucket and prefix stay, and rebinds its holder to a client built from the new configuration
(`ObjClientHolder::rebind`), so every rowset follows, as before the refactor. It builds a new file
system only when the bucket or prefix moves, which FE only allows without a storage policy. Every
backend change logs a WARNING with the resource id and both configurations, masked.

A storage vault: a vault's backend cannot be altered, but `drop_hdfs_vault` can drop an object
vault and the meta service can hand its id to the next vault. The vault refresh only reset the
holder, which refuses another backend, so loads for the new vault kept writing into the dropped
vault's bucket; before the refactor they failed. `S3FileSystem::refresh_vault` now takes the
holder out of service in that case (`ObjClientHolder::invalidate`). Every user shares the holder,
including `PackedFileManager`, so all of them fail with their existing "no client" errors until a
BE restart builds the new vault's file system. The invalidation is permanent: `reset`, `rebind`
and `init` return its reason, and `_publish` checks it under the lock before the generation, so
a refresh carrying an older configuration cannot bring the client back. The shared SDK client is
left alone, since other holders may use it. Because the client can now become null at runtime:
`S3FileWriter::_complete` takes it after waiting for the uploads, so that it no longer returns with
upload callbacks still running and sees an invalidation during the wait, and
`generate_presigned_url` checks it before its OSS branch, which would build a client of its own.

Limits, accepted here: a call already sent cannot be recalled, so a PutObject that took the client
may still succeed. Until the next vault sync (60s by default) a BE cannot know the id was reused.
A reused id on the same backend or between HDFS and S3 is not detected. These need the fix in the
meta service, which is handled separately.

Review follow-ups: the comments on why Recycler does not check the Azure key pair now give the
real reason (`S3Conf::from_obj_store_info` loads the pair complete or not at all; the meta service
only requires it when an entry is created). New tests pin which value becomes the Azure account
and which the key, in `AzureAuthFactory`, `AzureClientProvider` and Recycler's `S3Accessor`, without
an account. `ObjClientFactoryTest` cases that count builds use unique endpoints, so they can be
repeated in one process.

None

- Test: Unit Test (holder rebind across S3, Azure and back with a kept reader, vendor change inside
  the S3 family, invalidation and overlapping reset, vault refresh, presigned URL, writer timing
  with sync points, Azure credential wiring in BE and Recycler; BE UT and cloud UT in CI)
- Behavior changed: Yes. See the PR description, items 3 and 5.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: `S3FileSystem::refresh` applies a refreshed cold-storage resource and can return
a new file system, while `refresh_vault` applies a refreshed storage vault and never replaces the
file system. The bare name read like a general version that a vault could use too, so it is now
`refresh_resource`. No behavior change.

### Release note

None

### Check List (For Author)

- Test: No need to test (rename; existing unit tests cover it, BE UT in CI)
- Behavior changed: No
- Does this need documentation: No
@xylaaaaa

xylaaaaa commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: The rename to refresh_resource pushed a comment line past the column limit.

### Release note

None

### Check List (For Author)

- Test: No need to test (comment only)
- Behavior changed: No
- Does this need documentation: No
xylaaaaa added a commit to xylaaaaa/doris that referenced this pull request Nov 4, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: `ObjStorageFileSystem::refresh` applies a refreshed cold-storage resource and can
return a new file system, while `refresh_vault` applies a refreshed storage vault and never
replaces the file system. The bare name read like a general version that a vault could use too, so
it is now `refresh_resource`. No behavior change.

### Release note

None

### Check List (For Author)

- Test: No need to test (rename; existing unit tests cover it, BE UT in CI)
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: The holder invalidation tests only exercised synchronous close.
An invalidated holder used to let async close return before an upload callback
released its writer reference, leaving the destructor exposed to a use-after-free.
Block a PutObject task with latches, invalidate before async close, and verify
that close remains pending until the callback drains and then reports the error.
Also update the resource refresh comment to describe retaining the holder.

### Release note

None

### Check List (For Author)

- Test: Added BE unit test; clang-format 16 and build hygiene passed. Local ASAN build and test execution follow this push; CI pending.
- Behavior changed: No
- Does this need documentation: No
@xylaaaaa

xylaaaaa commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605

Problem Summary: The multipart invalidation test runs its hook after upload
callbacks finish and before CompleteMultipartUpload, but its name and comment
claimed invalidation happened during the wait. Rename the test and its object
key and describe the actual timing. Test logic is unchanged.

### Release note

None

### Check List (For Author)

- Test: Repository clang-format 16 format/check scripts on an isolated copy of
  the changed file (identical to the worktree); git diff --check passed.
  Unit tests not run locally; CI requested after push.
- Behavior changed: No
- Does this need documentation: No
@xylaaaaa

xylaaaaa commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28681 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit e887cf95babb8bc5d2f78697835277cf5c547efc, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17583	5167	5060	5060
q2	2142	304	267	267
q3	10244	1420	826	826
q4	4681	513	364	364
q5	7434	794	515	515
q6	184	182	155	155
q7	745	806	588	588
q8	9250	1322	1051	1051
q9	5520	4163	4194	4163
q10	6819	1305	1033	1033
q11	432	245	236	236
q12	645	398	277	277
q13	18117	3621	3027	3027
q14	274	278	252	252
q15	q16	730	723	677	677
q17	1202	739	568	568
q18	7026	6154	6113	6113
q19	1111	965	599	599
q20	398	336	222	222
q21	5597	2422	2442	2422
q22	398	324	266	266
Total cold run time: 100532 ms
Total hot run time: 28681 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	6005	5989	6105	5989
q2	772	620	568	568
q3	5616	5820	5316	5316
q4	2028	2138	1561	1561
q5	5778	6005	5543	5543
q6	252	185	145	145
q7	2228	2053	1817	1817
q8	2995	2813	2615	2615
q9	7965	7475	7486	7475
q10	3624	3601	3178	3178
q11	579	416	379	379
q12	675	680	494	494
q13	3275	3636	3071	3071
q14	287	302	270	270
q15	q16	668	724	611	611
q17	7695	7042	6883	6883
q18	12963	12200	12921	12200
q19	883	772	755	755
q20	2136	2138	1897	1897
q21	5590	4577	4694	4577
q22	489	439	407	407
Total cold run time: 72503 ms
Total hot run time: 65751 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 150883 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit e887cf95babb8bc5d2f78697835277cf5c547efc, data reload: false

query5	4318	590	427	427
query6	434	205	196	196
query7	4830	448	232	232
query8	321	162	149	149
query9	8789	3175	3176	3175
query10	477	310	258	258
query11	6040	3847	3567	3567
query12	147	92	87	87
query13	1243	439	346	346
query14	6157	3913	3674	3674
query14_1	3403	3408	3441	3408
query15	205	208	181	181
query16	937	451	411	411
query17	850	655	509	509
query18	2393	407	299	299
query19	203	156	128	128
query20	87	80	79	79
query21	204	126	110	110
query22	12969	12973	12723	12723
query23	13388	12898	12424	12424
query23_1	12576	12570	12592	12570
query24	7422	813	465	465
query24_1	497	481	474	474
query25	511	394	341	341
query26	1260	258	143	143
query27	2760	434	268	268
query28	4648	2019	2017	2017
query29	1542	594	443	443
query30	306	222	176	176
query31	1017	923	802	802
query32	150	102	87	87
query33	520	286	247	247
query34	925	845	496	496
query35	730	752	637	637
query36	821	856	721	721
query37	123	97	82	82
query38	1779	1717	1659	1659
query39	796	783	751	751
query39_1	729	727	725	725
query40	214	110	99	99
query41	75	69	69	69
query42	92	93	87	87
query43	385	362	314	314
query44	1316	716	730	716
query45	181	178	169	169
query46	831	974	592	592
query47	2723	3027	2680	2680
query48	302	314	224	224
query49	594	395	305	305
query50	689	282	218	218
query51	10673	10561	10471	10471
query52	87	80	74	74
query53	197	213	154	154
query54	241	198	185	185
query55	81	65	64	64
query56	261	220	220	220
query57	1677	1627	1552	1552
query58	316	275	266	266
query59	2157	2165	1993	1993
query60	291	251	237	237
query61	170	170	168	168
query62	405	333	289	289
query63	188	162	164	162
query64	2895	1049	861	861
query65	3333	3261	3237	3237
query66	1786	405	303	303
query67	20114	20178	19820	19820
query68	3078	997	624	624
query69	393	289	235	235
query70	852	796	781	781
query71	294	237	203	203
query72	2736	2552	2263	2263
query73	508	530	294	294
query74	4603	4485	4357	4357
query75	2311	2282	1910	1910
query76	2307	1009	620	620
query77	346	382	276	276
query78	9965	9765	9121	9121
query79	1005	855	529	529
query80	1230	412	349	349
query81	597	318	273	273
query82	816	137	109	109
query83	307	202	180	180
query84	307	122	105	105
query85	863	428	372	372
query86	402	236	233	233
query87	1985	1947	1778	1778
query88	3877	2787	2769	2769
query89	348	272	238	238
query90	1933	171	174	171
query91	160	155	129	129
query92	91	92	89	89
query93	1036	1006	585	585
query94	629	299	262	262
query95	584	327	368	327
query96	697	495	236	236
query97	2449	2445	2298	2298
query98	161	151	145	145
query99	774	767	649	649
Total cold run time: 236266 ms
Total hot run time: 150883 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.15 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit e887cf95babb8bc5d2f78697835277cf5c547efc, data reload: false

query1	0.01	0.01	0.00
query2	0.08	0.05	0.05
query3	0.26	0.08	0.09
query4	1.61	0.10	0.10
query5	0.23	0.25	0.24
query6	1.17	0.68	0.67
query7	0.04	0.01	0.00
query8	0.06	0.03	0.04
query9	0.40	0.34	0.37
query10	0.60	0.60	0.57
query11	0.20	0.14	0.14
query12	0.18	0.14	0.15
query13	0.50	0.48	0.49
query14	1.03	1.02	0.99
query15	0.62	0.60	0.61
query16	0.35	0.34	0.31
query17	1.17	1.14	1.09
query18	0.21	0.19	0.19
query19	2.10	1.92	2.02
query20	0.02	0.02	0.01
query21	15.49	0.18	0.13
query22	4.90	0.05	0.06
query23	16.29	0.35	0.12
query24	2.83	0.48	0.39
query25	0.10	0.05	0.06
query26	1.22	0.22	0.14
query27	0.04	0.04	0.05
query28	3.57	0.80	0.40
query29	12.52	4.07	3.25
query30	0.29	0.13	0.11
query31	2.77	0.56	0.32
query32	3.23	0.60	0.50
query33	3.12	3.28	3.20
query34	15.46	3.98	3.39
query35	3.31	3.34	3.33
query36	0.58	0.43	0.46
query37	0.09	0.06	0.07
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.15	0.14
query41	0.08	0.03	0.03
query42	0.03	0.02	0.02
query43	0.04	0.04	0.03
Total cold run time: 97.06 s
Total hot run time: 24.15 s

morningman added a commit that referenced this pull request Nov 4, 2026
…berg WriteInfo (#70209)

### What problem does this PR solve?

Issue Number: None

Related PR: #69392, #69971

Problem Summary:

**In short.** `doris_be_test` does not compile on master since #69971
was merged, so the BE UT of every PR fails with `BE BUILD FAIL`. This
fixes the one test case that breaks it.

**The problem.** #69392 replaced the `write_path`, `original_write_path`
and `target_path` fields of `IPartitionWriterBase::WriteInfo` with one
`IcebergOutputLocation location`, and updated the callers it knew about.
#69971, merged after it, added
`VParquetWriterTest.HiveAndIcebergParquetPageIndexOptions`, which still
sets the old fields:

```
be/test/format/transformer/vparquet_writer_test.cpp:380:41: error: no matching constructor for initialization of 'VIcebergPartitionWriter'
```

Each PR built alone, so both were green. TeamCity's BE UT has failed
this way for PRs merged with master `0eb5131db62` (for example build
1092024 of #69605).

**How this PR fixes it.** The case builds its `WriteInfo` as the other
Iceberg case in the same file does, `.location =
IcebergOutputLocation(".", ".")`. Nothing else changes.
@xylaaaaa

xylaaaaa commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

run beut

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 85.27% (110/129) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 67.47% (32235/47780)
Line Coverage 52.31% (339176/648419)
Region Coverage 47.79% (275519/576549)
Branch Coverage 49.65% (129973/261756)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 90.08% (109/121) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.70% (35972/46297)
Line Coverage 63.05% (404354/641359)
Region Coverage 59.55% (341719/573874)
Branch Coverage 60.78% (158534/260820)

@morningman

Copy link
Copy Markdown
Contributor

/review

@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Selected model is at capacity. Please try a different model.
Workflow run: https://github.com/apache/doris/actions/runs/42145826183

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

xylaaaaa added a commit to xylaaaaa/doris that referenced this pull request Nov 9, 2026
…rage-rename

Bring in the latest apache#69605 (BE-1) and apply the renames of this PR to the
code it added since: refresh_resource / refresh_vault, the invalidation
tests, and the master changes it carries (S3FileReaderTest, the peer race
skip counter, the split spill writers).
xylaaaaa added a commit to xylaaaaa/doris that referenced this pull request Nov 9, 2026
…mment

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69605, apache#69082

Problem Summary: The bulk rename of S3ClientFactory to AwsSdkManager also
rewrote a comment in obj_storage_config.cpp that names the function the
config checks came from, so it read "the former AwsSdkManager::create".
That function was S3ClientFactory::create; AwsSdkManager never had a
create method. Restore the original name. A scan of comments that name a
method of AwsSdkManager or the ObjStorage* classes found no other method
that does not exist.

### Release note

None

### Check List (For Author)

- Test: No need to test (comment only)
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Related PR: apache#69605

Problem Summary: The typed configuration omitted the bucket from the S3
client cache key. Buckets with otherwise identical settings would share
a client and its connection pool. Carry the bucket from legacy inputs
through cache lookup and reset. S3 requests still supply their target
bucket individually.

### Release note

None

### Check List (For Author)

- Test: 69 ASAN BE unit tests passed across ObjClientFactoryTest,
  ObjStorageUriS3RulesTest and S3ClientFactoryTest. Header hygiene,
  formatting and clang-tidy passed.
- Behavior changed: No (preserves the pre-refactor S3 cache behavior)
- Does this need documentation: No
xylaaaaa pushed a commit to xylaaaaa/doris that referenced this pull request Nov 9, 2026
### What problem does this PR solve?

Related PR: apache#69082, apache#69605

Problem Summary: Keep the integration branch aligned with the split BE
refactor. Include the bucket supplied by the legacy configuration sources
in the S3 client cache key and preserve it through refresh. This keeps
separate connection pools for otherwise identical configurations that
name different buckets.

### Release note

None

### Check List (For Author)

- Test: FE and ASAN BE builds passed. A local two-bucket S3 regression
  passed with cold and warm caches. BE logs confirmed two client builds,
  and SQL and an independent S3 client verified the exported data.
  Header hygiene, formatting and clang-tidy passed.
- Behavior changed: No (preserves the pre-refactor S3 cache behavior)
- Does this need documentation: No
### What problem does this PR solve?

Related PR: apache#69605

Problem Summary: Public URL generation assumed an S3 client whenever an
endpoint matched the OSS private endpoint suffix. A custom Azure endpoint
with that suffix would fail before signing. Rewrite the endpoint in a
copy of the configured backend so its own client signs the public URL.

### Release note

None

### Check List (For Author)

- Test: FE and ASAN BE builds passed. All 74 selected BE unit tests
  passed with Azure enabled, including public and private signing.
  Header hygiene, clang-format 16 and clang-tidy passed.
- Behavior changed: No (restores pre-refactor public URL generation).
- Does this need documentation: No
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.

5 participants