Skip to content

Commit 0d3285f

Browse files
committed
Merge remote-tracking branch 'origin/antalya-25.8' into backports/antalya-25.8/79012
2 parents 9c390fc + c24224b commit 0d3285f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+707
-260
lines changed

.github/workflows/master.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4178,7 +4178,7 @@ jobs:
41784178
secrets: inherit
41794179
with:
41804180
runner_type: altinity-on-demand, altinity-regression-tester
4181-
commit: 00a50b5b8f12c9c603b9a3fa17dd2c5ea2012cac
4181+
commit: 8d2c6d2072771d450a47faca38966da7493821e1
41824182
arch: release
41834183
build_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
41844184
timeout_minutes: 300
@@ -4190,7 +4190,7 @@ jobs:
41904190
secrets: inherit
41914191
with:
41924192
runner_type: altinity-on-demand, altinity-regression-tester-aarch64
4193-
commit: 00a50b5b8f12c9c603b9a3fa17dd2c5ea2012cac
4193+
commit: 8d2c6d2072771d450a47faca38966da7493821e1
41944194
arch: aarch64
41954195
build_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
41964196
timeout_minutes: 300

.github/workflows/pull_request.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4134,7 +4134,7 @@ jobs:
41344134
secrets: inherit
41354135
with:
41364136
runner_type: altinity-on-demand, altinity-regression-tester
4137-
commit: 00a50b5b8f12c9c603b9a3fa17dd2c5ea2012cac
4137+
commit: 8d2c6d2072771d450a47faca38966da7493821e1
41384138
arch: release
41394139
build_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
41404140
timeout_minutes: 300
@@ -4146,7 +4146,7 @@ jobs:
41464146
secrets: inherit
41474147
with:
41484148
runner_type: altinity-on-demand, altinity-regression-tester-aarch64
4149-
commit: 00a50b5b8f12c9c603b9a3fa17dd2c5ea2012cac
4149+
commit: 8d2c6d2072771d450a47faca38966da7493821e1
41504150
arch: aarch64
41514151
build_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
41524152
timeout_minutes: 300

ci/praktika/yaml_additional_templates.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class AltinityWorkflowTemplates:
3535
echo "Workflow Run Report: [View Report]($REPORT_LINK)" >> $GITHUB_STEP_SUMMARY
3636
"""
3737
# Additional jobs
38-
REGRESSION_HASH = "00a50b5b8f12c9c603b9a3fa17dd2c5ea2012cac"
38+
REGRESSION_HASH = "8d2c6d2072771d450a47faca38966da7493821e1"
3939
ALTINITY_JOBS = {
4040
"GrypeScan": r"""
4141
GrypeScanServer:

docs/en/engines/table-engines/mergetree-family/part_export.md

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Overview
44

5-
The `ALTER TABLE EXPORT PART` command exports individual MergeTree data parts to object storage (S3, Azure Blob Storage, etc.), typically in Parquet format.
5+
The `ALTER TABLE EXPORT PART` command exports individual MergeTree data parts to object storage (S3, Azure Blob Storage, etc.), typically in Parquet format. A commit file is shipped to the same destination directory containing all data files exported within that transaction.
66

77
**Key Characteristics:**
88
- **Experimental feature** - must be enabled via `allow_experimental_export_merge_tree_part` setting
@@ -48,6 +48,18 @@ Source and destination tables must be 100% compatible:
4848
- **Default**: `false`
4949
- **Description**: If set to `true`, it will overwrite the file. Otherwise, fails with exception.
5050

51+
### `export_merge_tree_part_max_bytes_per_file` (Optional)
52+
53+
- **Type**: `UInt64`
54+
- **Default**: `0`
55+
- **Description**: Maximum number of bytes to write to a single file when exporting a merge tree part. 0 means no limit. This is not a hard limit, and it highly depends on the output format granularity and input source chunk size. Using this might break idempotency, use it with care.
56+
57+
### `export_merge_tree_part_max_rows_per_file` (Optional)
58+
59+
- **Type**: `UInt64`
60+
- **Default**: `0`
61+
- **Description**: Maximum number of rows to write to a single file when exporting a merge tree part. 0 means no limit. This is not a hard limit, and it highly depends on the output format granularity and input source chunk size. Using this might break idempotency, use it with care.
62+
5163
## Examples
5264

