eldenmoon opened a new pull request, #68202:
URL: https://github.com/apache/doris/pull/68202

   ### What problem does this PR solve?
   
   Issue Number: None
   
   Related PR: #68039, #53980
   
   Problem Summary:
   
   A VARIANT column copies each inverted index definition to every materialized 
path, so one SNII segment holds one logical index per (path, definition). Most 
paths are sparse, and two SNII structures cost one entry per segment row 
regardless of how many rows hold a value:
   
   - **BM25 norms**: an analyzed index with positions stores one byte per row, 
NULL rows included.
   - **NULL bitmaps**: `NullBitmapWriter` serialized its CRoaring bitmap 
without `runOptimize`, so every 65536-row container of a mostly NULL path 
stayed an 8 KiB bitset (about N/8 bytes per index per segment). The definitions 
on one path also stored identical copies.
   
   On top of that, BM25 used the segment row count as the document count, so on 
sparse paths avgdl came out far too small and idf differences were flattened.
   
   This PR has two commits.
   
   **Commit 1: no on-disk format change**
   1. `NullBitmapWriter` run-optimizes and shrinks the bitmap before sizing and 
writing it. The inverted index, direct SNII index compaction and the BKD blob's 
`bkd_nulls` all go through this writer. Run containers are part of the portable 
CRoaring format, so older builds read them.
   2. The compound writer stores one null bitmap per subcolumn. It remembers 
the last null bitmap it appended (suffix, XXH3-128 hash, length, region). When 
the next logical index on the same suffix produces an identical bitmap, it 
references that region instead of appending a copy. Region references are 
absolute offsets and no reader assumes a region belongs to one index, so the 
format is unchanged.
   3. `CollectionStatistics` keeps a document count per field. SNII uses the 
field's `indexed_doc_count` (non-NULL rows) for both avgdl and idf N, which is 
what Lucene's `docCount` means. CLucene numbers are unchanged.
   
   **Commit 2: sparse BM25 norms behind a switch**
   - New section type `kNormsSparse` (15). It stores norms only for the 
documents that carry one: present docids in 65536-docid blocks (ALL / ARRAY / 
BITSET with a rank table / RUNS), plus one byte per present document, or a 
single byte when all norms are equal.
   - The writer keeps the existing dense `kNormsPod` (14) section, 
byte-identical to before, when no document lacks a norm or when dense is not 
larger.
   - New mutable BE config `enable_snii_sparse_norms`, default `true`. The only 
decision point is `LogicalIndexWriter::finalize_build`, which load, compaction, 
schema change and BUILD INDEX all go through. Readers accept both layouts 
regardless of the config.
   - We also compared a CRoaring-based encoding of the present set on the same 
data:
     - Section bytes differed by under 1%.
     - Its per-document lookups were 2–34x slower on a RELEASE build. They were 
1.7–2.4x slower even with a batch API at 5% density, the most common density we 
measured.
     - Its zero-copy frozen view cannot be used safely on untrusted offsets.
     - The hand-written block format was therefore kept.
   
   **End-to-end results.** Each row compares master with this PR on the same 
data, measured as the bytes of one compacted segment file on an ASAN build.
   
   | Table | master | this PR | Detail |
   |---|---|---|---|
   | 1.02M-row VARIANT table, 1000 paths at 0.8% density, 11 index definitions 
(3 analyzed with phrase) | 601.2 MB | **77.4 MB (7.8x)** | norms 382.5 → 6.2 
MB; inverted null bitmaps 114.8 → 16.4 MB; BKD null bitmaps 65.6 → 16.4 MB; 
dictionary / postings / BKD data unchanged |
   | 677k-row GitHub-events table, payload `parser=none` + `parser=english` 
(phrase) | 913.1 MB | **571.9 MB (1.60x)** | norms 324.3 → 39.8 MB; inverted 
null bitmaps 86.4 → 31.9 MB; dictionary 351.2 MB unchanged |
   | Same 1.02M-row data, `enable_snii_sparse_norms = false`, 10 load rowsets | 
647.1 MB | 453.7 MB | null bitmaps only; norms stay in the dense layout |
   
   BM25 scores are bit-identical between the dense and sparse layouts: after an 
