Skip to content

Commit 2c96ce7

Browse files
authored
feat(parquet): support parquet metadata cache (alibaba#373)
1 parent 0ea520a commit 2c96ce7

15 files changed

Lines changed: 491 additions & 41 deletions

docs/source/user_guide.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ User Guide
2525
user_guide/snapshot
2626
user_guide/manifest
2727
user_guide/manifest_cache
28+
user_guide/parquet_metadata_cache
2829
user_guide/data_types
2930
user_guide/primary_key_table
3031
user_guide/append_only_table
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
.. Copyright 2026-present Alibaba Inc.
2+
3+
.. Licensed under the Apache License, Version 2.0 (the "License");
4+
.. you may not use this file except in compliance with the License.
5+
.. You may obtain a copy of the License at
6+
7+
.. http://www.apache.org/licenses/LICENSE-2.0
8+
9+
.. Unless required by applicable law or agreed to in writing, software
10+
.. distributed under the License is distributed on an "AS IS" BASIS,
11+
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
.. See the License for the specific language governing permissions and
13+
.. limitations under the License.
14+
15+
Parquet Metadata Cache
16+
======================
17+
18+
Overview
19+
--------
20+
21+
paimon-cpp can cache serialized Parquet metadata footer bytes for Parquet data files.
22+
The cache is used by ``ParquetReaderBuilder`` before opening the Arrow Parquet
23+
reader. On a cache miss, paimon-cpp loads the Parquet file metadata, serializes
24+
it as a complete metadata footer, and stores those bytes in the public
25+
``Cache`` abstraction. On a cache hit, paimon-cpp parses the cached footer bytes into
26+
``parquet::FileMetaData`` and passes the metadata to the Parquet reader.
27+
28+
The cache stores serialized metadata footer bytes instead of caching a
29+
``parquet::FileMetaData`` instance. This keeps the cache value compact and
30+
similar to manifest cache values: the cache weight follows the actual cached
31+
bytes, while the Parquet library still owns metadata parsing and validation.
32+
33+
This optimization is useful when the same Parquet files are opened repeatedly
34+
in the same process, for example repeated ``get`` or ``scan`` requests over the
35+
same snapshot. On a cache hit, the read path avoids reading the Parquet footer
36+
bytes from the filesystem again. paimon-cpp still parses the cached footer bytes
37+
into ``parquet::FileMetaData`` for each reader open. Data pages, page indexes,
38+
and column chunks are still read from the file as usual.
39+
40+
Configuration
41+
-------------
42+
43+
Parquet metadata caching is disabled by default. Embedding applications that
44+
need it can provide a custom ``Cache`` implementation and inject it through
45+
``ScanContextBuilder`` or ``ReadContextBuilder``. Parquet reader builders
46+
receive the cache from the read context and create cache keys with
47+
``CacheKind::DATA_FILE_FOOTER`` internally.
48+
49+
The cache key represents the file footer and is created from the file URI with
50+
position ``-1`` and length ``-1``. Callers do not need to construct this key
51+
directly; they only need to route ``CacheKind::DATA_FILE_FOOTER`` entries to an
52+
appropriate cache backend.
53+
54+
Example:
55+
56+
.. code-block:: cpp
57+
58+
class RoutingCache : public paimon::Cache {
59+
public:
60+
RoutingCache(std::shared_ptr<paimon::Cache> default_cache,
61+
std::shared_ptr<paimon::Cache> parquet_metadata_cache)
62+
: default_cache_(std::move(default_cache)),
63+
parquet_metadata_cache_(std::move(parquet_metadata_cache)) {}
64+
65+
paimon::Result<std::shared_ptr<paimon::CacheValue>> Get(
66+
const std::shared_ptr<paimon::CacheKey>& key,
67+
std::function<paimon::Result<std::shared_ptr<paimon::CacheValue>>(
68+
const std::shared_ptr<paimon::CacheKey>&)> supplier) override {
69+
return Select(key)->Get(key, std::move(supplier));
70+
}
71+
72+
// Put(), Invalidate(), InvalidateAll(), and Size() route in the same way.
73+
74+
private:
75+
std::shared_ptr<paimon::Cache> Select(
76+
const std::shared_ptr<paimon::CacheKey>& key) const {
77+
return key && key->GetKind() == paimon::CacheKind::DATA_FILE_FOOTER
78+
? parquet_metadata_cache_
79+
: default_cache_;
80+
}
81+
82+
std::shared_ptr<paimon::Cache> default_cache_;
83+
std::shared_ptr<paimon::Cache> parquet_metadata_cache_;
84+
};
85+
86+
auto cache = std::make_shared<RoutingCache>(
87+
std::make_shared<MyDefaultCache>(),
88+
std::make_shared<MyParquetMetadataCache>());
89+
90+
paimon::ScanContextBuilder scan_builder(table_path);
91+
scan_builder.WithCache(cache);
92+
93+
paimon::ReadContextBuilder read_builder(table_path);
94+
read_builder.WithCache(cache);
95+
96+
Passing ``nullptr`` or omitting ``WithCache()`` leaves Parquet metadata caching
97+
disabled. If a file URI cannot be obtained, paimon-cpp also bypasses the cache
98+
and opens the Parquet file normally.
99+
100+
Future Optimizations
101+
--------------------
102+
103+
- Add hit, miss, bypass, and eviction metrics for Parquet metadata cache.
104+
- Add single-flight loading for high-concurrency misses on the same Parquet
105+
file.
106+
- Evaluate sharing cached metadata footer bytes with page-index prefetch logic when
107+
those read paths can use the same cache abstraction.

include/paimon/cache/cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class CacheValue;
3232
enum class CacheKind {
3333
DEFAULT,
3434
MANIFEST,
35+
DATA_FILE_FOOTER,
3536
};
3637

3738
class PAIMON_EXPORT CacheKey {

include/paimon/format/reader_builder.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "paimon/type_fwd.h"
2525

2626
namespace paimon {
27+
class Cache;
2728

2829
/// Create a file batch reader based on the file path. Allows you to specify memory pool.
2930
class PAIMON_EXPORT ReaderBuilder {
@@ -33,6 +34,12 @@ class PAIMON_EXPORT ReaderBuilder {
3334
/// Set memory pool to use.
3435
virtual ReaderBuilder* WithMemoryPool(const std::shared_ptr<MemoryPool>& pool) = 0;
3536

37+
/// Inject a cache for reader-specific immutable metadata.
38+
virtual ReaderBuilder* WithCache(const std::shared_ptr<Cache>& cache) {
39+
(void)cache;
40+
return this;
41+
}
42+
3643
/// Build a file batch reader based on the created `InputStream`.
3744
virtual Result<std::unique_ptr<FileBatchReader>> Build(
3845
const std::shared_ptr<InputStream>& path) const = 0;

src/paimon/core/manifest/manifest_file_test.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
#include "paimon/fs/local/local_file_system.h"
4242
#include "paimon/memory/memory_pool.h"
4343
#include "paimon/testing/utils/binary_row_generator.h"
44-
#include "paimon/testing/utils/manifest_cache_test_utils.h"
44+
#include "paimon/testing/utils/counting_cache_test_utils.h"
4545
#include "paimon/testing/utils/testharness.h"
4646

4747
namespace paimon::test {
@@ -277,7 +277,8 @@ TEST_F(ManifestFileTest, TestManifestCacheIsDisabledWithoutInjectedCache) {
277277
TEST_F(ManifestFileTest, TestManifestCacheReusesCachedBytes) {
278278
auto pool = GetDefaultPool();
279279
auto counting_file_system = std::make_shared<CountingFileSystem>();
280-
auto manifest_cache = std::make_shared<CountingManifestRoutingCache>();
280+
auto manifest_cache =
281+
std::make_shared<CountingRoutingCache>(CacheKind::MANIFEST, 64 * 1024 * 1024);
281282
ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileFormat> file_format,
282283
FileFormatFactory::Get("orc", {}));
283284
std::string root_path = paimon::test::GetDataDir() + "/orc/append_09.db/append_09";

src/paimon/core/operation/abstract_split_read.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ Result<std::unique_ptr<ReaderBuilder>> AbstractSplitRead::PrepareReaderBuilder(
117117
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<ReaderBuilder> reader_builder,
118118
file_format->CreateReaderBuilder(options_.GetReadBatchSize()));
119119
reader_builder->WithMemoryPool(pool_);
120+
reader_builder->WithCache(options_.GetCache());
120121
return reader_builder;
121122
}
122123

src/paimon/format/parquet/page_filtered_row_group_reader_test.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,9 @@ class PageFilteredRowGroupReaderTest : public ::testing::Test {
118118

119119
std::map<std::string, std::string> options;
120120
options[PARQUET_READ_ENABLE_PAGE_INDEX_FILTER] = "true";
121-
ASSERT_OK_AND_ASSIGN(
122-
auto batch_reader,
123-
ParquetFileBatchReader::Create(std::move(in_stream), arrow_pool_, options, batch_size));
121+
ASSERT_OK_AND_ASSIGN(auto batch_reader, ParquetFileBatchReader::Create(
122+
std::move(in_stream), options, batch_size,
123+
/*file_metadata=*/nullptr, arrow_pool_));
124124
auto c_schema = std::make_unique<ArrowSchema>();
125125
ASSERT_TRUE(arrow::ExportSchema(*read_schema, c_schema.get()).ok());
126126
ASSERT_OK(batch_reader->SetReadSchema(c_schema.get(), predicate,

src/paimon/format/parquet/parquet_file_batch_reader.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ ParquetFileBatchReader::ParquetFileBatchReader(
7171

7272
Result<std::unique_ptr<ParquetFileBatchReader>> ParquetFileBatchReader::Create(
7373
std::shared_ptr<arrow::io::RandomAccessFile>&& input_stream,
74-
const std::shared_ptr<arrow::MemoryPool>& pool,
75-
const std::map<std::string, std::string>& options, int32_t batch_size) {
74+
const std::map<std::string, std::string>& options, int32_t batch_size,
75+
std::shared_ptr<::parquet::FileMetaData> file_metadata,
76+
const std::shared_ptr<arrow::MemoryPool>& pool) {
7677
try {
7778
assert(input_stream);
7879
PAIMON_ASSIGN_OR_RAISE(::parquet::ReaderProperties reader_properties,
@@ -82,7 +83,8 @@ Result<std::unique_ptr<ParquetFileBatchReader>> ParquetFileBatchReader::Create(
8283
CreateArrowReaderProperties(pool, options, batch_size));
8384

8485
::parquet::arrow::FileReaderBuilder file_reader_builder;
85-
PAIMON_RETURN_NOT_OK_FROM_ARROW(file_reader_builder.Open(input_stream, reader_properties));
86+
PAIMON_RETURN_NOT_OK_FROM_ARROW(
87+
file_reader_builder.Open(input_stream, reader_properties, std::move(file_metadata)));
8688

8789
std::unique_ptr<::parquet::arrow::FileReader> file_reader;
8890
PAIMON_RETURN_NOT_OK_FROM_ARROW(file_reader_builder.memory_pool(pool.get())

src/paimon/format/parquet/parquet_file_batch_reader.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ namespace io {
5151
class RandomAccessFile;
5252
} // namespace io
5353
} // namespace arrow
54+
namespace parquet {
55+
class FileMetaData;
56+
} // namespace parquet
5457
namespace paimon {
5558
class Metrics;
5659
class Predicate;
@@ -63,8 +66,13 @@ class ParquetFileBatchReader : public PrefetchFileBatchReader {
6366
public:
6467
static Result<std::unique_ptr<ParquetFileBatchReader>> Create(
6568
std::shared_ptr<arrow::io::RandomAccessFile>&& input_stream,
69+
const std::map<std::string, std::string>& options, int32_t batch_size,
70+
std::shared_ptr<::parquet::FileMetaData> file_metadata,
71+
const std::shared_ptr<arrow::MemoryPool>& pool);
72+
73+
static Result<::parquet::ReaderProperties> CreateReaderProperties(
6674
const std::shared_ptr<arrow::MemoryPool>& pool,
67-
const std::map<std::string, std::string>& options, int32_t batch_size);
75+
const std::map<std::string, std::string>& options);
6876

6977
// For timestamp type, we return the schema stored in file, e.g., second in parquet file will
7078
// store as milli.
@@ -128,10 +136,6 @@ class ParquetFileBatchReader : public PrefetchFileBatchReader {
128136
const std::map<std::string, std::string>& options,
129137
const std::shared_ptr<arrow::MemoryPool>& arrow_pool);
130138

131-
static Result<::parquet::ReaderProperties> CreateReaderProperties(
132-
const std::shared_ptr<arrow::MemoryPool>& pool,
133-
const std::map<std::string, std::string>& options);
134-
135139
static Result<::parquet::ArrowReaderProperties> CreateArrowReaderProperties(
136140
const std::shared_ptr<arrow::MemoryPool>& pool,
137141
const std::map<std::string, std::string>& options, int32_t batch_size);

0 commit comments

Comments
 (0)