github-actions[bot] commented on code in PR #67650:
URL: https://github.com/apache/doris/pull/67650#discussion_r3972952472


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/lance/source/LanceScanNode.java:
##########
@@ -302,24 +317,26 @@ private Map<Long, LanceFragmentInfo> 
getVisibleFragments(LanceTableMetadata meta
         return visible;
     }
 
-    private List<Split> createFragmentSplits(LanceTableMetadata metadata,
-            Map<Long, LanceFragmentInfo> visibleFragments) {
-        long targetRows = 1;
-        for (LanceFragmentInfo fragment : visibleFragments.values()) {
-            targetRows = Math.max(targetRows, 
Math.max(fragment.getPhysicalRows(), 1));
-        }
-
-        // Keep one fragment per split. Use the largest fragment's physical 
row count as the
-        // normalization baseline for split weights, so backend scheduling 
reflects the relative
-        // amount of physical data each fragment scans, including rows covered 
by deletion metadata.
-        List<Split> splits = new ArrayList<>(visibleFragments.size());
-        for (LanceFragmentInfo fragment : visibleFragments.values()) {
-            LanceSplit split = 
LanceSplit.forFragment(metadata.getDatasetUri(), metadata.getVersion(),
-                    fragment.getId(), fragment.getPhysicalRows());
-            split.setTargetSplitSize(targetRows);
-            splits.add(split);
+    private List<Split> createNormalFragmentSplits(LanceTableMetadata metadata,
+            Map<Long, LanceFragmentInfo> visibleFragments, int numBackends) {
+        if (plannedFragmentsPerSplit > 0) {
+            // Keep the debug grouping exact, even when it produces fewer 
splits than BEs.
+            IndexSegmentSplitPlan plan = new 
IndexSegmentSplitPlan(metadata.getDatasetUri(), metadata.getVersion(), 0);
+            plan.addUncoveredFragments(visibleFragments.values(), 
plannedFragmentsPerSplit);
+            return plan.buildSplits();
+        }
+        scalarIndexPlan = LanceScalarIndexPlanner.plan(metadata, 
lancePushedConjuncts, visibleFragments);

Review Comment:
   [P1] Keep scalar-segment plans executable on old BEs
   
   This new normal-scan path serializes its segment UUID in the pre-existing 
`index_segment_uuids` field. A base-version BE does not ignore that field: its 
`_configure_normal_scan` explicitly returns `InvalidArgument("normal Lance scan 
cannot contain index segment UUIDs")` whenever it is nonempty. Thus a new FE 
can send every indexed ordinary split to a smooth-upgrade source BE and fail 
the query instead of falling back to the explicit fragment domain. Please use a 
new scalar-specific optional field that old BEs can ignore, or gate this 
planning mode on a proven BE capability, and cover the new-FE/base-BE contract.



##########
be/src/format_v2/table/lance_reader.cpp:
##########
@@ -673,12 +699,11 @@ Status LanceTableReader::_open_dataset(const DatasetKey& 
key) {
     std::unique_ptr<LanceDataset, LanceDatasetDeleter> dataset;
     {
         SCOPED_TIMER(_dataset_open_time);
-        dataset.reset(lance_dataset_open(
+        LanceDataset* raw_dataset = nullptr;
+        RETURN_IF_ERROR(LanceSessionManager::instance().open_dataset(

Review Comment:
   [P1] Make session metadata generation-safe before sharing it
   
   This call now routes every dataset open through one BE-wide Lance session, 
but the pinned Lance v11 metadata cache is partitioned only by URI. 
`RowIdIndexKey` serializes only `manifest.version`, while the stable-row-ID 
`RowAddrMaskKey` uses only that version plus a fragment-subset hash. Dropping 
and recreating a dataset at the same URI restarts the version, so indexed/take 
and deletion-prefilter paths can consume the prior incarnation's ID mapping or 
allow/block mask and return wrong rows or fail. Upstream's later eTag addition 
to `RowIdIndexKey` explicitly confirms this previous-incarnation collision, but 
that key alone does not protect the mask. Please make every 
generation-sensitive entry safe (or avoid/invalidate cross-generation sharing) 
and test a warm-cache drop/recreate at the same URI/version.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/lance/LanceMetadataLoader.java:
##########
@@ -64,17 +66,10 @@ public static LanceTableMetadata loadLatestForTvf(
      * <p>Called by
      * {@link LanceExternalCatalog#loadTableMetadata(String, String, 
java.util.Optional)} when no
      * time-travel version is requested. Schema, version, and fragments are 
read from the same
-     * opened dataset snapshot.
+     * opened dataset snapshot, together with index coverage for fragment 
grouping and external searches.
      */
     public static LanceTableMetadata loadLatest(String datasetUri,
             Map<String, String> lanceStorageOptions, BufferAllocator 
allocator) throws Exception {
-        return loadInternal(
-                datasetUri, lanceStorageOptions, OptionalLong.empty(), 
allocator, false);
-    }
-
-    /** Loads the latest fixed snapshot together with search-index segment 
coverage. */
-    public static LanceTableMetadata loadLatestWithIndexSegments(
-            String datasetUri, Map<String, String> lanceStorageOptions, 
BufferAllocator allocator) throws Exception {
         return loadInternal(
                 datasetUri, lanceStorageOptions, OptionalLong.empty(), 
allocator, true);

Review Comment:
   [P1] Keep ordinary scans independent of index discovery
   
   Passing `true` here makes `describeUserIndexes` part of every catalog 
metadata load, including `LanceExternalTable.initSchema`, unfiltered scans, 
row-count fetches, and time travel. That helper intentionally throws on 
index/provider inconsistencies and on datasets with more than 256 logical 
indexes, so index-only metadata can now make otherwise readable data and schema 
unavailable; before this change only the search-specific loader took that 
dependency. Scalar segment grouping is an optimization, so please make its 
discovery best-effort for ordinary loads (fall back to the already-read fixed 
schema/version/fragments), while retaining strict discovery where vector/FTS 
semantics require it.



##########
be/src/common/config.cpp:
##########
@@ -1206,6 +1206,26 @@ DEFINE_Validator(variant_max_json_key_length,
 DEFINE_Validator(variant_storage_parse_mode,
                  [](const int config) -> bool { return config >= 0 && config 
<= 2; });
 
+// Lance uses one BE-wide session so metadata/index caches and the optional 
Foyer data-file cache
+// can be shared by all Lance dataset readers.
+DEFINE_Int64(lance_index_cache_size_bytes, "10737418240");   // 10 GiB
+DEFINE_Int64(lance_metadata_cache_size_bytes, "1073741824"); // 1GB
+DEFINE_Bool(enable_lance_data_cache, "true");

Review Comment:
   [P1] Fix short cache hits before enabling this by default
   
   The activated Foyer adapter's `assemble_range` treats an incomplete cache 
value as a successful hit. In its single-block branch, `end` is clamped to 
`block.len()`, so a cached 32 KiB value for a requested 64 KiB block returns 
only 32 KiB whenever the start is present. The multi-block branch likewise 
breaks on a short block and returns the accumulated output without checking 
`requested_len`. Because `cached_ranges` falls back to origin only on `Err`, a 
partial/corrupt entry becomes truncated Lance input instead of a cache miss. 
Please require exact requested coverage (and verify the final assembled length) 
before making this cache default-on.



##########
thirdparty/download-thirdparty.sh:
##########
@@ -718,19 +718,19 @@ if [[ " ${TP_ARCHIVES[*]} " =~ " AZURE " ]]; then
     echo "Finished patching ${AZURE_SOURCE}"
 fi
 
-# Apply Doris lance-c patches.
+# Apply Doris lance-c patches as one chain to the pinned release archive.
 if [[ " ${TP_ARCHIVES[*]} " =~ " LANCE_C " ]]; then
-    if [[ "${LANCE_C_SOURCE}" == "lance-c-0.1.9" ]]; then
-        cd "${TP_SOURCE_DIR}/${LANCE_C_SOURCE}"
-        if [[ ! -f "${PATCHED_MARK}" ]]; then
-            patch --batch --forward --reject-file=- --fuzz=0 
--no-backup-if-mismatch -s \
-                -p1 <"${TP_PATCH_DIR}/lance-c-0.1.9-pr-73.patch"
+    cd "${TP_SOURCE_DIR}/${LANCE_C_SOURCE}"
+    if [[ ! -f "${PATCHED_MARK}" ]]; then

Review Comment:
   [P1] Version the Lance patch marker when changing the patch set
   
   A source tree already prepared by the base revision contains 
`thirdparty/src/lance-c-0.1.9/patched_mark`. Because this PR keeps both 
`LANCE_C_SOURCE` and the generic marker name unchanged, the condition here 
skips every newly added/updated patch on the next `download-thirdparty.sh` run, 
while the unpack stage also preserves the existing directory. Incremental 
builders can therefore compile the old Lance v10/C ABI even though this 
revision's BE expects the new session/scalar-index symbols and FE is on Lance 
v11. Please key the marker/source directory to the patch-set revision (or 
otherwise invalidate and re-extract it when the chain changes).



##########
be/src/format_v2/lance/lance_session_manager.cpp:
##########
@@ -0,0 +1,218 @@
+// 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.
+
+#include "format_v2/lance/lance_session_manager.h"
+
+#include <lance/lance.h>
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "common/config.h"
+#include "common/logging.h"
+#include "common/metrics/doris_metrics.h"
+#include "common/metrics/metrics.h"
+#include "format_v2/lance/lance_reader_helper.h"
+
+namespace doris::format::lance {
+namespace {
+
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_capacity_bytes, 
MetricUnit::BYTES);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_usage_bytes, 
MetricUnit::BYTES);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_entries, 
MetricUnit::NOUNIT);
+DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_hits_total, 
MetricUnit::OPERATIONS);
+DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_misses_total,
+                                     MetricUnit::OPERATIONS);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_capacity_bytes,
 MetricUnit::BYTES);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_usage_bytes, 
MetricUnit::BYTES);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_entries, 
MetricUnit::NOUNIT);
+DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_hits_total,
+                                     MetricUnit::OPERATIONS);
+DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_misses_total,
+                                     MetricUnit::OPERATIONS);
+
+constexpr std::string_view LANCE_SESSION_CACHE_METRICS_HOOK = 
"lance_session_cache";
+
+int64_t metric_value(uint64_t value) {
+    return static_cast<int64_t>(
+            std::min(value, 
static_cast<uint64_t>(std::numeric_limits<int64_t>::max())));
+}
+
+LanceSessionManager::Config load_lance_session_config() {
+    return {
+            .lance_index_cache_size_bytes = 
config::lance_index_cache_size_bytes,
+            .lance_metadata_cache_size_bytes = 
config::lance_metadata_cache_size_bytes,
+            .enable_lance_data_cache = config::enable_lance_data_cache,
+            .lance_data_cache_path = config::lance_data_cache_path,
+            .lance_data_cache_disk_capacity_bytes = 
config::lance_data_cache_disk_capacity_bytes,
+            .lance_data_cache_read_block_size_bytes =
+                    config::lance_data_cache_read_block_size_bytes,
+    };
+}
+
+} // namespace
+
+class LanceSessionMetrics final {
+public:
+    LanceSessionMetrics(LanceSession* session, const 
LanceSessionManager::Config& config)
+            : _session(session), 
_entity(DorisMetrics::instance()->server_entity()) {
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_index_cache_capacity_bytes);
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_index_cache_usage_bytes);
+        INT_GAUGE_METRIC_REGISTER(_entity, lance_session_index_cache_entries);
+        INT_COUNTER_METRIC_REGISTER(_entity, 
lance_session_index_cache_hits_total);
+        INT_COUNTER_METRIC_REGISTER(_entity, 
lance_session_index_cache_misses_total);
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_capacity_bytes);
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_usage_bytes);
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_entries);
+        INT_COUNTER_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_hits_total);
+        INT_COUNTER_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_misses_total);
+
+        
lance_session_index_cache_capacity_bytes->set_value(config.lance_index_cache_size_bytes);
+        lance_session_metadata_cache_capacity_bytes->set_value(
+                config.lance_metadata_cache_size_bytes);
+        _entity->register_hook(std::string(LANCE_SESSION_CACHE_METRICS_HOOK),
+                               [this]() { update(); });
+        update();
+    }
+
+    ~LanceSessionMetrics() {
+        
_entity->deregister_hook(std::string(LANCE_SESSION_CACHE_METRICS_HOOK));
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_capacity_bytes);
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_usage_bytes);
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_entries);
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_hits_total);
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_misses_total);
+        METRIC_DEREGISTER(_entity, 
lance_session_metadata_cache_capacity_bytes);
+        METRIC_DEREGISTER(_entity, lance_session_metadata_cache_usage_bytes);
+        METRIC_DEREGISTER(_entity, lance_session_metadata_cache_entries);
+        METRIC_DEREGISTER(_entity, lance_session_metadata_cache_hits_total);
+        METRIC_DEREGISTER(_entity, lance_session_metadata_cache_misses_total);
+    }
+
+private:
+    void update() {
+        // Session caches are shared across queries, so publish one 
process-wide snapshot instead
+        // of attributing concurrent cache activity to an individual query 
profile.
+        LanceSessionCacheStats stats {};
+        if (lance_session_get_cache_stats(_session, &stats) != 0) {
+            LOG_EVERY_N(WARNING, 100)
+                    << lance_error("collect Lance session cache 
statistics").to_string();
+            return;
+        }
+        lance_session_index_cache_usage_bytes->set_value(
+                metric_value(stats.index_cache_size_bytes));
+        
lance_session_index_cache_entries->set_value(metric_value(stats.index_cache_entries));
+        
lance_session_index_cache_hits_total->set_value(metric_value(stats.index_cache_hits));
+        
lance_session_index_cache_misses_total->set_value(metric_value(stats.index_cache_misses));
+        lance_session_metadata_cache_usage_bytes->set_value(
+                metric_value(stats.metadata_cache_size_bytes));
+        
lance_session_metadata_cache_entries->set_value(metric_value(stats.metadata_cache_entries));
+        
lance_session_metadata_cache_hits_total->set_value(metric_value(stats.metadata_cache_hits));
+        lance_session_metadata_cache_misses_total->set_value(
+                metric_value(stats.metadata_cache_misses));
+    }
+
+    LanceSession* _session;
+    MetricEntity* _entity;
+    IntGauge* lance_session_index_cache_capacity_bytes = nullptr;
+    IntGauge* lance_session_index_cache_usage_bytes = nullptr;
+    IntGauge* lance_session_index_cache_entries = nullptr;
+    IntCounter* lance_session_index_cache_hits_total = nullptr;
+    IntCounter* lance_session_index_cache_misses_total = nullptr;
+    IntGauge* lance_session_metadata_cache_capacity_bytes = nullptr;
+    IntGauge* lance_session_metadata_cache_usage_bytes = nullptr;
+    IntGauge* lance_session_metadata_cache_entries = nullptr;
+    IntCounter* lance_session_metadata_cache_hits_total = nullptr;
+    IntCounter* lance_session_metadata_cache_misses_total = nullptr;
+};
+
+LanceSessionManager& LanceSessionManager::instance() {
+    // Function-local static initialization is thread safe. Cache 
configuration is process scoped,
+    // so changing it requires a BE restart.
+    static LanceSessionManager manager(load_lance_session_config());
+    return manager;
+}
+
+LanceSessionManager::LanceSessionManager(Config config) : 
_config(std::move(config)) {
+    LOG(INFO) << "Creating BE-wide Lance session manager: 
lance_index_cache_size_bytes="
+              << _config.lance_index_cache_size_bytes
+              << ", lance_metadata_cache_size_bytes=" << 
_config.lance_metadata_cache_size_bytes
+              << ", enable_lance_data_cache=" << 
_config.enable_lance_data_cache
+              << ", lance_data_cache_path=" << _config.lance_data_cache_path
+              << ", lance_data_cache_disk_capacity_bytes="
+              << _config.lance_data_cache_disk_capacity_bytes
+              << ", lance_data_cache_read_block_size_bytes="
+              << _config.lance_data_cache_read_block_size_bytes
+              << ", foyer_memory_capacity_bytes=" << 
_config.lance_data_cache_read_block_size_bytes;
+}
+
+LanceSessionManager::~LanceSessionManager() {
+    _metrics.reset();
+    lance_session_close(_session);
+}
+
+Status LanceSessionManager::_initialize() {
+    if (_config.enable_lance_data_cache) {
+        const LanceDataCacheOptions data_cache_options {
+                .directory = _config.lance_data_cache_path.c_str(),
+                // Foyer's HybridCache requires a memory tier. Keep it at the 
minimum useful
+                // capacity of exactly one range-cache block; entries use 
WriteOnInsertion and
+                // are persisted to the disk tier immediately.
+                .memory_capacity_bytes =
+                        
static_cast<uint64_t>(_config.lance_data_cache_read_block_size_bytes),
+                .disk_capacity_bytes =
+                        
static_cast<uint64_t>(_config.lance_data_cache_disk_capacity_bytes),
+                .read_block_size_bytes =
+                        
static_cast<uint64_t>(_config.lance_data_cache_read_block_size_bytes),
+        };
+        _session = lance_session_new_with_data_cache(

Review Comment:
   [P1] Include the effective object store in the data-cache namespace
   
   This creates one Foyer cache for all BE datasets, but the adapter keys 
blocks only by Lance's `store_prefix` plus object path. In the pinned Lance v11 
code, S3 inherits the default prefix `s3$<bucket>`, which ignores storage 
options such as `aws_endpoint`; Doris forwards those options independently for 
each catalog. Thus two S3-compatible endpoints containing the same bucket/path 
generate identical cache keys: after endpoint A warms a `.lance` data file, a 
query against endpoint B can return A's cached size/bytes without consulting B. 
Please add a stable identity for the effective endpoint/account to the cache 
namespace (without embedding secrets), or fix the provider prefix before 
sharing this cache, and test two stores with identical URI paths but different 
contents.



##########
be/src/format_v2/lance/lance_session_manager.cpp:
##########
@@ -0,0 +1,218 @@
+// 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.
+
+#include "format_v2/lance/lance_session_manager.h"
+
+#include <lance/lance.h>
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "common/config.h"
+#include "common/logging.h"
+#include "common/metrics/doris_metrics.h"
+#include "common/metrics/metrics.h"
+#include "format_v2/lance/lance_reader_helper.h"
+
+namespace doris::format::lance {
+namespace {
+
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_capacity_bytes, 
MetricUnit::BYTES);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_usage_bytes, 
MetricUnit::BYTES);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_entries, 
MetricUnit::NOUNIT);
+DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_hits_total, 
MetricUnit::OPERATIONS);
+DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(lance_session_index_cache_misses_total,
+                                     MetricUnit::OPERATIONS);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_capacity_bytes,
 MetricUnit::BYTES);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_usage_bytes, 
