airborne12 commented on code in PR #69854:
URL: https://github.com/apache/doris/pull/69854#discussion_r4430796090


##########
be/src/storage/index/inverted/reader/collection_statistics.cpp:
##########
@@ -89,6 +91,62 @@ Result<std::shared_ptr<lucene::search::IndexSearcher>> 
open_optional_clucene_sta
     return std::make_shared<lucene::search::IndexSearcher>(reader, true);
 }
 
+Result<std::unique_ptr<::doris::snii::reader::LogicalIndexReader>> 
open_snii_stats_reader(
+        const segment_v2::IndexFileReader& file_reader, const TabletIndex* 
index,
+        const io::IOContext* io_ctx) {
+    auto logical_reader = file_reader.open_snii_index(index, io_ctx);
+    if (!logical_reader.has_value()) {
+        auto status = std::move(logical_reader.error());
+        if (status.code() == ErrorCode::INVERTED_INDEX_SNII_NOT_FOUND ||
+            status.code() == ErrorCode::INVERTED_INDEX_FILE_NOT_FOUND ||
+            status.code() == ErrorCode::INVERTED_INDEX_BYPASS) {
+            return 
ResultError(Status::Error<ErrorCode::INVERTED_INDEX_NOT_SUPPORTED>(
+                    "SNII scoring requires every logical index in the 
collection: {}",
+                    status.msg()));
+        }
+        return ResultError(std::move(status));
+    }
+    return std::move(logical_reader.value());
+}
+
+// A segment's SNII logical index as the statistics read it, and the norm 
length sums kept with it.
+// Outside tests the reader is the one the query path caches in the searcher 
cache, so the sums
+// live and are evicted with it, and a query whose segments are cached opens 
no index.
+struct SniiStatsReader {
+#ifdef BE_TEST
+    std::unique_ptr<::doris::snii::reader::LogicalIndexReader> owned_reader;
+    segment_v2::NormLengthSums owned_norm_length_sums;
+#else
+    InvertedIndexCacheHandle cache_handle;
+#endif
+    const ::doris::snii::reader::LogicalIndexReader* reader = nullptr;
+    segment_v2::NormLengthSums* norm_length_sums = nullptr;
+};
+
+Status open_snii_stats_reader(const 
std::shared_ptr<segment_v2::IndexFileReader>& file_reader,
+                              const TabletIndex* index, const io::IOContext* 
io_ctx,
+                              SniiStatsReader* out) {
+#ifdef BE_TEST
+    out->owned_reader = DORIS_TRY(open_snii_stats_reader(*file_reader, index, 
io_ctx));
+    out->reader = out->owned_reader.get();
+    out->norm_length_sums = &out->owned_norm_length_sums;
+#else
+    InvertedIndexSearcherCache::CacheKey 
cache_key(file_reader->get_index_file_cache_key(index));
+    if (!InvertedIndexSearcherCache::instance()->lookup(cache_key, 
&out->cache_handle)) {
+        auto opened = DORIS_TRY(open_snii_stats_reader(*file_reader, index, 
io_ctx));
+        const size_t reader_size = std::max<size_t>(opened->memory_usage(), 1);
+        auto* cache_value = new segment_v2::SniiLogicalReaderCacheValue(
+                std::move(opened), reader_size, UnixMillis(), file_reader);
+        InvertedIndexSearcherCache::instance()->insert(cache_key, cache_value, 
&out->cache_handle);
+    }
+    auto* cache_value = 
out->cache_handle.get<segment_v2::SniiLogicalReaderCacheValue>();

Review Comment:
   **[P1] A cached SNII BKD searcher can reach this cast, which can crash the 
BE.**
   
   `InvertedIndexCacheHandle::get<T>()` is a `static_cast`, and 
`snii_bkd_index_reader.cpp` stores `SniiBkdSearcherCacheValue` under a key from 
the same `get_index_file_cache_key()`. On a cache hit, nothing here checks that 
the value is a logical reader.
   
   Trigger, on an SNII table with an analyzed index on a VARIANT column, where 
a segment holds the dynamic path `v.n` as numbers and so indexes it with BKD:
   
   1. `CAST(v['n'] AS BIGINT) = 1` puts the BKD searcher of `v.n` in the 
searcher cache.
   2. A `MATCH` on `v['n']` with `score()` makes the statistics visit that 
segment. The lookup here hits the BKD entry.
   3. The `BkdSearcher` pointer is used as a `LogicalIndexReader`. On a 
merge-on-write table, `norm_length_sums` also lies past the end of the smaller 
`SniiBkdSearcherCacheValue`.
   
   Before this PR the same query failed cleanly: the statistics always called 
`open_snii_index`, and `open_index` rejects the entry in `require_inverted`. 
The cache lookup skips that check, so the error becomes undefined behavior once 
step 1 has run. Unit tests do not reach this code, because 
`open_snii_stats_reader` takes the `BE_TEST` branch.
   
   I found this by reading the code at 2934ecaaa0 and have not reproduced it on 
this PR. The same case on CLucene did crash the BE in #69790's Cloud P0 
(`std::bad_variant_access`, fixed there in df74dd4ca4).
   
   Suggested fix: #69790 adds `snii_field_has_no_terms`, which skips such a 
field by reading the segment's directory. Keep that check before the cache 
lookup when the two PRs are merged; they conflict in this part of 
`process_segment`. If this PR merges first, it needs an equivalent check of its 
own. Either way, please add a regression case that runs the numeric predicate 
first, so that the cache hit path is tested.
   



-- 
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