3030#include " iceberg/manifest/manifest_entry.h"
3131#include " iceberg/manifest/manifest_list.h"
3232#include " iceberg/manifest/manifest_reader.h"
33+ #include " iceberg/metadata_cache.h"
3334#include " iceberg/util/macros.h"
3435#include " iceberg/util/string_util.h"
3536
@@ -39,59 +40,107 @@ namespace internal {
3940
4041class SnapshotCacheData {
4142 public:
42- Result<std::reference_wrapper<SnapshotCache::ManifestsCache>> Get (
43+ explicit SnapshotCacheData (std::shared_ptr<MetadataCache> metadata_cache)
44+ : metadata_cache_(std::move(metadata_cache)) {}
45+
46+ Result<std::shared_ptr<const SnapshotCache::ManifestsCache>> Get (
4347 const Snapshot* snapshot, std::shared_ptr<FileIO> file_io) {
48+ if (metadata_cache_ == nullptr || !metadata_cache_->options ().enabled ) {
49+ ICEBERG_ASSIGN_OR_RAISE (
50+ auto loaded, SnapshotCache::LoadManifestsCache (snapshot, std::move (file_io)));
51+ return std::make_shared<const SnapshotCache::ManifestsCache>(std::move (loaded));
52+ }
53+
54+ const auto & location = snapshot->manifest_list ;
4455 std::shared_ptr<Entry> entry;
45- {
56+ while (true ) {
57+ // Checking the content cache also applies its expiration policy and refreshes its
58+ // LRU position. Parsed entries are reusable only while the matching bytes remain
59+ // cached.
60+ auto content = metadata_cache_->GetIfPresent (location);
4661 std::unique_lock lock (mutex_);
47- while (true ) {
48- auto [it, inserted] = entries_.try_emplace (snapshot->manifest_list );
49- if (inserted) {
50- it->second = std::make_shared<Entry>();
62+ PruneExpired ();
63+ auto [it, inserted] = entries_.try_emplace (location);
64+ if (inserted) {
65+ it->second = std::make_shared<Entry>();
66+ }
67+ entry = it->second ;
68+ if (entry->value != nullptr ) {
69+ if (content != nullptr && entry->content .lock () == content) {
70+ return entry->value ;
5171 }
52- entry = it->second ;
53- if (entry->value .has_value ()) {
54- return std::ref (*entry->value );
72+ entries_.erase (it);
73+ continue ;
74+ }
75+ if (entry->loading ) {
76+ entry->loaded .wait (lock, [&entry] { return !entry->loading ; });
77+ if (entry->error .has_value ()) {
78+ return std::unexpected<Error>(*entry->error );
5579 }
56- if (!entry->loading ) {
57- entry->loading = true ;
58- break ;
80+ if (entry->value != nullptr ) {
81+ return entry->value ;
5982 }
60- entry-> loaded . wait (lock, [&entry] { return !entry-> loading ; }) ;
83+ continue ;
6184 }
85+ entry->loading = true ;
86+ break ;
6287 }
6388
64- auto loaded = SnapshotCache::InitManifestsCache (snapshot, std::move (file_io));
89+ auto loaded = SnapshotCache::LoadManifestsCache (snapshot, std::move (file_io));
90+ auto content = metadata_cache_->GetIfPresent (location);
6591 std::lock_guard lock (mutex_);
66- auto it = entries_.find (snapshot-> manifest_list );
92+ auto it = entries_.find (location );
6793 if (!loaded.has_value ()) {
6894 if (it != entries_.end () && it->second == entry) {
6995 entries_.erase (it);
7096 }
97+ entry->error = loaded.error ();
7198 entry->loading = false ;
7299 entry->loaded .notify_all ();
73100 return std::unexpected<Error>(std::move (loaded).error ());
74101 }
75102
76- entry->value = std::move (loaded).value ();
103+ entry->value =
104+ std::make_shared<const SnapshotCache::ManifestsCache>(std::move (loaded).value ());
105+ if (content != nullptr ) {
106+ entry->content = content;
107+ } else if (it != entries_.end () && it->second == entry) {
108+ // Oversized content is not retained by MetadataCache, so do not retain its parsed
109+ // representation in the cross-query cache either.
110+ entries_.erase (it);
111+ }
77112 entry->loading = false ;
78113 entry->loaded .notify_all ();
79- return std::ref (* entry->value ) ;
114+ return entry->value ;
80115 }
81116
82117 private:
83118 struct Entry {
84119 bool loading = false ;
85- std::optional<SnapshotCache::ManifestsCache> value;
120+ std::shared_ptr<const SnapshotCache::ManifestsCache> value;
121+ std::weak_ptr<const std::string> content;
122+ std::optional<Error> error;
86123 std::condition_variable loaded;
87124 };
88125
126+ void PruneExpired () {
127+ for (auto it = entries_.begin (); it != entries_.end ();) {
128+ if (!it->second ->loading && it->second ->content .expired ()) {
129+ it = entries_.erase (it);
130+ } else {
131+ ++it;
132+ }
133+ }
134+ }
135+
136+ std::shared_ptr<MetadataCache> metadata_cache_;
89137 std::mutex mutex_;
90138 std::unordered_map<std::string, std::shared_ptr<Entry>> entries_;
91139};
92140
93- std::shared_ptr<SnapshotCacheData> MakeSnapshotCacheData () {
94- return std::make_shared<SnapshotCacheData>();
141+ std::shared_ptr<SnapshotCacheData> MakeSnapshotCacheData (
142+ std::shared_ptr<MetadataCache> metadata_cache) {
143+ return std::make_shared<SnapshotCacheData>(std::move (metadata_cache));
95144}
96145
97146} // namespace internal
@@ -267,7 +316,14 @@ Result<std::unique_ptr<Snapshot>> Snapshot::Make(
267316 });
268317}
269318
270- Result<SnapshotCache::ManifestsCache> SnapshotCache::InitManifestsCache (
319+ Result<std::shared_ptr<const SnapshotCache::ManifestsCache>>
320+ SnapshotCache::InitManifestsCache (const Snapshot* snapshot,
321+ std::shared_ptr<FileIO> file_io) {
322+ auto cache_data = file_io->GetSnapshotCacheData ();
323+ return cache_data->Get (snapshot, std::move (file_io));
324+ }
325+
326+ Result<SnapshotCache::ManifestsCache> SnapshotCache::LoadManifestsCache (
271327 const Snapshot* snapshot, std::shared_ptr<FileIO> file_io) {
272328 if (file_io == nullptr ) {
273329 return InvalidArgument (" Cannot cache manifests: FileIO is null" );
@@ -304,29 +360,29 @@ Result<std::span<const ManifestFile>> SnapshotCache::Manifests(
304360 std::shared_ptr<FileIO> file_io) const {
305361 ICEBERG_PRECHECK (snapshot_ != nullptr , " Cannot cache manifests for a null snapshot" );
306362 ICEBERG_PRECHECK (file_io != nullptr , " Cannot cache manifests: FileIO is null" );
307- auto cache_data = file_io-> GetSnapshotCacheData ();
308- ICEBERG_ASSIGN_OR_RAISE ( auto cache_ref, cache_data-> Get (snapshot_, std::move (file_io)));
309- auto & cache = cache_ref.get ();
363+ ICEBERG_ASSIGN_OR_RAISE ( auto cache_ref,
364+ manifests_cache_. Get (snapshot_, std::move (file_io)));
365+ const auto & cache = * cache_ref.get ();
310366 return std::span<const ManifestFile>(cache.first .data (), cache.first .size ());
311367}
312368
313369Result<std::span<const ManifestFile>> SnapshotCache::DataManifests (
314370 std::shared_ptr<FileIO> file_io) const {
315371 ICEBERG_PRECHECK (snapshot_ != nullptr , " Cannot cache manifests for a null snapshot" );
316372 ICEBERG_PRECHECK (file_io != nullptr , " Cannot cache manifests: FileIO is null" );
317- auto cache_data = file_io-> GetSnapshotCacheData ();
318- ICEBERG_ASSIGN_OR_RAISE ( auto cache_ref, cache_data-> Get (snapshot_, std::move (file_io)));
319- auto & cache = cache_ref.get ();
373+ ICEBERG_ASSIGN_OR_RAISE ( auto cache_ref,
374+ manifests_cache_. Get (snapshot_, std::move (file_io)));
375+ const auto & cache = * cache_ref.get ();
320376 return std::span<const ManifestFile>(cache.first .data (), cache.second );
321377}
322378
323379Result<std::span<const ManifestFile>> SnapshotCache::DeleteManifests (
324380 std::shared_ptr<FileIO> file_io) const {
325381 ICEBERG_PRECHECK (snapshot_ != nullptr , " Cannot cache manifests for a null snapshot" );
326382 ICEBERG_PRECHECK (file_io != nullptr , " Cannot cache manifests: FileIO is null" );
327- auto cache_data = file_io-> GetSnapshotCacheData ();
328- ICEBERG_ASSIGN_OR_RAISE ( auto cache_ref, cache_data-> Get (snapshot_, std::move (file_io)));
329- auto & cache = cache_ref.get ();
383+ ICEBERG_ASSIGN_OR_RAISE ( auto cache_ref,
384+ manifests_cache_. Get (snapshot_, std::move (file_io)));
385+ const auto & cache = * cache_ref.get ();
330386 const size_t delete_start = cache.second ;
331387 const size_t delete_count = cache.first .size () - delete_start;
332388 return std::span<const ManifestFile>(cache.first .data () + delete_start, delete_count);
0 commit comments