MetricUnit::BYTES);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_entries, 
MetricUnit::NOUNIT);
+DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_hits_total,
+                                     MetricUnit::OPERATIONS);
+DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(lance_session_metadata_cache_misses_total,
+                                     MetricUnit::OPERATIONS);
+
+constexpr std::string_view LANCE_SESSION_CACHE_METRICS_HOOK = 
"lance_session_cache";
+
+int64_t metric_value(uint64_t value) {
+    return static_cast<int64_t>(
+            std::min(value, 
static_cast<uint64_t>(std::numeric_limits<int64_t>::max())));
+}
+
+LanceSessionManager::Config load_lance_session_config() {
+    return {
+            .lance_index_cache_size_bytes = 
config::lance_index_cache_size_bytes,
+            .lance_metadata_cache_size_bytes = 
config::lance_metadata_cache_size_bytes,
+            .enable_lance_data_cache = config::enable_lance_data_cache,
+            .lance_data_cache_path = config::lance_data_cache_path,
+            .lance_data_cache_disk_capacity_bytes = 
config::lance_data_cache_disk_capacity_bytes,
+            .lance_data_cache_read_block_size_bytes =
+                    config::lance_data_cache_read_block_size_bytes,
+    };
+}
+
+} // namespace
+
+class LanceSessionMetrics final {
+public:
+    LanceSessionMetrics(LanceSession* session, const 
LanceSessionManager::Config& config)
+            : _session(session), 
_entity(DorisMetrics::instance()->server_entity()) {
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_index_cache_capacity_bytes);
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_index_cache_usage_bytes);
+        INT_GAUGE_METRIC_REGISTER(_entity, lance_session_index_cache_entries);
+        INT_COUNTER_METRIC_REGISTER(_entity, 
lance_session_index_cache_hits_total);
+        INT_COUNTER_METRIC_REGISTER(_entity, 
lance_session_index_cache_misses_total);
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_capacity_bytes);
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_usage_bytes);
+        INT_GAUGE_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_entries);
+        INT_COUNTER_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_hits_total);
+        INT_COUNTER_METRIC_REGISTER(_entity, 
lance_session_metadata_cache_misses_total);
+
+        
lance_session_index_cache_capacity_bytes->set_value(config.lance_index_cache_size_bytes);
+        lance_session_metadata_cache_capacity_bytes->set_value(
+                config.lance_metadata_cache_size_bytes);
+        _entity->register_hook(std::string(LANCE_SESSION_CACHE_METRICS_HOOK),
+                               [this]() { update(); });
+        update();
+    }
+
+    ~LanceSessionMetrics() {
+        
_entity->deregister_hook(std::string(LANCE_SESSION_CACHE_METRICS_HOOK));
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_capacity_bytes);
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_usage_bytes);
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_entries);
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_hits_total);
+        METRIC_DEREGISTER(_entity, lance_session_index_cache_misses_total);
+        METRIC_DEREGISTER(_entity, 
lance_session_metadata_cache_capacity_bytes);
+        METRIC_DEREGISTER(_entity, lance_session_metadata_cache_usage_bytes);
+        METRIC_DEREGISTER(_entity, lance_session_metadata_cache_entries);
+        METRIC_DEREGISTER(_entity, lance_session_metadata_cache_hits_total);
+        METRIC_DEREGISTER(_entity, lance_session_metadata_cache_misses_total);
+    }
+
+private:
+    void update() {
+        // Session caches are shared across queries, so publish one 
process-wide snapshot instead
+        // of attributing concurrent cache activity to an individual query 
profile.
+        LanceSessionCacheStats stats {};
+        if (lance_session_get_cache_stats(_session, &stats) != 0) {
+            LOG_EVERY_N(WARNING, 100)
+                    << lance_error("collect Lance session cache 
statistics").to_string();
+            return;
+        }
+        lance_session_index_cache_usage_bytes->set_value(
+                metric_value(stats.index_cache_size_bytes));
+        
lance_session_index_cache_entries->set_value(metric_value(stats.index_cache_entries));
+        
lance_session_index_cache_hits_total->set_value(metric_value(stats.index_cache_hits));
+        
lance_session_index_cache_misses_total->set_value(metric_value(stats.index_cache_misses));
+        lance_session_metadata_cache_usage_bytes->set_value(
+                metric_value(stats.metadata_cache_size_bytes));
+        
lance_session_metadata_cache_entries->set_value(metric_value(stats.metadata_cache_entries));
+        
lance_session_metadata_cache_hits_total->set_value(metric_value(stats.metadata_cache_hits));
+        lance_session_metadata_cache_misses_total->set_value(
+                metric_value(stats.metadata_cache_misses));
+    }
+
+    LanceSession* _session;
+    MetricEntity* _entity;
+    IntGauge* lance_session_index_cache_capacity_bytes = nullptr;
+    IntGauge* lance_session_index_cache_usage_bytes = nullptr;
+    IntGauge* lance_session_index_cache_entries = nullptr;
+    IntCounter* lance_session_index_cache_hits_total = nullptr;
+    IntCounter* lance_session_index_cache_misses_total = nullptr;
+    IntGauge* lance_session_metadata_cache_capacity_bytes = nullptr;
+    IntGauge* lance_session_metadata_cache_usage_bytes = nullptr;
+    IntGauge* lance_session_metadata_cache_entries = nullptr;
+    IntCounter* lance_session_metadata_cache_hits_total = nullptr;
+    IntCounter* lance_session_metadata_cache_misses_total = nullptr;
+};
+
+LanceSessionManager& LanceSessionManager::instance() {
+    // Function-local static initialization is thread safe. Cache 
configuration is process scoped,
+    // so changing it requires a BE restart.
+    static LanceSessionManager manager(load_lance_session_config());
+    return manager;
+}
+
+LanceSessionManager::LanceSessionManager(Config config) : 
_config(std::move(config)) {
+    LOG(INFO) << "Creating BE-wide Lance session manager: 
lance_index_cache_size_bytes="
+              << _config.lance_index_cache_size_bytes
+              << ", lance_metadata_cache_size_bytes=" << 
_config.lance_metadata_cache_size_bytes
+              << ", enable_lance_data_cache=" << 
_config.enable_lance_data_cache
+              << ", lance_data_cache_path=" << _config.lance_data_cache_path
+              << ", lance_data_cache_disk_capacity_bytes="
+              << _config.lance_data_cache_disk_capacity_bytes
+              << ", lance_data_cache_read_block_size_bytes="
+              << _config.lance_data_cache_read_block_size_bytes
+              << ", foyer_memory_capacity_bytes=" << 
_config.lance_data_cache_read_block_size_bytes;
+}
+
+LanceSessionManager::~LanceSessionManager() {
+    _metrics.reset();
+    lance_session_close(_session);
+}
+
+Status LanceSessionManager::_initialize() {
+    if (_config.enable_lance_data_cache) {
+        const LanceDataCacheOptions data_cache_options {
+                .directory = _config.lance_data_cache_path.c_str(),
+                // Foyer's HybridCache requires a memory tier. Keep it at the 
minimum useful
+                // capacity of exactly one range-cache block; entries use 
WriteOnInsertion and
+                // are persisted to the disk tier immediately.
+                .memory_capacity_bytes =
+                        
static_cast<uint64_t>(_config.lance_data_cache_read_block_size_bytes),
+                .disk_capacity_bytes =
+                        
static_cast<uint64_t>(_config.lance_data_cache_disk_capacity_bytes),
+                .read_block_size_bytes =
+                        
static_cast<uint64_t>(_config.lance_data_cache_read_block_size_bytes),
+        };
+        _session = lance_session_new_with_data_cache(
+                static_cast<uint64_t>(_config.lance_index_cache_size_bytes),
+                static_cast<uint64_t>(_config.lance_metadata_cache_size_bytes),
+                &data_cache_options);
+    } else {
+        _session =
+                
lance_session_new(static_cast<uint64_t>(_config.lance_index_cache_size_bytes),
+                                  
static_cast<uint64_t>(_config.lance_metadata_cache_size_bytes));
+    }
+    if (_session == nullptr) {
+        return lance_error("create shared Lance session");
+    }
+    _metrics = std::make_unique<LanceSessionMetrics>(_session, _config);
+    return Status::OK();
+}
+
+Status LanceSessionManager::open_dataset(const char* uri, const char* const* 
storage_options,
+                                         uint64_t version, LanceDataset** 
dataset) {
+    if (uri == nullptr || dataset == nullptr) {
+        return Status::InvalidArgument("Lance dataset URI and output must not 
be null");
+    }
+    *dataset = nullptr;
+
+    std::call_once(_initialize_once, [this] { _initialize_status = 
_initialize(); });

Review Comment:
   [P1] Fall back when the optional data cache cannot initialize
   
   With the new default, `_initialize()` only attempts 
`lance_session_new_with_data_cache`. Any cache-specific failure (for example an 
inaccessible configured path or a recovery/device I/O error) is stored by this 
`call_once`, and every later Lance reader in the BE returns the same failure 
even though `lance_session_new` could still serve the table without the disk 
cache. Please treat cache initialization as best-effort and fall back to the 
ordinary shared session after logging/recording the cause (or validate it as an 
explicit BE-startup fatal condition), and cover the failure path.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to