5365
### Basic Export to S3
@@ -93,7 +105,7 @@ destination_database: default
93105
destination_table: destination_table
94106
create_time: 2025-11-19 09:09:11
95107
part_name: 20251016-365_1_1_0
96-
destination_file_path: table_root/eventDate=2025-10-16/retention=365/20251016-365_1_1_0_17B2F6CD5D3C18E787C07AE3DAF16EB1.parquet
108+
destination_file_paths: ['table_root/eventDate=2025-10-16/retention=365/20251016-365_1_1_0_17B2F6CD5D3C18E787C07AE3DAF16EB1.1.parquet']
97109
elapsed: 2.04845441
98110
rows_read: 1138688 -- 1.14 million
99111
total_rows_to_read: 550961374 -- 550.96 million
@@ -138,7 +150,8 @@ partition_id: 2021
138150
partition: 2021
139151
part_type: Compact
140152
disk_name: default
141-
path_on_disk: year=2021/2021_0_0_0_78C704B133D41CB0EF64DD2A9ED3B6BA.parquet
153+
path_on_disk:
154+
remote_file_paths ['year=2021/2021_0_0_0_78C704B133D41CB0EF64DD2A9ED3B6BA.1.parquet']
142155
rows: 1
143156
size_in_bytes: 272
144157
merged_from: ['2021_0_0_0']
@@ -158,3 +171,99 @@ ProfileEvents: {}
158171
- `PartsExportDuplicated` - Number of part exports that failed because target already exists.
159172
- `PartsExportTotalMilliseconds` - Total time
160173

174+
### Split large files
175+
176+
```sql
177+
alter table big_table export part '2025_0_32_3' to table replicated_big_destination SETTINGS export_merge_tree_part_max_bytes_per_file=10000000, output_format_parquet_row_group_size_bytes=5000000;
178+
179+
arthur :) select * from system.exports;
180+
181+
SELECT *
182+
FROM system.exports
183+
184+
Query id: d78d9ce5-cfbc-4957-b7dd-bc8129811634
185+
186+
Row 1:
187+
──────
188+
source_database: default
189+
source_table: big_table
190+
destination_database: default
191+
destination_table: replicated_big_destination
192+
create_time: 2025-12-15 13:12:48
193+
part_name: 2025_0_32_3
194+
destination_file_paths: ['replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.1.parquet','replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.2.parquet','replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.3.parquet','replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.4.parquet']
195+
elapsed: 14.360427274
196+
rows_read: 10256384 -- 10.26 million
197+
total_rows_to_read: 10485760 -- 10.49 million
198+
total_size_bytes_compressed: 83779395 -- 83.78 million
199+
total_size_bytes_uncompressed: 10611691600 -- 10.61 billion
200+
bytes_read_uncompressed: 10440998912 -- 10.44 billion
201+
memory_usage: 89795477 -- 89.80 million
202+
peak_memory_usage: 107362133 -- 107.36 million
203+
204+
1 row in set. Elapsed: 0.014 sec.
205+
206+
arthur :) select * from system.part_log where event_type = 'ExportPart' order by event_time desc limit 1 format Vertical;
207+
208+
SELECT *
209+
FROM system.part_log
210+
WHERE event_type = 'ExportPart'
211+
ORDER BY event_time DESC
212+
LIMIT 1
213+
FORMAT Vertical
214+
215+
Query id: 95128b01-b751-4726-8e3e-320728ac6af7
216+
217+
Row 1:
218+
──────
219+
hostname: arthur
220+
query_id:
221+
event_type: ExportPart
222+
merge_reason: NotAMerge
223+
merge_algorithm: Undecided
224+
event_date: 2025-12-15
225+
event_time: 2025-12-15 13:13:03
226+
event_time_microseconds: 2025-12-15 13:13:03.197492
227+
duration_ms: 14673
228+
database: default
229+
table: big_table
230+
table_uuid: a3eeeea0-295c-41a3-84ef-6b5463dbbe8c
231+
part_name: 2025_0_32_3
232+
partition_id: 2025
233+
partition: 2025
234+
part_type: Wide
235+
disk_name: default
236+
path_on_disk: ./store/a3e/a3eeeea0-295c-41a3-84ef-6b5463dbbe8c/2025_0_32_3/
237+
remote_file_paths: ['replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.1.parquet','replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.2.parquet','replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.3.parquet','replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.4.parquet']
238+
rows: 10485760 -- 10.49 million
239+
size_in_bytes: 83779395 -- 83.78 million
240+
merged_from: ['2025_0_32_3']
241+
bytes_uncompressed: 10611691600 -- 10.61 billion
242+
read_rows: 10485760 -- 10.49 million
243+
read_bytes: 10674503680 -- 10.67 billion
244+
peak_memory_usage: 107362133 -- 107.36 million
245+
error: 0
246+
exception:
247+
ProfileEvents: {}
248+
249+
1 row in set. Elapsed: 0.044 sec.
250+
251+
arthur :) select _path, formatReadableSize(_size) as _size from s3(s3_conn, filename='**', format=One);
252+
253+
SELECT
254+
_path,
255+
formatReadableSize(_size) AS _size
256+
FROM s3(s3_conn, filename = '**', format = One)
257+
258+
Query id: c48ae709-f590-4d1b-8158-191f8d628966
259+
260+
┌─_path────────────────────────────────────────────────────────────────────────────────┬─_size─────┐
261+
1. │ test/replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.1.parquet │ 17.36 MiB │
262+
2. │ test/replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.2.parquet │ 17.32 MiB │
263+
3. │ test/replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.4.parquet │ 5.04 MiB │
264+
4. │ test/replicated_big/year=2025/2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7.3.parquet │ 17.40 MiB │
265+
5. │ test/replicated_big/year=2025/commit_2025_0_32_3_E439C23833C39C6E5104F6F4D1048BE7 │ 320.00 B │
266+
└──────────────────────────────────────────────────────────────────────────────────────┴───────────┘
267+
268+
5 rows in set. Elapsed: 0.072 sec.
269+
```

