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


##########
be/src/storage/index/inverted/query/exec/index_source.cpp:
##########
@@ -43,4 +46,132 @@ Status IndexSource::collect_terms(std::span<const 
std::string> terms, DocIdSink&
     return Status::OK();
 }
 
+namespace {
+
+// How many postings a scan decodes in the time a seek costs.
+constexpr uint64_t kPostingsPerSeek = 32;
+
+// Decodes every posting once and walks `docs` alongside, both ascending.
+Status count_listed_docs_by_scan(PostingsCursor& cursor, std::span<const 
uint32_t> docs,
+                                 uint64_t* out) {
+    size_t next = 0;
+    PostingsBlock block;
+    bool eof = false;
+    while (next < docs.size()) {
+        RETURN_IF_ERROR(cursor.next_block(&block, &eof));
+        if (eof) {
+            break;
+        }
+        if (block.dense) {
+            const auto begin = std::lower_bound(docs.begin() + next, 
docs.end(), block.range_begin);
+            const auto end = std::lower_bound(begin, docs.end(), 
block.range_end);
+            *out += static_cast<uint64_t>(end - begin);
+            next = end - docs.begin();
+            continue;
+        }
+        for (const uint32_t doc : block.docs) {
+            while (next < docs.size() && docs[next] < doc) {
+                ++next;
+            }
+            if (next == docs.size()) {
+                break;
+            }
+            if (docs[next] == doc) {
+                ++*out;
+                ++next;
+            }
+        }
+    }
+    return Status::OK();
+}
+
+} // namespace
+
+Status count_listed_docs(PostingsCursor& cursor, std::span<const uint32_t> 
docs, uint64_t* out) {
+    *out = 0;
+    if (docs.empty() || cursor.doc_freq() == 0) {
+        return Status::OK();
+    }
+    // Leaping seeks once per wanted document, scanning decodes every posting: 
leap only over
+    // documents much rarer than the postings.
+    if (docs.size() * kPostingsPerSeek >= cursor.doc_freq()) {
+        return count_listed_docs_by_scan(cursor, docs, out);
+    }
+    size_t next = 0;
+    PostingsBlock block;
+    bool eof = false;
+    while (next < docs.size()) {
+        RETURN_IF_ERROR(cursor.seek_block(docs[next], &block, &eof));
+        if (eof) {
+            break;
+        }
+        const uint64_t size = block.size();
+        uint64_t ordinal = 0;
+        while (ordinal < size && next < docs.size()) {
+            const uint32_t listed = block.doc_at(ordinal);
+            const uint32_t wanted = docs[next];
+            if (listed == wanted) {
+                ++*out;
+                ++ordinal;
+                ++next;
+            } else if (listed < wanted) {
+                if (block.dense) {
+                    ordinal = wanted - block.range_begin;
+                } else {
+                    ordinal = std::lower_bound(block.docs.begin() + ordinal, 
block.docs.end(),
+                                               wanted) -
+                              block.docs.begin();
+                }
+            } else {
+                next = std::lower_bound(docs.begin() + next, docs.end(), 
listed) - docs.begin();
+            }
+        }
+    }
+    return Status::OK();
+}
+
+Status IndexSource::scoring_doc_freq_among(std::string_view term, 
std::span<const uint32_t> docs,
+                                           uint64_t* out) {
+    *out = 0;
+    if (docs.empty()) {
+        return Status::OK();
+    }
+    std::unique_ptr<PostingsCursor> cursor;
+    RETURN_IF_ERROR(open_term(term, /*positions=*/false, /*scoring=*/false, 
&cursor));
+    if (cursor == nullptr) {
+        return Status::OK();
+    }
+    return count_listed_docs(*cursor, docs, out);
+}
+
+Status IndexSource::sum_norm_lengths_of(std::span<const uint32_t> docs, 
uint64_t* out) {
+    *out = 0;
+    const std::span<const float> lengths = norm_lengths();
+    std::vector<uint32_t> norms;

Review Comment:
   [P2] Bound or track the SNII selected-row norm buffer
   
   For an analyzed SNII MoW segment with 10 million NULL rows and one matching 
row, `sum_non_null_norm_lengths` passes the entire NULL list here, and 
`SniiIndexSource::encoded_norms` assigns one `uint32_t` per ID. This allocates 
another roughly 40 MB before scan pruning, without a MemTracker charge or 
reservation. The manually charged NULL-ID vector and `DorisVector` hidden IDs 
cover separate inputs; the 4096-row batching in `sum_norm_lengths` does not 
bound this direct call. Large hidden non-NULL sets reach the same path, so 
concurrent tablet collection can exceed query memory limits. Please batch 
selected rows here or sum them directly with tracked storage. This is a 
distinct norm-code output buffer from the previously reported input row-ID 
arrays.



##########
be/src/storage/index/inverted/clucene/spi/clucene_index_source.cpp:
##########
@@ -243,6 +243,82 @@ std::span<const float> CluceneIndexSource::norm_lengths() 
const {
     return BM25Similarity::lucene_norm_lengths();
 }
 
+// The statistics read a field's norms into a buffer of their own, freed after 
use: asking a reader
+// for its norms makes it keep them, uncharged to the searcher cache, for 
every segment the
+// statistics visit, scored or not. Reading per segment also avoids the copy 
of all segments' norms
+// a reader of several segments would keep. A segment without the field's 
norms reads as zero
+// lengths, as that reader would.
+std::vector<CluceneIndexSource::ReaderPartition> 
CluceneIndexSource::_norm_partitions() const {
+    if (!_partitions.empty()) {
+        return _partitions;
+    }
+    return {{.reader = _reader.get(), .begin = 0, .end = doc_count()}};
+}
+
+void CluceneIndexSource::_read_norms(const ReaderPartition& partition,
+                                     DorisVector<uint8_t>* norms) const {
+    norms->assign(partition.end - partition.begin, 0);
+    partition.reader->norms(_field.c_str(), norms->data());

Review Comment:
   [P2] Preserve the query I/O context on statistics norm reads
   
   For a cold remote MoW tablet, the new statistics pass reads norms from every 
captured segment, including segments a selective scan later prunes. This call 
never supplies the source's `_io_ctx`: the norm stream's `CSIndexInput` has no 
per-read context, and the cached base stream was reset to defaults. 
Consequently `CacheContext` sees `is_disposable=false`, no query ID and no 
remote-scan cache-write limiter. Even with `disable_file_cache=true` or a query 
cache-write budget, these new bulk reads populate normal-priority cache blocks 
and bypass that budget. Ordinary scoring had the same limitation, but 
previously did not read norms of all pruned segments. Please propagate the 
context per norm read without retaining query-owned pointers on the shared 
cached searcher.



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