.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements.  See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership.  The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License.  You may obtain a copy of the License at

..   http://www.apache.org/licenses/LICENSE-2.0

.. Unless required by applicable law or agreed to in writing,
.. software distributed under the License is distributed on an
.. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
.. KIND, either express or implied.  See the License for the
.. specific language governing permissions and limitations
.. under the License.

Manifest Entry Cache
====================

Overview
--------

Large tables may contain many manifest entries, while a scan may only need a
small subset after bucket, partition, and statistics pruning. The snapshot live
manifest entry cache reduces repeated manifest decoding cost for successive full
scans that target the same bucket.

The cache stores decoded and merged live manifest entries by table path, branch,
and bucket for ``ScanMode::ALL``. Each cache value can retain several snapshot
results for that bucket. Exact snapshot hits are served from the cache; cache
misses rebuild the target snapshot bucket from the target snapshot's data
manifests and store the rebuilt live entries.

Request-specific filters are not stored in the cache. Partition, level, and
predicate filters are still evaluated for each scan, so cached entries can be
reused safely across different scan predicates for the same bucket.

Configuration
-------------

Manifest entry caching reuses the cache instance provided by
``ScanContextBuilder::WithCache()`` and stores bucket-scoped snapshot entries
under
``CacheKind::SNAPSHOT_LIVE_MANIFEST``:

.. code-block:: cpp

   auto cache = std::make_shared<LruCache>(128 * 1024 * 1024);
   ScanContextBuilder context_builder(table_path);
   PAIMON_ASSIGN_OR_RAISE(
       std::unique_ptr<ScanContext> scan_context,
       context_builder
           .WithCache(cache)
           .AddOption(Options::SCAN_MANIFEST_ENTRY_CACHE_MAX_SNAPSHOTS, "3")
           .Finish());

Cache entries are scoped by table path, branch, and bucket, so they can be
reused across newly created ``TableScan`` and ``FileStoreScan`` instances as
long as they share the same cache object and scan the same bucket.

``Options::SCAN_MANIFEST_ENTRY_CACHE_MAX_SNAPSHOTS`` controls how many snapshot
results are retained in each table/branch/bucket cache value. Older snapshots in
the same bucket are evicted first. The default value is ``0``, which disables
the cache path. Set it to a positive value to enable the cache when
``ScanContextBuilder::WithCache()`` is also configured. Physical cache eviction
is still controlled by the configured ``Cache`` implementation, for example the
capacity of ``LruCache``.

If no cache is provided through ``ScanContextBuilder::WithCache()``, this
optimization is skipped. The snapshot manifest entry cache shares the same
``Cache`` interface with raw manifest and data-file footer caches, but it uses a
dedicated ``CacheKind`` and a table/branch/bucket key instead of file byte
ranges.

Limitations
-----------

The cache is currently used only for ``ScanMode::ALL`` scans that can determine
a single target bucket. It is skipped for scans without a bucket filter because
reading or deserializing all buckets would be too expensive for selective
queries. It is also skipped for row-range scans because row-range pruning is
applied at manifest-meta level.

Metrics
-------

The scan metrics expose existing counters for the last scan:

- ``lastScannedManifests``: how many manifest files were loaded during this
  scan before manifest entry decoding.