src/Core/Settings.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6899,6 +6899,14 @@ Possible values:
68996899
- `` (empty value) - use session timezone
69006900
69016901
Default value is `UTC`.
6902+
)", 0) \
6903+
DECLARE(UInt64, export_merge_tree_part_max_bytes_per_file, 0, R"(
6904+
Maximum number of bytes to write to a single file when exporting a merge tree part. 0 means no limit.
6905+
This is not a hard limit, and it highly depends on the output format granularity and input source chunk size.
6906+
)", 0) \
6907+
DECLARE(UInt64, export_merge_tree_part_max_rows_per_file, 0, R"(
6908+
Maximum number of rows to write to a single file when exporting a merge tree part. 0 means no limit.
6909+
This is not a hard limit, and it highly depends on the output format granularity and input source chunk size.
69026910
)", 0) \
69036911
\
69046912
/* ####################################################### */ \
@@ -7103,9 +7111,6 @@ Use Shuffle aggregation strategy instead of PartialAggregation + Merge in distri
71037111
)", EXPERIMENTAL) \
71047112
DECLARE(Bool, allow_experimental_iceberg_read_optimization, true, R"(
71057113
Allow Iceberg read optimization based on Iceberg metadata.
7106-
)", EXPERIMENTAL) \
7107-
DECLARE(Bool, allow_retries_in_cluster_requests, false, R"(
7108-
Allow retries in cluster request, when one node goes offline
71097114
)", EXPERIMENTAL) \
71107115
DECLARE(Bool, object_storage_remote_initiator, false, R"(
71117116
Execute request to object storage as remote on one of object_storage_cluster nodes.
@@ -7228,6 +7233,7 @@ Sets the evaluation time to be used with promql dialect. 'auto' means the curren
72287233
MAKE_OBSOLETE(M, Bool, allow_experimental_shared_set_join, true) \
72297234
MAKE_OBSOLETE(M, UInt64, min_external_sort_block_bytes, 100_MiB) \
72307235
MAKE_OBSOLETE(M, UInt64, distributed_cache_read_alignment, 0) \
7236+
MAKE_OBSOLETE(M, Bool, allow_retries_in_cluster_requests, false) \
72317237
/** The section above is for obsolete settings. Do not add anything there. */
72327238
#endif /// __CLION_IDE__
72337239

src/Core/SettingsChangesHistory.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ const VersionToSettingsChangesMap & getSettingsChangesHistory()
5959
{"input_format_parquet_use_native_reader_v3", false, true, "Seems stable"},
6060
{"input_format_parquet_verify_checksums", true, true, "New setting."},
6161
{"output_format_parquet_write_checksums", false, true, "New setting."},
62+
{"export_merge_tree_part_max_bytes_per_file", 0, 0, "New setting."},
63+
{"export_merge_tree_part_max_rows_per_file", 0, 0, "New setting."},
6264
});
6365
addSettingsChanges(settings_changes_history, "25.8",
6466
{

src/Databases/DataLake/RestCatalog.cpp

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
#include <Poco/URI.h>
2929
#include <Poco/JSON/Array.h>
3030
#include <Poco/JSON/Parser.h>
31+
#include <Poco/Net/HTTPClientSession.h>
32+
#include <Poco/Net/HTTPResponse.h>
33+
#include <Poco/Net/HTTPSClientSession.h>
34+
#include <Poco/Net/SSLManager.h>
35+
#include <Poco/StreamCopier.h>
3136

3237

3338
namespace DB::ErrorCodes
@@ -203,12 +208,11 @@ std::string RestCatalog::retrieveAccessToken() const
203208
/// 1. support oauth2-server-uri
204209
/// https://github.com/apache/iceberg/blob/918f81f3c3f498f46afcea17c1ac9cdc6913cb5c/open-api/rest-catalog-open-api.yaml#L183C82-L183C99
205210

206-
DB::HTTPHeaderEntries headers;
207-
headers.emplace_back("Content-Type", "application/x-www-form-urlencoded");
208-
headers.emplace_back("Accepts", "application/json; charset=UTF-8");
209-
210211
Poco::URI url;
211212
DB::ReadWriteBufferFromHTTP::OutStreamCallback out_stream_callback;
213+
size_t body_size = 0;
214+
String body;
215+
212216
if (oauth_server_uri.empty() && !oauth_server_use_request_body)
213217
{
214218
url = Poco::URI(base_url / oauth_tokens_endpoint);
@@ -223,11 +227,20 @@ std::string RestCatalog::retrieveAccessToken() const
223227
}
224228
else
225229
{
230+
String encoded_auth_scope;
231+
String encoded_client_id;
232+
String encoded_client_secret;
233+
Poco::URI::encode(auth_scope, auth_scope, encoded_auth_scope);
234+
Poco::URI::encode(client_id, client_id, encoded_client_id);
235+
Poco::URI::encode(client_secret, client_secret, encoded_client_secret);
236+
237+
body = fmt::format(
238+
"grant_type=client_credentials&scope={}&client_id={}&client_secret={}",
239+
encoded_auth_scope, encoded_client_id, encoded_client_secret);
240+
body_size = body.size();
226241
out_stream_callback = [&](std::ostream & os)
227242
{
228-
os << fmt::format(
229-
"grant_type=client_credentials&scope={}&client_id={}&client_secret={}",
230-
auth_scope, client_id, client_secret);
243+
os << body;
231244
};
232245

233246
if (oauth_server_uri.empty())
@@ -237,19 +250,23 @@ std::string RestCatalog::retrieveAccessToken() const
237250
}
238251

239252
const auto & context = getContext();
240-
auto wb = DB::BuilderRWBufferFromHTTP(url)
241-
.withConnectionGroup(DB::HTTPConnectionGroupType::HTTP)
242-
.withMethod(Poco::Net::HTTPRequest::HTTP_POST)
243-
.withSettings(context->getReadSettings())
244-
.withTimeouts(DB::ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), context->getServerSettings()))
245-
.withHostFilter(&context->getRemoteHostFilter())
246-
.withOutCallback(std::move(out_stream_callback))
247-
.withSkipNotFound(false)
248-
.withHeaders(headers)
249-
.create(credentials);
253+
auto timeouts = DB::ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), context->getServerSettings());
254+
auto session = makeHTTPSession(DB::HTTPConnectionGroupType::HTTP, url, timeouts, {});
255+
256+
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, url.getPathAndQuery(),
257+
Poco::Net::HTTPMessage::HTTP_1_1);
258+
request.setContentType("application/x-www-form-urlencoded");
259+
request.setContentLength(body_size);
260+
request.set("Accept", "application/json");
261+
262+
std::ostream & os = session->sendRequest(request);
263+
out_stream_callback(os);
264+
265+
Poco::Net::HTTPResponse response;
266+
std::istream & rs = session->receiveResponse(response);
250267