in-place upgrade, on tables mixing master-written and new segments, and after 
full compaction into the new layout. `score()` latency did not regress.
   
   **Compatibility (tested by swapping BE binaries over the same storage)**
   
   | Case | Result |
   |---|---|
   | This PR reading segments written by master | MATCH, MATCH_PHRASE, 
equality, IS NULL and BKD range counts identical |
   | Master reading segments written by this PR with the config off | Read 
natively, 0 fallbacks, `score()` works; commit 1 needs no switch |
   | Master reading sparse norms sections | The index cannot be opened. Filters 
fall back to row-by-row evaluation with correct results; `score()` fails with 
E-6012. A full compaction by the older binary rewrites the index densely and 
restores `score()`. Turn the config off while older BEs may read newly written 
segments. |
   
   ### Release note
   
   - BM25 scores (`score()`) of SNII inverted indexes on columns or VARIANT 
paths with NULL rows change: avgdl and idf now use the field's non-NULL 
document count instead of the segment row count, as Lucene does. Filtering 
results do not change.
   - SNII NULL bitmaps are run-length encoded and stored once per subcolumn; 
the format is unchanged.
   - New mutable BE config `enable_snii_sparse_norms` (default `true`): SNII 
stores BM25 norms only for rows that carry one when that is smaller. BEs 
without this change cannot use indexes written this way (filters fall back to 
row-by-row evaluation and `score()` fails); set it to `false` while such BEs 
may read newly written segments, e.g. during a rolling upgrade or before a 
downgrade.
   
   ### Check List (For Author)
   
   - Test <!-- At least one of them must be included. -->
       - [x] Regression test
       - [x] Unit Test
       - [x] Manual test (add detailed scripts or steps below)
       - [ ] No need to test or manual test. Explain why:
           - [ ] This is a refactor/code format and no logic has been changed.
           - [ ] Previous test can cover this change.
           - [ ] No code files have been changed.
           - [ ] Other reason <!-- Add your reason?  -->
   
       **Unit tests** (both commits built and tested on their own):
       - Commit 1: 1788 SNII / norms / NULL bitmap / BM25 tests, 1779 pass.
       - Final head: 1803 tests, 1794 pass.
       - In both runs the only failures are 8 environment-specific 
`SniiBatchRangeFetcher.*` / `SniiLocalFile.*` tests (their fixed `/tmp` files 
belong to another OS user on the test host), and 
`SniiGoldenCorpus.WriteOrVerify` is skipped without `SNII_GOLDEN_DIR`.
       - New suites: `SniiNormsSection`, `SniiSparseNormsTest`, 
`SniiSharedNullBitmap`, plus extended `SniiNullBitmap`, 
`SniiIndexCompactionTest` (mixed dense/sparse sources, config on and off) and 
`CollectionStatisticsTest`.
   
       **Regression tests:**
       - New `test_storage_format_snii_sparse_norms`: config on / off / toggled 
per batch, full compaction, identical results and scores across layouts.
       - `test_storage_format_snii_norms`: expectations regenerated for the new 
BM25 statistics and hand-checked.
       - These pass unchanged: `test_storage_format_snii`, `_utf8_wildcard`, 
`_custom_analyzer`, `test_variant_search_subcolumn_snii`, 
`regression_test_variant_var_index_snii`, 
`regression_test_variant_snii_compaction`, `test_variant_v2_snii_index`, 
`test_timestamp_ns_index`.
   
       **Manual test:** the end-to-end A/B, upgrade and rollback runs 
summarized above, on two clusters built from master and from this PR.
   
   - Behavior changed:
       - [ ] No.
       - [x] Yes. <!-- Explain the behavior change -->
           - `score()` values change for SNII indexes on fields with NULL rows 
(see release note).
           - With `enable_snii_sparse_norms` on, new SNII segments may carry 
the new `kNormsSparse` section, which BEs without this change cannot use.
   
   - Does this need documentation?
       - [ ] No.
       - [x] Yes. <!-- Add document PR link here. eg: 
https://github.com/apache/doris-website/pull/1214 -->
           - An entry for `enable_snii_sparse_norms` in the BE configuration 
reference, and a note on the BM25 statistics in the scoring docs.
           - Doc PR to follow.
   
   ### Check List (For Reviewer who merge this PR)
   
   - [ ] Confirm the release note
   - [ ] Confirm test cases
   - [ ] Confirm document
   - [ ] Add branch pick label <!-- Add branch pick label that this PR should 
merge into -->
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


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