Skip to content

Commit 4cc5558

Browse files
authored
Merge pull request #2619 from Altinity/backport/antalya-26.8/115686
Antalya 26.8, Backport of 115686: Parquet: size the initial footer read to 1% of the file within [128 KiB...2 MiB]
2 parents 18d0a55 + 9af546a commit 4cc5558

12 files changed

Lines changed: 81 additions & 18 deletions

File tree

‎src/Client/BuzzHouse/Generator/SessionSettings.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,7 @@ void loadFuzzerServerSettings(const FuzzConfig & fc)
19891989
"iceberg_insert_max_bytes_in_data_file",
19901990
"input_format_max_block_size_bytes",
19911991
"input_format_parquet_dictionary_filter_push_down",
1992+
"input_format_parquet_footer_read_size",
19921993
"input_format_parquet_local_file_min_bytes_for_seek",
19931994
"input_format_parquet_prefer_block_bytes",
19941995
"input_format_parquet_memory_low_watermark",

‎src/Core/FormatFactorySettings.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ When reading Parquet files, skip whole row groups based on the WHERE expressions
208208
)", 0) \
209209
DECLARE(UInt64, input_format_parquet_dictionary_filter_push_down, 1024 * 1024, R"(
210210
When reading Parquet files (with reader v3), skip whole row groups based on the WHERE/PREWHERE expressions and the dictionary page contents, when all data pages of a column chunk are dictionary-encoded. The value is the maximum dictionary page size (in bytes) for which this optimization is applied; set to 0 to disable. This takes precedence over the bloom filter when both are available.
211+
)", 0) \
212+
DECLARE(UInt64, input_format_parquet_footer_read_size, 0, R"(
213+
Size (in bytes) of the initial tail read that fetches the Parquet footer (`FileMetaData`) when opening a file with reader v3. `0` (default) sizes the read adaptively to the file - 1% of the file size, clamped to `[128 KiB, 2 MiB]` - so it usually covers the whole footer in a single read even for wide files with hundreds of columns. Set a non-zero value to force a fixed initial read size instead of the adaptive one; this is useful for very high-latency object storage where reading a larger tail up front avoids a second round trip. The value is clamped to the file size.
211214
)", 0) \
212215
DECLARE(Bool, input_format_parquet_enable_json_parsing, true, R"(
213216
When reading Parquet files, parse JSON columns as ClickHouse JSON Column.

‎src/Core/SettingsChangesHistory.cpp‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ const VersionToSettingsChangesMap & getSettingsChangesHistory()
4545
{
4646
{"azure_validate_etag_on_read", false, true, "New setting to detect concurrent in-place overwrites of Azure blobs during a read by pinning every `GET` to the listed `ETag` with `If-Match` and validating the `ETag` of the response, like `s3_validate_etag_on_read` does for S3. `compatibility` with versions before 26.10 restores the previous behavior (no validation)."},
4747
});
48+
addSettingsChanges(settings_changes_history, "26.8.27.20001.altinityantalya",
49+
{
50+
{"input_format_parquet_footer_read_size", 65536, 0, "New setting to override the adaptive Parquet footer initial read size with a fixed number of bytes; 0 keeps the adaptive behavior. Before this setting the reader always read a fixed 64 KiB tail. `compatibility` with 26.8 restores that fixed read."},
51+
});
4852
addSettingsChanges(settings_changes_history, "26.8",
4953
{
5054
{"use_aggregation_memory_tracker", false, false, "New setting. In this release it is disabled by default, which restores the two-level conversion of `GROUP BY` from before 26.7 (https://github.com/ClickHouse/ClickHouse/pull/107490)."},

‎src/Formats/FormatFactory.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ FormatSettings getFormatSettings(const ContextPtr & context, const Settings & se
227227
format_settings.parquet.filter_push_down = settings[Setting::input_format_parquet_filter_push_down];
228228
format_settings.parquet.bloom_filter_push_down = settings[Setting::input_format_parquet_bloom_filter_push_down];
229229
format_settings.parquet.dictionary_filter_push_down = settings[Setting::input_format_parquet_dictionary_filter_push_down];
230+
format_settings.parquet.footer_read_size = settings[Setting::input_format_parquet_footer_read_size];
230231
format_settings.parquet.page_filter_push_down = settings[Setting::input_format_parquet_page_filter_push_down];
231232
format_settings.parquet.spatial_filter_push_down = settings[Setting::input_format_parquet_spatial_filter_push_down];
232233
format_settings.parquet.use_offset_index = settings[Setting::input_format_parquet_use_offset_index];

‎src/Formats/FormatSettings.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ struct FormatSettings
374374
bool filter_push_down = true;
375375
bool bloom_filter_push_down = true;
376376
size_t dictionary_filter_push_down = 1024 * 1024;
377+
/// `0` sizes the initial footer read adaptively (1% of the file, clamped to [128 KiB, 2 MiB]).
378+
size_t footer_read_size = 0;
377379
bool page_filter_push_down = true;
378380
bool use_offset_index = true;
379381

‎src/Processors/Formats/Impl/Parquet/ReadManager.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void ReadManager::init(FormatParserSharedResourcesPtr parser_shared_resources_,
5454
parser_shared_resources = parser_shared_resources_;
5555

5656
if (reader.file_metadata.schema.empty())
57-
reader.file_metadata = Reader::readFileMetaData(reader.prefetcher);
57+
reader.file_metadata = Reader::readFileMetaData(reader.prefetcher, reader.options.format.parquet.footer_read_size);
5858

5959
if (buckets_to_read_)
6060
{

‎src/Processors/Formats/Impl/Parquet/Reader.cpp‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ void Reader::init(const ReadOptions & options_, const Block & sample_block_, For
218218
format_filter_info = format_filter_info_;
219219
}
220220

221-
parq::FileMetaData Reader::readFileMetaData(Prefetcher & prefetcher)
221+
parq::FileMetaData Reader::readFileMetaData(Prefetcher & prefetcher, size_t footer_read_size)
222222
{
223223
/// Parquet file ends with:
224224
/// * serialized FileMetaData struct,
@@ -229,9 +229,14 @@ parq::FileMetaData Reader::readFileMetaData(Prefetcher & prefetcher)
229229
if (file_size <= 8)
230230
throw Exception(ErrorCodes::INCORRECT_DATA, "Parquet file too short: {} bytes", file_size);
231231

232-
/// Read the last 64 KiB in hopes that FileMetaData is smaller than that.
233-
/// This is usually enough for files smaller than a few hundred MB.
234-
size_t initial_read_size = std::min(file_size, 64ul << 10);
232+
/// Read a tail sized to the file (1%, clamped to [128 KiB, 2 MiB]) so it usually covers the whole
233+
/// footer - FileMetaData for wider range of layouts - in one read. A non-zero
234+
/// `footer_read_size` overrides this adaptive size with a fixed read size.
235+
if (footer_read_size == 0)
236+
footer_read_size = std::clamp<size_t>(file_size / 100, 128ul << 10, 2ul << 20);
237+
/// The read must cover at least the 8-byte trailer (metadata size + magic) so the offsets below
238+
/// don't underflow; an explicit `footer_read_size` smaller than that is bumped up to 8.
239+
size_t initial_read_size = std::min(file_size, std::max<size_t>(footer_read_size, 8));
235240
PODArray<char> buf(initial_read_size);
236241
prefetcher.readSync(buf.data(), initial_read_size, file_size - initial_read_size);
237242

‎src/Processors/Formats/Impl/Parquet/Reader.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,8 @@ struct Reader
566566

567567
void init(const ReadOptions & options_, const Block & sample_block_, FormatFilterInfoPtr format_filter_info_);
568568

569-
static parq::FileMetaData readFileMetaData(Prefetcher & prefetcher);
569+
/// `footer_read_size` overrides the initial footer read size; 0 sizes it adaptively to the file.
570+
static parq::FileMetaData readFileMetaData(Prefetcher & prefetcher, size_t footer_read_size);
570571
void prefilterAndInitRowGroups(const std::optional<std::unordered_set<UInt64>> & row_groups_to_read);
571572
void preparePrewhere();
572573

‎src/Processors/Formats/Impl/ParquetV3BlockInputFormat.cpp‎

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
#include <IO/VarInt.h>
1616
#include <IO/copyData.h>
1717
#include <Interpreters/Context.h>
18-
#include <Processors/Formats/Impl/ArrowBufferedStreams.h>
1918
#include <Processors/Formats/Impl/Parquet/SchemaConverter.h>
20-
#include <parquet/file_reader.h>
2119

2220
namespace DB
2321
{
@@ -115,11 +113,11 @@ parquet::format::FileMetaData ParquetV3BlockInputFormat::getFileMetadata(Parquet
115113
String etag = object_with_metadata->metadata->etag;
116114
ParquetMetadataCacheKey cache_key = ParquetMetadataCache::createKey(file_name, etag);
117115
return metadata_cache->getOrSetMetadata(
118-
cache_key, [&]() { return Parquet::Reader::readFileMetaData(prefetcher); });
116+
cache_key, [&]() { return Parquet::Reader::readFileMetaData(prefetcher, read_options.format.parquet.footer_read_size); });
119117
}
120118
else
121119
{
122-
return Parquet::Reader::readFileMetaData(prefetcher);
120+
return Parquet::Reader::readFileMetaData(prefetcher, read_options.format.parquet.footer_read_size);
123121
}
124122
}
125123

@@ -275,7 +273,7 @@ void NativeParquetSchemaReader::initializeIfNeeded()
275273
return;
276274
Parquet::Prefetcher prefetcher;
277275
prefetcher.init(&in, read_options, /*parser_shared_resources_=*/ nullptr);
278-
file_metadata = Parquet::Reader::readFileMetaData(prefetcher);
276+
file_metadata = Parquet::Reader::readFileMetaData(prefetcher, read_options.format.parquet.footer_read_size);
279277
initialized = true;
280278
}
281279

@@ -350,12 +348,13 @@ void registerParquetFileBucketInfo(std::unordered_map<String, FileBucketInfoPtr>
350348

351349
std::vector<FileBucketInfoPtr> ParquetBucketSplitter::splitToBuckets(size_t bucket_size, ReadBuffer & buf, const FormatSettings & format_settings_)
352350
{
353-
std::atomic<int> is_stopped = false;
354-
auto arrow_file = asArrowFile(buf, format_settings_, is_stopped, "Parquet", PARQUET_MAGIC_BYTES, /* avoid_buffering */ true, nullptr);
355-
auto metadata = parquet::ReadMetaData(arrow_file);
351+
Parquet::ReadOptions read_options = convertReadOptions(format_settings_);
352+
Parquet::Prefetcher prefetcher;
353+
prefetcher.init(&buf, read_options, /*parser_shared_resources_=*/ nullptr);
354+
auto metadata = Parquet::Reader::readFileMetaData(prefetcher, read_options.format.parquet.footer_read_size);
356355
std::vector<size_t> bucket_sizes;
357-
for (int i = 0; i < metadata->num_row_groups(); ++i)
358-
bucket_sizes.push_back(metadata->RowGroup(i)->total_byte_size());
356+
for (const auto & row_group : metadata.row_groups)
357+
bucket_sizes.push_back(size_t(row_group.total_byte_size));
359358

360359
std::vector<std::vector<size_t>> buckets;
361360
size_t current_weight = 0;

‎tests/integration/test_storage_delta/test.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2751,10 +2751,16 @@ def test_column_pruning(started_cluster):
27512751

27522752
table_function = f"deltaLake('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/{result_file}/', 'minio', '{minio_secret_key}')"
27532753

2754+
# `input_format_parquet_footer_read_size` is pinned so that the number of bytes read stays a
2755+
# measure of column pruning only. With the adaptive default the initial footer read is at least
2756+
# 128 KiB, which is more than this (much smaller) file, so the whole file would be read and the
2757+
# numbers below would no longer say anything about which columns were fetched.
2758+
footer_read_size = 64 * 1024
2759+
27542760
query_id = f"query_{TABLE_NAME}_1"
27552761
sum = int(
27562762
instance.query(
2757-
f"SELECT sum(id) FROM {table_function} SETTINGS allow_experimental_delta_kernel_rs=0, max_read_buffer_size_remote_fs=100, remote_read_min_bytes_for_seek=1",
2763+
f"SELECT sum(id) FROM {table_function} SETTINGS allow_experimental_delta_kernel_rs=0, max_read_buffer_size_remote_fs=100, remote_read_min_bytes_for_seek=1, input_format_parquet_footer_read_size={footer_read_size}",
27582764
query_id=query_id,
27592765
)
27602766
)
@@ -2770,7 +2776,7 @@ def test_column_pruning(started_cluster):
27702776
query_id = f"query_{TABLE_NAME}_2"
27712777
assert sum == int(
27722778
instance.query(
2773-
f"SELECT sum(id) FROM {table_function} SETTINGS enable_filesystem_cache=0, max_read_buffer_size_remote_fs=100, remote_read_min_bytes_for_seek=1, use_parquet_metadata_cache=0",
2779+
f"SELECT sum(id) FROM {table_function} SETTINGS enable_filesystem_cache=0, max_read_buffer_size_remote_fs=100, remote_read_min_bytes_for_seek=1, use_parquet_metadata_cache=0, input_format_parquet_footer_read_size={footer_read_size}",
27742780
query_id=query_id,
27752781
)
27762782
)

0 commit comments

Comments
 (0)