251268
std::string json_str;
252-
readJSONObjectPossiblyInvalid(json_str, *wb);
269+
Poco::StreamCopier::copyToString(rs, json_str);
253270

254271
Poco::JSON::Parser parser;
255272
Poco::Dynamic::Var res_json = parser.parse(json_str);

src/Interpreters/PartLog.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ ColumnsDescription PartLogElement::getColumnsDescription()
134134
{"part_type", std::make_shared<DataTypeString>(), "The type of the part. Possible values: Wide and Compact."},
135135
{"disk_name", std::make_shared<DataTypeString>(), "The disk name data part lies on."},
136136
{"path_on_disk", std::make_shared<DataTypeString>(), "Absolute path to the folder with data part files."},
137+
{"remote_file_paths", std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>()), "In case of an export operation to remote storages, the file paths a given export generated"},
137138

138139
{"rows", std::make_shared<DataTypeUInt64>(), "The number of rows in the data part."},
139140
{"size_in_bytes", std::make_shared<DataTypeUInt64>(), "Size of the data part on disk in bytes."},
@@ -187,6 +188,12 @@ void PartLogElement::appendToBlock(MutableColumns & columns) const
187188
columns[i++]->insert(disk_name);
188189
columns[i++]->insert(path_on_disk);
189190

191+
Array remote_file_paths_array;
192+
remote_file_paths_array.reserve(remote_file_paths.size());
193+
for (const auto & remote_file_path : remote_file_paths)
194+
remote_file_paths_array.push_back(remote_file_path);
195+
columns[i++]->insert(remote_file_paths_array);
196+
190197
columns[i++]->insert(rows);
191198
columns[i++]->insert(bytes_compressed_on_disk);
192199

src/Interpreters/PartLog.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ struct PartLogElement
7171
String partition;
7272
String disk_name;
7373
String path_on_disk;
74+
std::vector<String> remote_file_paths;
7475

7576
MergeTreeDataPartType part_type;
7677

0 commit comments

Comments
 (0)