Skip to content

[feature-wip](inverted index) Introduce SPIMI V4 inverted index storage format - #63633

Open
airborne12 wants to merge 78 commits into
apache:masterfrom
airborne12:inverted-index-spimi
Open

[feature-wip](inverted index) Introduce SPIMI V4 inverted index storage format#63633
airborne12 wants to merge 78 commits into
apache:masterfrom
airborne12:inverted-index-spimi

Conversation

@airborne12

@airborne12 airborne12 commented May 25, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: close #xxx

Related PR: #xxx

Problem Summary:

Introduces a new inverted index storage format V4 powered by SPIMI
(Single-Pass In-Memory Indexing), replacing the CLucene IndexWriter
on the write path for analyzed (fulltext) string columns.

Why

CLucene's IndexWriter accumulates per-token Posting linked-list
nodes plus a term hash table plus a char[] interning pool. On Doris
fulltext columns this dominates BE memory during write and shows up
in OOM kills on large segments. The encoding is byte-equivalent to
Lucene 2.x, but the in-memory representation is the cost. SPIMI
keeps a flat (term_id, doc_id, position) record array plus a
single intern arena, then sorts + emits the same Lucene 2.x sibling
files (.tis/.tii/.frq/.prx/.fnm/segments_N) on finish(). The
on-disk format is unchanged; only the writer's working memory shape
changes.

Measured impact (SPIMI_BENCH=1, ~614 K occurrences/segment)

Dimension V4 vs V2 (mostly_unique / all_unique) V4 vs V2 (repetitive, vocab=16)
Writer peak memory −55.6 % / −55.6 % +406 % (160 KB → 811 KB; both negligible)
Writer CPU (median) −68 % / −68 % +5 % (within bench cap)
.idx on-disk size ~0 % +8 % (PFOR sub-block header overhead)
Query latency ~0 % (not measured at bench scale)

Repetitive vocab is the architectural trade-off region: V4's
compact-mode VInt-delta stream scales per-occurrence while CLucene's
Posting struct scales per-unique-term. Absolute memory in this
regime is sub-MB on both sides, so the percentage swing has no
production impact. Storage-size delta on repetitive is the
documented PFOR header cost.

What's in this PR

  • V4 writer pipeline (be/src/storage/index/inverted/spimi/):
    SpimiPostingBuffer (flat record + arena + intern map with
    hybrid compact-mode VInt-delta migration), SegmentWriter,
    TermDictWriter, FieldInfosWriter, SegmentInfosWriter,
    PFOR encoder for high-doc-freq postings, ByteOutput family
    abstracting CLucene's IndexOutput.
  • V4 reader pipeline: SpimiQueryIndexReader,
    SpimiTermDocsReader, SpimiProxReader, SpimiTermEnum,
    SpimiSearcherBuilder; SpimiFulltextIndexReader is the
    Doris-side adapter (overrides type() -> SPIMI_FULLTEXT so
    the searcher cache routes correctly).
  • column_reader.cpp dispatch: V4 storage format → SPIMI
    reader; V1/V2/V3 unchanged.
  • EmitSegment post-flush self-validation:
    ValidateClosedSegmentByteCounts re-queries on-disk file
    lengths after close, throws INVERTED_INDEX_FILE_CORRUPTED on
    mismatch — guards against the async-S3 partial-flush class of
    bugs that single-node tests can't see.
  • 108 BE unit tests under be/test/storage/index/inverted/spimi/
    plus extended tests under be/test/storage/segment/:
    • 17 corruption-path tests covering every SPIMI_THROW_CORRUPT
      site (segments_N / .frq / .prx / PFOR / .tis-.tii readers)
    • 7 byte-count validator tests including the truncation
      fault-injection case
    • Storage-size benchmark (V2 vs V4 .idx byte parity)
    • Throughput benchmark with 11 runs + 2 warmup discards +
      randomized V2/V4 alternation + full distribution report
    • Memory benchmark across mostly_unique / all_unique /
      repetitive workloads
    • Query-latency benchmark via the production read path
      (InvertedIndexReaderTest.SpimiV2V4QueryLatencyBenchmark)
      using the corrected SpimiFulltextIndexReader::create_shared
      dispatch
  • SPIMI_BENCH env-var tier: default UT runs use 12 K
    occurrences (fast regression guard); SPIMI_BENCH=1 scales to
    ~614 K, SPIMI_BENCH=large scales to ~6 M for full-segment
    stress. Keeps headline benchmark numbers reproducible without
    ballooning every UT pass.
  • Regression suites:
    • inverted_index_p0/storage_format/test_storage_format_v4
      — V2 vs V4 black-box parity across MATCH_ANY / MATCH_ALL /
      MATCH_PHRASE / MATCH_PHRASE_PREFIX / MATCH_REGEXP, NULL/empty
      handling, and the support_phrase=false (omit_tfap) no-prox
      write+read path.
    • test_storage_format_v4_cloud — same coverage gated by
      isCloudMode() so the async-S3 upload path gets exercised.
    • test_storage_format_v4_query_latency — cluster-level
      V2 vs V4 query timing distribution.
  • FE plumbing (PropertyAnalyzer, TabletIndex,
    OlapTable): accept inverted_index_storage_format=V4 in
    CREATE TABLE PROPERTIES; propagate through the protocol to BE.

What's NOT in this PR (known gaps)

  • V4 segment compaction across multiple SPIMI segments — V4
    currently emits a single _0 segment per column; compaction is
    documented as a follow-up in SPIMI_DESIGN.md.
  • BM25-style scoring on V4 — V4 sets omit_norms=true; the read
    side synthesizes a default-norm array. Score-using paths
    (MATCH_ALL with relevance ordering) fall back to V2 behavior
    on V4 columns. Listed in design doc.
  • V4 only covers analyzed (fulltext) string columns. Keyword-mode
    (should_analyzer=false) and numeric (BKD) paths remain on the
    existing writers.

Release note

Add inverted index storage format V4, an in-house SPIMI-based writer
that reduces BE write-side memory by ~55 % and CPU by ~68 % on
diverse-vocab fulltext workloads while keeping segment on-disk
format Lucene 2.x compatible. Enable by setting
inverted_index_storage_format = "V4" in CREATE TABLE PROPERTIES.

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • 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
  • Behavior changed:

    • No.
    • Yes. New value 'V4' accepted by inverted_index_storage_format property; V1/V2/V3 paths unchanged.
  • Does this need documentation?

    • No.
    • Yes. Doc PR will follow against apache/doris-website.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

gavinchou
gavinchou previously approved these changes May 25, 2026

@gavinchou gavinchou left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@airborne12

Copy link
Copy Markdown
Member Author

run buildall

@airborne12
airborne12 force-pushed the inverted-index-spimi branch from 9281771 to 0f96cfa Compare May 26, 2026 03:22
@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 78.06% (1854/2375)
Line Coverage 64.50% (33323/51665)
Region Coverage 65.23% (16530/25343)
Branch Coverage 55.72% (8834/15854)

@airborne12 airborne12 changed the title [feature](be)(fe) Introduce SPIMI V4 inverted index storage format [feature](inverted index) Introduce SPIMI V4 inverted index storage format May 26, 2026
@airborne12

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 0.00% (0/8) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 1.69% (2/118) 🎉
Increment coverage report
Complete coverage report

2 similar comments
@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 1.69% (2/118) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 1.69% (2/118) 🎉
Increment coverage report
Complete coverage report

@airborne12

Copy link
Copy Markdown
Member Author

run buildall

@airborne12

Copy link
Copy Markdown
Member Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found blocking correctness issues in the V4 SPIMI spill path and validation gaps that can expose unsupported V4 tables.

Critical checkpoint conclusions:

  • Goal/test proof: the PR wires V4 SPIMI write/read and adds broad tests, but the tests do not cover the production multi-spill case with absolute row ids and include a timing assertion that is likely flaky in CI.
  • Scope/focus: mostly focused on V4 SPIMI, but the new p0 latency benchmark is not a stable functional test.
  • Concurrency/lifecycle: no new shared concurrent state issue identified in the reviewed path; cache-reader lifetime changes appear to use existing searcher cache patterns.
  • Configuration/compatibility: V4 is exposed through table properties/config and thrift/proto, but FE validation still permits unsupported V4 index shapes that BE later rejects during load.
  • Parallel paths: cloud/non-cloud schema propagation has V4 branches, but unsupported parser/array paths need consistent FE rejection.
  • Data correctness: multi-spill merging can corrupt posting doc ids, and flush can persist a stale doc_count for the triggering row. These affect query correctness for larger V4 fulltext segments.
  • Test coverage/results: there are many unit/regression tests, but the missing absolute-doc-id multi-spill coverage lets the main spill corruption escape; the latency regression should not be p0 threshold-based.
  • Observability/performance: no additional observability blocker found beyond the functional issues above.

User focus: no additional user-provided focus points were present.

// Apply doc_id offset and append.
for (auto& d : docs) {
d.doc_id += offset;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This offset corrupts production V4 spills. InvertedIndexColumnWriter::add_values() appends the segment-level _rid into SpimiPostingBuffer, and SpillManager::FlushBuffer() emits those doc ids as-is, so spill inputs already contain absolute row ids. When a large segment crosses the SPIMI memory budget more than once, the second and later spills get running added again here, shifting postings beyond their real rows (often beyond total_doc_count) and causing MATCH queries to return wrong/missing rows. The current merger tests build artificial inputs with local doc ids, so they do not cover the actual writer->spill contract. Please either make spill buffers localize doc ids before emitting, or remove this offset for V4 spill inputs and add an end-to-end test with multiple spills using absolute _rid values.

// and continue accepting tokens.
if (_spimi_writer->ShouldFlush()) {
_spimi_writer->FlushPending(_spimi_doc_count);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc count passed to the spill is stale for the row that triggered the flush. At this point the current row's tokens have already been appended with doc id _rid, but _spimi_doc_count is only advanced below this block. If this is the first row past the budget, the spill manifest can advertise a doc_count that excludes a doc whose postings are present. That stale count is then used by SegmentMerger for offsets/metadata. Update _spimi_doc_count before FlushPending() (or pass static_cast<int32_t>(_rid) + 1) so each spill's metadata covers all postings it contains.

@@ -1275,11 +1277,15 @@ public static TInvertedIndexFileStorageFormat analyzeInvertedIndexFileStorageFor
return TInvertedIndexFileStorageFormat.V2;
} else if (invertedIndexFileStorageFormat.equalsIgnoreCase("v3")) {
return TInvertedIndexFileStorageFormat.V3;
} else if (invertedIndexFileStorageFormat.equalsIgnoreCase("v4")) {
return TInvertedIndexFileStorageFormat.V4;
} else if (invertedIndexFileStorageFormat.equalsIgnoreCase("default")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exposing explicit V4 here needs matching FE validation for the supported index shapes. The BE writer now rejects V4 when should_analyzer is false and also rejects array string indexes, but InvertedIndexUtil.checkInvertedIndexParser() still accepts parser none and array-with-parser-none for the same table property. That lets CREATE TABLE ... PROPERTIES("inverted_index_storage_format"="V4") succeed for keyword/array inverted indexes, and the first load then fails in BE writer init. Please reject unsupported V4 parser/type combinations during analysis, or implement those paths end-to-end.

// on top of the actual reader latency. A real reader regression
// would shift the median far past this cap.
assertTrue(ratio < 2.0,
"${tag}: V4 median ${v4.median} us / V2 median ${v2.median} us = ${ratio} " +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A p0 regression suite should not fail on wall-clock query timing. This median ratio includes planner/executor startup, cache state, BE scheduling, network/runner noise, and unrelated concurrent load; on shared CI it can exceed 2x without a functional regression, especially with only 9 retained samples. This will create flaky failures for unrelated changes. Please keep this as logging/manual benchmark coverage, move it out of p0/per-PR execution, or replace the assertion with deterministic functional checks.

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 0.00% (0/8) 🎉
Increment coverage report
Complete coverage report

@airborne12

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 78.06% (1854/2375)
Line Coverage 64.53% (33337/51663)
Region Coverage 65.19% (16522/25343)
Branch Coverage 55.75% (8838/15854)

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 31767 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 3eae47c6fefd24b24b0604c82634bf4898004b70, data reload: false

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17796	4176	4206	4176
q2	q3	11004	1483	831	831
q4	4803	476	347	347
q5	10511	2272	2127	2127
q6	391	189	144	144
q7	1006	790	652	652
q8	9607	1737	1578	1578
q9	7064	5027	5015	5015
q10	6622	2246	1855	1855
q11	438	279	253	253
q12	697	430	303	303
q13	18191	3503	2823	2823
q14	265	260	245	245
q15	q16	822	774	709	709
q17	902	954	923	923
q18	7077	5731	5533	5533
q19	1201	1406	1174	1174
q20	507	402	263	263
q21	5682	2597	2505	2505
q22	430	366	311	311
Total cold run time: 105016 ms
Total hot run time: 31767 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	4545	4419	4515	4419
q2	q3	4588	4991	4305	4305
q4	2158	2228	1397	1397
q5	4483	4979	4997	4979
q6	272	206	149	149
q7	2051	1859	1663	1663
q8	2684	2367	2283	2283
q9	8141	8061	8252	8061
q10	4889	4770	4406	4406
q11	628	447	412	412
q12	746	771	542	542
q13	3335	3752	3082	3082
q14	322	315	292	292
q15	q16	763	719	655	655
q17	1399	1363	1356	1356
q18	8098	7373	6985	6985
q19	1143	1093	1071	1071
q20	2221	2228	1946	1946
q21	5349	4716	4494	4494
q22	565	476	407	407
Total cold run time: 58380 ms
Total hot run time: 52904 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 172092 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 3eae47c6fefd24b24b0604c82634bf4898004b70, data reload: false

query5	4318	660	527	527
query6	343	250	213	213
query7	4249	540	331	331
query8	335	245	229	229
query9	8812	4024	4033	4024
query10	465	361	296	296
query11	5740	2552	2235	2235
query12	185	131	128	128
query13	1294	600	449	449
query14	6096	5474	5152	5152
query14_1	4513	4524	4511	4511
query15	216	205	189	189
query16	1044	478	459	459
query17	1174	776	619	619
query18	2788	509	372	372
query19	228	205	186	186
query20	135	133	132	132
query21	219	139	117	117
query22	13692	13570	13386	13386
query23	17442	16636	16285	16285
query23_1	16240	16395	16410	16395
query24	7521	1775	1332	1332
query24_1	1346	1311	1344	1311
query25	582	497	455	455
query26	1316	314	174	174
query27	2681	574	344	344
query28	4392	2007	2009	2007
query29	1000	647	526	526
query30	314	246	199	199
query31	1130	1095	977	977
query32	103	79	82	79
query33	571	360	302	302
query34	1210	1129	656	656
query35	815	795	709	709
query36	1378	1413	1274	1274
query37	169	106	95	95
query38	3194	3209	3063	3063
query39	932	920	907	907
query39_1	900	889	873	873
query40	234	151	119	119
query41	66	62	61	61
query42	109	110	106	106
query43	321	338	290	290
query44	
query45	216	200	201	200
query46	1105	1205	742	742
query47	2410	2388	2234	2234
query48	368	434	302	302
query49	633	492	377	377
query50	1014	357	251	251
query51	4406	4316	4283	4283
query52	100	104	94	94
query53	246	282	200	200
query54	310	263	244	244
query55	92	92	88	88
query56	288	308	305	305
query57	1477	1408	1362	1362
query58	291	265	262	262
query59	1553	1644	1404	1404
query60	314	319	301	301
query61	165	164	164	164
query62	683	659	603	603
query63	251	203	201	201
query64	2361	788	627	627
query65	
query66	1672	478	353	353
query67	29734	29824	29450	29450
query68	
query69	452	367	305	305
query70	1018	1013	1007	1007
query71	309	276	266	266
query72	2992	2706	2398	2398
query73	859	774	420	420
query74	5092	4961	4751	4751
query75	2698	2631	2270	2270
query76	2305	1147	792	792
query77	406	413	334	334
query78	12442	12366	11808	11808
query79	1518	1002	732	732
query80	1347	540	460	460
query81	495	285	241	241
query82	1343	156	118	118
query83	349	267	251	251
query84	270	147	113	113
query85	930	542	471	471
query86	449	332	351	332
query87	3432	3418	3267	3267
query88	3631	2732	2724	2724
query89	459	384	345	345
query90	1917	179	181	179
query91	177	170	144	144
query92	77	80	76	76
query93	1549	1428	865	865
query94	754	373	321	321
query95	674	370	335	335
query96	1053	757	338	338
query97	2732	2738	2621	2621
query98	238	228	228	228
query99	1184	1157	1040	1040
Total cold run time: 255573 ms
Total hot run time: 172092 ms

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 25.00% (2/8) 🎉
Increment coverage report
Complete coverage report

@airborne12
airborne12 force-pushed the inverted-index-spimi branch from 3eae47c to b334f74 Compare June 2, 2026 15:38
…ms chain-copy

The DOCS_ONLY posting chain stored phrase-shaped docCode VInts (delta<<1|flag [+freq]) that the emit only decoded to throw the freq away and re-encode bare deltas. Store the bare doc-delta directly — written EAGERLY at doc open (no deferred close; FinalizeBlocks skips omit buffers), one entry per doc, which is byte-for-byte the on-disk DOCS_ONLY slim block. Slim omit terms (df < skip_interval) then take the same chain-copy fast path as phrase-on terms (EmitSlimTermPreEncoded grows omit support: no prox bytes, prox_pointer=0); PFOR omit terms replay doc_count bare deltas. The chain also shrinks in memory (no freq codes), trimming the DOCS_ONLY buffer.

Format ownership: the chain format follows the BUFFER's omit flag, the on-disk format follows the WRITER's — chain-copy and the bare-delta replay are gated on the flags AGREEING. The one legal mixed combination (omit writer over a phrase buffer, which tests use to emit DOCS_ONLY from a generic buffer) keeps the docCode decode+re-encode replay; the OmitTfapByteNeutral A/B pair caught the first cut keying this off the writer flag alone.

Omit records semantics: DecodeCompactTerm/DecodeTermToRecords now yield one record per DOC for omit buffers (per-occurrence multiplicity is not recoverable from a bare-delta chain, and nothing downstream needs it — the omit emit ignores freq). Norms are the only per-occurrence consumer, and omit fields never write norms in V2/CLucene either; EmitSegment grows a DCHECK so a future norms-over-omit caller fails loudly.

Validated: SPIMI-wide ASAN suite 486 pass / 0 fail, including the OmitTfapByteNeutral{VInt,Pfor} byte-equality oracles, OmitTfapDirectEmitMatchesRecordsPath, and the DOCS_ONLY roundtrip.
…, last-term swap

Three byte-identical term-dictionary wins, one per term across the whole vocabulary: Utf8ToWideInto fills a reused member wstring instead of heap-allocating one per Add/AddInline; the front-coded suffix is staged once (AppendSCharsFromWide, sharing one EncodeSChar core with WriteSCharsFromWide so the encodings cannot drift) and emitted with a single bulk WriteBytes instead of a virtual WriteByte per encoded byte; and the .tis last-term update swaps the scratch instead of copying the wstring (the .tii boundary entries, 1 in 128, still copy and never reach the swap branch). SPIMI-wide ASAN suite 486 pass / 0 fail.
…d emit)

The windowed-term replay decoded every position from the prox chain (prefix-summing within-doc deltas to absolutes) only for AddPosition to recompute the SAME deltas and re-encode the SAME LEB128 bytes into the windowed position buffer. Since the chain bytes and the rebuilt buffer are byte-identical, EmitFromCompactDirect now copies the whole prox chain once (one memcpy per slice), decodes only the per-doc docCode entries (df values — cheap relative to occurrences), recovers each doc's position byte offset with a continuation-bit scan (no value decode), and hands everything to FreqProxEncoder::EmitWindowedTermPreDecoded — which produces exactly FinishTermWindowed's output via the same WindowFrameEncoder::Encode call. High-frequency terms (wiki-class head words, millions of occurrences) skip the per-occurrence decode + re-encode entirely.

Gated on V4 windowed + phrase-on with a phrase BUFFER (the chain must carry positions); the omit-writer-over-phrase-buffer combination keeps the replay. SPIMI-wide ASAN suite 486 pass / 0 fail, including the V4Inline direct-vs-records A/B's windowed (df=600) case.
@airborne12

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29465 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 4006aee3a4cc7b9112c427bbf83d5e175d9d4dab, data reload: false

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17734	4108	4038	4038
q2	q3	10998	1467	819	819
q4	4809	476	345	345
q5	8617	894	577	577
q6	362	174	140	140
q7	911	870	639	639
q8	10906	1470	1677	1470
q9	7176	4497	4546	4497
q10	6804	1820	1521	1521
q11	447	266	259	259
q12	657	433	297	297
q13	18199	3414	2824	2824
q14	272	267	237	237
q15	q16	819	773	710	710
q17	952	981	991	981
q18	6885	5782	5723	5723
q19	1160	1243	1186	1186
q20	513	421	263	263
q21	6093	2891	2626	2626
q22	441	375	313	313
Total cold run time: 104755 ms
Total hot run time: 29465 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	4845	4692	5048	4692
q2	q3	4911	5211	4663	4663
q4	2156	2216	1376	1376
q5	4941	4667	4726	4667
q6	227	177	135	135
q7	1852	1801	1557	1557
q8	2399	2030	1939	1939
q9	7471	7467	7391	7391
q10	4737	4690	4261	4261
q11	545	391	370	370
q12	734	732	532	532
q13	3095	3371	2813	2813
q14	282	269	253	253
q15	q16	687	704	606	606
q17	1294	1261	1279	1261
q18	7564	6990	6934	6934
q19	1154	1116	1090	1090
q20	2232	2236	1952	1952
q21	5311	4575	4461	4461
q22	532	470	417	417
Total cold run time: 56969 ms
Total hot run time: 51370 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 168328 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 4006aee3a4cc7b9112c427bbf83d5e175d9d4dab, data reload: false

query5	4372	630	478	478
query6	528	205	171	171
query7	4800	560	306	306
query8	388	213	197	197
query9	8776	4085	4072	4072
query10	482	312	251	251
query11	5928	2357	2186	2186
query12	159	97	96	96
query13	1337	621	436	436
query14	6377	5391	5056	5056
query14_1	4336	4420	4424	4420
query15	205	197	174	174
query16	1123	483	437	437
query17	1136	697	566	566
query18	2589	478	339	339
query19	216	180	147	147
query20	110	109	106	106
query21	219	151	118	118
query22	13655	13565	13383	13383
query23	17412	16573	16161	16161
query23_1	16319	16240	16344	16240
query24	7517	1800	1320	1320
query24_1	1322	1303	1308	1303
query25	615	471	423	423
query26	1398	329	163	163
query27	2597	536	338	338
query28	4471	2038	2031	2031
query29	1126	662	502	502
query30	340	249	203	203
query31	1121	1085	947	947
query32	106	61	63	61
query33	553	320	261	261
query34	1220	1154	669	669
query35	764	800	685	685
query36	1391	1417	1254	1254
query37	154	103	94	94
query38	3246	3155	3045	3045
query39	943	917	889	889
query39_1	878	875	889	875
query40	223	125	105	105
query41	75	67	66	66
query42	98	98	95	95
query43	330	333	285	285
query44	
query45	201	188	181	181
query46	1112	1157	734	734
query47	2319	2438	2263	2263
query48	396	438	312	312
query49	645	482	373	373
query50	995	348	261	261
query51	4315	4272	4235	4235
query52	89	92	83	83
query53	247	273	185	185
query54	297	230	206	206
query55	82	84	77	77
query56	269	232	236	232
query57	1405	1395	1312	1312
query58	252	229	240	229
query59	1599	1700	1419	1419
query60	300	271	246	246
query61	188	144	152	144
query62	692	648	583	583
query63	237	195	191	191
query64	2607	755	621	621
query65	
query66	1822	458	348	348
query67	30079	29734	28946	28946
query68	
query69	457	316	265	265
query70	982	951	956	951
query71	297	219	214	214
query72	2994	2694	2360	2360
query73	895	755	449	449
query74	5149	4942	4755	4755
query75	2651	2581	2212	2212
query76	2340	1151	779	779
query77	361	375	300	300
query78	12560	12462	11912	11912
query79	1541	1048	752	752
query80	741	468	377	377
query81	479	282	235	235
query82	574	156	126	126
query83	336	280	247	247
query84	
query85	922	508	424	424
query86	439	293	290	290
query87	3436	3315	3148	3148
query88	3702	2754	2791	2754
query89	442	380	330	330
query90	1819	184	179	179
query91	173	156	132	132
query92	65	60	56	56
query93	1505	1502	924	924
query94	658	356	295	295
query95	734	451	357	357
query96	1040	837	337	337
query97	2697	2710	2560	2560
query98	217	205	207	205
query99	1150	1172	1028	1028
Total cold run time: 252971 ms
Total hot run time: 168328 ms

…wrote -4)

根因:SpimiIndexWriter::EmitDirect 调用 SpimiFulltextWriter::EmitSegment 时
漏传最后一个实参 inline_small_terms(默认 false)。直写路径(单次 flush、
无 spill —— 生产最常见形态)产出的 V4 段因此从不内联小 term:.tis 头
FORMAT=-4,小 term 的 frq/prx 留在外置文件,查询每个小 term 多付一次
读 GET。这与设计意图(fulltext_writer.h 注释:最终段与 V4 spill 段
lockstep 开启内联)相悖;spill-merge 路径一直正常(SpillManager 显式传
true、SegmentMerger 内部按 use_windowed 派生),导致同一字段
「有 spill 则内联、无 spill 则不内联」的不一致。

修复:EmitDirect 补传 inline_small_terms=true。EmitSegment 内部已有
use_windowed(index_version >= kIndexVersionV4)门控,V0/V2 兼容段不受
影响(仍 FORMAT=-4、无内联)。其余三个发射点(SpimiFulltextWriter::
Finish、SpillManager::FlushBuffer、SegmentMerger::Merge)复核无同类
漏传,未改动。

读侧安全性:持久读侧门控为 .fnm 的 index_version >= V4 + .tis 头
FORMAT=-5 分发;生产读路径(query_term_docs 等)已长期解码 spill-merge
产出的 -5 内联段,本修复只是让直写段进入同一已验证格式。

验证(TDD,ASAN UT 构建):
- 新增 SpimiIndexWriterTest.DirectEmitV4InlinesSmallTerms:修复前失败
  (FORMAT=-4、TermInfo.inlined=false),修复后通过(FORMAT=-5、内联
  frq 字节非空)。
- 新增 DirectEmitNonV4StaysExternalFormat 反向断言:非 V4 直写段保持
  FORMAT=-4、无内联(修复前后均通过)。
- ASAN 宽 SPIMI 套件全绿:50 个 suite 共 441 用例,436 通过、5 个既有
  env 门控/前置 PR 跳过、0 失败;含端到端
  InvertedIndexReaderTest.SpimiV4SingleQueryProbe(生产写路径直写内联段
  + 生产读路径查询)。
@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 83.33% (5462/6555) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 74.16% (28712/38715)
Line Coverage 58.28% (313875/538573)
Region Coverage 54.97% (262742/477962)
Branch Coverage 56.33% (114027/202443)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 13.33% (2/15) 🎉
Increment coverage report
Complete coverage report

…n dense terms

设计要点(解剖结论:/tmp/idx_anatomy 36 组受控 .idx,.frq 是 V4 相对 V2 唯一
普遍反向的组件;根源是 V2 TurboPFor p4nd1enc 的 delta-1 域 + 0-bit 常数块对
delta≡1/freq≡1 的稠密 term 近乎免费,而 V4 PFOR 位宽下限 1 bit/值 + 每
128 值块 1B 宽度头 ⇒ 全等值块仍要 17B):

- 取块级方案 (a):PFOR 子块新增「常数块」形态 [0x00][VInt(c)]。0x00 在旧格式
  中是非法宽度字节 ⇒ 无歧义;块内 N 个值全等且 1+VIntLen(c) 严格小于普通块时
  才发射,平手保持普通块(确定性、非全等块逐字节不变)。相比窗口级 kWinConst:
  粒度更细(混合 term 的局部全等块同样受益)、skip 表语义/窗口边界/range-GET
  framing 与 kWinRaw/kWinZstd 字节语义完全不动、merger 字节拷贝快路径天然兼容。
- 仅 windowed V4 写路径开启(EncodePforPart → allow_const=true);legacy
  kCodeModeSpimiPfor 路径保持旧字节。解码三处同步识别 0x00:DecodeBlockFromBytes
  与两份 DecodePforRun 副本(query 路径 frq_window_decode_internal.h、merge 路径
  posting_decoder.cpp);0x80(patch 标志+宽度 0)仍按损坏字节硬失败,新增截断
  /超长 VInt 损坏用例。
- W 选型与 zstd 交互自动正确:常数块长度进入 dd_len/fq_len ⇒ AnalyticRawFrqSize
  与 MeasureAndCacheFrq 的代价评估、skip 偏移与 1.10x 预算逐字节一致(analytic
  vs measured 的 DCHECK 交叉验证保留);常数窗 payload 极小,zstd 候选自然落选。
- omit/tfap 核查结论:DOCS_ONLY 无 freq 域(omit 路径已覆盖);phrase-on 的全 1
  freq 块此前仍付 17B/块,现收敛为 2B/块——phrase-on 收益的另一半来源。

效果(RELEASE benchmark 18-cell 矩阵,对照同 HEAD 未打补丁基线,V2 逐字节不变,
节省全部落在 .frq,.prx/.tis 不动):
- httplogs p0 30K:V4/V2 1.138→1.043(idx -8.4%,.frq -12%)
- httplogs p1 30K/200K:0.658→0.580 / 0.610→0.523(.frq -23%)
- textbench p1 20K/200K/1M:0.779→0.760 / 0.718→0.690 / 0.702→0.672
- agentlogs p1 in/out:0.851→0.836 / 0.837→0.824
- 其余 cell(textbench p0、weibo、wikipedia、agentlogs p0)0.00%~-0.01%,无一变大
- 写 CPU A/B(交替 4 轮、V2 同机对照):V4 httplogs -0.5%/-4.3%、textbench
  -0.9%/+1.7%,均不超过 V2 对照漂移 ⇒ 无回归(全等块还省去 pack_bits/nth_element)

测试:TDD(RED 4 例修复前失败→GREEN);ByteIdentityGolden 按机制重基线 6/7
(唯一无全等值块的 prox_varfreq700 digest 不变,证明非全等块字节稳定);宽
SPIMI ASAN 套件 442 用例 440 过 / 2 env-skip / 0 失败。
V4 SPIMI posting buffer 的内存预算从编译期常量 kDefaultMemoryBudget
(128 MiB)改为跟随 config::inverted_index_ram_buffer_size(MB,mDouble
可热改)——与 V2/CLucene setRAMBufferSizeMB 同源同值,默认 512 MB。

动机:固定 128 MiB 预算下 >2M 行的大段进入病态 spill churn——httplogs 9M
phrase 写入 CPU 相对 V2 达 3.81x;跟随 512 MB config 后回到 0.99x,峰值
内存与 V2 同量级。

实现:
- SpimiPostingBuffer::ConfiguredMemoryBudgetBytes():在 MB 域(double)
  先夹紧到 [16 MiB, 8 GiB] 再乘 2^20,超大配置不会在 size_t 转换处溢出;
  非有限值(NaN/inf)或 <= 0 回退 kDefaultMemoryBudget,config 未初始化
  (全零)时安全。
- SpimiIndexWriter 构造时读一次(新增生产 ctor 注入 Limits,保留随机
  hash seed),热改 config 对新段生效;Append 热路径零 config 读,每行
  spill gate 仍只读 ShouldFlush() latch。
- kDefaultMemoryBudget 降级为测试兜底 + 非法 config 的回退值。
- benchmark [BENCH-CONFIG] 凭证行 buffer_budget_mb 改打实际生效值
  (ConfiguredMemoryBudgetBytes() >> 20),不再打编译期常量。

本提交显式取代 a863652(256->128 MiB)的固定低预算决策(用户拍板):
wiki 类大文档小段场景放弃 546->259 MB 的写峰值收益,512 MB 默认下其
写峰值将回升到 ~546 MB 量级(高于 V2 的 288 MB);需要旧行为的部署可
调低 inverted_index_ram_buffer_size(热改生效)。预算只移动 spill 切分
点,不改变最终索引字节:新增 UT SpillCadenceDoesNotChangeIndexBytes
(同语料 0/1/3 次 spill 三种节奏,V4 段全部落盘文件逐字节一致)作为
回归护栏;httplogs 9M 实测 128/512 两档 idx_bytes=58928778 逐字节一致。
a863652 当年 +0.75% .idx 随 spill 数变化的现象,在 spill-merge 预解码
再编码重做之后不再复现。

测试:SpimiIndexWriterTest 新增 5 个 UT(config 跟随 / 构造时读一次 /
夹紧上下限 / 非法值回退 / spill 节奏 byte-identity);宽 SPIMI ASAN
套件 447 ran / 445 PASSED / 2 SKIPPED(均为既有 env-gated 测量用例)/
0 FAILED。顺带清扫 inverted_index_writer.{h,cpp} 中 5 处陈旧的
"256MiB ShouldFlush latch" 注释(预算已 config 驱动)。
…rride

Batch 0 of the spill/merge streaming-concatenation work: evaluation
infrastructure that pins the byte gold standard BEFORE any merge
implementation changes.

1. SPIMI_RAM_BUFFER_MB env override in benchmark_spimi.hpp
   ensure_env_inited (same style as SPIMI_FRQ_ZSTD): forces the
   spill+merge path (e.g. 128) on corpora that would not spill under
   the 512 default now that the buffer budget follows
   inverted_index_ram_buffer_size. Verified live: [BENCH-CONFIG]
   prints buffer_budget_mb=128 with the env set, 512 without.

2. SpimiMergeByteIdentityGolden (G1): fixed term matrix (40 inline
   tiny terms, slim 511/512/513 boundary trio, windowed df=5000
   multi-window, PFOR const-block df=2048, heavy-positions df=600)
   built through the production spill contract (absolute doc_ids,
   k contiguous doc ranges, SpillManager::FlushBuffer) for
   k in {direct,1,2,7} x {phrase, DOCS_ONLY, frq-ZSTD}; chained
   FNV-1a digests over the merged .tis/.tii/.frq/.prx pinned as
   literals captured at this HEAD. MergedEqualsDirectWrite
   additionally asserts the live invariant merge == direct write
   byte-for-byte (it already holds today); the upcoming streaming
   merge batches must keep every one of these bytes identical.

3. ScopedHeapHighWater test utility: heap high-water sampling for
   the later peak-memory RED cases (ASAN
   __sanitizer_get_current_allocated_bytes 200us sampler; jemalloc
   thread.peak fallback; GTEST_SKIP-able when neither exists) with
   self-tests.

Wide SPIMI ASAN suite: 451 tests, 449 pass, 2 pre-existing
env-driven skips, 0 sanitizer reports.
…nput

Batch 1 (S2) of the spill/merge streaming work. At Finish() time the
residual posting buffer used to be FlushBuffer()ed into one more spill
(7 tmp files written) and immediately LoadSpill()ed back — a full disk
round-trip of the (often largest) final segment for nothing.

New SpillManager::EmitBufferToInput emits the residual buffer through
EmitSegment into MemoryByteOutput sinks and hands back a
SegmentMerger::Input shaped exactly like LoadSpill's, appended as the
LAST merge input (highest doc range, absolute-doc-id order contract
unchanged). The merger is untouched. Encoding parameters are lockstep
with FlushBuffer (index_version, omit_tfap, omit_norms, inline_small_
terms) and MemoryByteOutput/FileByteOutput report identical FilePointer
streams, so the merged output stays byte-identical — verified by the
batch-0 golden digests (all 10 unchanged) and a direct-write baseline
comparison in the new tests.

Edge cases preserved: k=0 pure direct write never enters EmitMerged;
empty residual (ShouldFlush latched, zero records) contributes no input,
mirroring FlushBuffer's empty-skip (NULL/empty-segment behavior intact);
zero spills + non-empty residual keeps the MergeSingleInput byte-copy
fast path reachable via the single in-memory input.

RED -> GREEN: new SpillManager::TotalSpillsCreated() (monotonic, not
reset by cleanup) lets tests observe spills created DURING Finish;
FinishResidualBufferDoesNotCreateNewSpill failed on the old path
(3 spills vs 2) and passes now, with the final segment byte-equal to
the direct-write baseline. Wide SPIMI ASAN suite: 452 passed /
2 pre-existing env-skips / 0 sanitizer reports.
…df tier selection

Kill the per-term vector<DecodedDoc> materialization in the k-way spill
merge: a df=N term used to allocate N structs plus N per-doc positions
heap blocks (plus a defensive stable_sort of already-ordered runs),
dominating merge peak memory on high-df terms.

- PostingDecoder::DecodeFlat appends a term run into flat arrays
  (doc_deltas/freqs + verbatim within-doc position VInt bytes + per-doc
  byte offsets), re-basing each subsequent input's first delta against
  the previous run's last absolute doc id. Decode() is now a thin
  wrapper over the same single envelope/codec core (DecodeFrqFlat +
  ResolvePrxInner), so both shapes share one dispatch implementation.
- SegmentMerger drains the term heap in two phases: collect every
  input's (input, TermInfo) first — df precedes the posting pointers in
  the .tis entry — so the merged sigma-df drives the slim/windowed/
  inline tier exactly like a direct write of the same data (sigma-df >=
  512 upgrades slim runs straddling spills to windowed; never the
  inputs' per-run is_slim).
- Emission reuses the existing encoders byte-for-byte:
  EmitSlimTermPreEncoded (rebuilt docCode VInts + spliced raw .prx
  payload through the same FlushProxRaw ZSTD policy),
  EmitWindowedTermPreDecoded (extended to DOCS_ONLY: dd-only windowed
  terms, mirroring FinishTermWindowed's omit-mode call), and a flat
  replay for legacy (pre-V4) PFOR re-encode.

Byte identity: merged output stays byte-for-byte equal to a direct
write of the same data — batch-0 golden digests unchanged
(phrase/docsonly/frqzstd x direct/k1/k2/k7) and every new RED case also
asserts 4-stream equality against direct write.

Peak (ASAN net-allocation high-water across LoadSpill+Merge, new RED
suite spill_merge_peak_red_test.cpp, red-before/green-after):
  R1 windowed df~800K k=2:   60MB -> 26MB  (line 32MB)
  R3 const-block df=1M k=2:  68MB -> 28MB  (line 32MB)
  R4 DOCS_ONLY df~800K k=2:  49MB ->  7MB  (line 16MB)
  R5 heavy-pos df~600K k=3:  77MB -> 34MB  (line 48MB)
  R2/R6/R7 guards (sigma-df 511/512/513 straddle, k=3 absolute ids,
  staged-inline lockstep) green before and after.

Full SPIMI ASAN suite: 459 passed / 2 pre-existing env-skips / 0
sanitizer reports.
EmitMerged previously loaded every spill's full .tis/.tii/.frq/.prx back
into memory (LoadSpill), so the k-way merge peak carried the SUM of all
spill bytes for the whole merge. Replace the full load with k x 4
forward sliding-window cursors (ForwardByteSource): the spill layout
already guarantees sequential consumption (.tis in term order, posting
pointers monotonically non-decreasing), and both the .frq and .prx
decodes are strictly forward and self-delimiting given doc_freq, so no
block end needs to be known up front.

- byte_source.{h,cpp} (new): ForwardByteSource (inline hot path, virtual
  Refill only on window exhaustion) + MemoryByteSource (borrowing /
  owning) + SpillFileByteSource (pread window of min(1MiB, stream len)).
- posting_decoder: DecodeFlat core now consumes ForwardByteSource; the
  (ptr,len) overload wraps a memory source. kProxRaw scans VInts straight
  off the source; ZSTD/windowed envelopes inflate exactly as before.
- term_enum: parse over ForwardByteSource; the legacy vector ctor keeps
  the borrowed inline-span contract; the streaming ctor copies inline
  spans into a per-entry scratch (bounded by the inline cap).
- segment_merger: StreamInput (four cursors) is the core Merge input;
  the vector<Input> overload is a thin wrapper, so output bytes are
  identical on both entries. take() copies inline spans into the
  TermSource; MergeSingleInput chunk-copies streams and reads the .tis
  footer via ReadAt, keeping the k=1 byte-copy fast path.
- spill_manager: OpenSpillCursor(i, out, buffer_bytes=1MiB); LoadSpill
  stays for tests/diagnostics.
- spimi_index_writer: EmitMerged opens cursors; the residual buffer's
  in-memory Input is wrapped as an owning memory cursor.

RED->GREEN (ASAN heap high-water, spill_merge_peak_red_test):
- new R8 (httplogs-shaped 500K-doc term sea, k=7, file-backed sinks):
  91,163,420B peak with the full load -> 21,071,702B with cursors
  (inputs 77,545,850B; line 64MB).
- R1/R3/R4/R5 lines tightened (input bytes no longer a legitimate
  residency allowance); R9 stresses 4KB cursor windows (phrase +
  DOCS_ONLY) for refill/seek/inline-span lifetimes under ASAN.
- byte golden unchanged: all 10 digests (phrase/docsonly/frqzstd x
  direct/k1/k2/k7) hold literally; merged == direct write in every cell.
- spill_segment_merger_test: fix a latent past-the-end TermEntry read in
  CompactModeFlushBufferDoesNotSkipData (UB exposed by the new heap
  layout).

Wide ASAN SPIMI suite: 463 ran / 461 passed / 2 pre-existing env-skips /
0 sanitizer reports.
Batch 4 of the spill-merge overhaul: when a merged term's sum-df stays
below the skip interval (512), every input run is necessarily SLIM, so
the k-way merge now splices the posting bytes directly instead of
flat-decoding and re-encoding every value:

- .frq chain: only each subsequent run's FIRST docCode is re-based
  (delta vs the previous run's last doc, freq low-bit and trailing freq
  VInt preserved); all remaining bytes are copied verbatim while a
  read-only scan tracks the re-base anchor and sum-freq. Canonical
  LEB128 keeps the first run's chain fully verbatim.
- .prx: the whole-term envelope is resolved to its raw payload and
  spliced verbatim (kProxRaw scans sum-freq VInts, kProxZstd inflates
  first); the merged block's mode-byte/ZSTD policy is re-run on the
  concatenated payload by EmitSlimTermPreEncoded, so output bytes stay
  identical to a direct write (and to the flattened re-encode path).

Dispatch priority: k=1 whole-segment byte copy > k>1 slim concat >
flattened re-encode (sum-df >= 512 upgrades unchanged). New
PostingDecoder::ConcatSlimRun hosts the splice core; SegmentMerger
gains thread-local MergeStats (verbatim hit-rate observability) and a
test-only force-reencode hook for the cross-path byte assertion.

Tests (RED->GREEN): spill_merge_slim_concat_test pins concat == forced
re-encode == direct write byte-for-byte across omit x k in {2,3,7},
the 511/512/513 sum-df upgrade boundary, the slim .prx 512B ZSTD gate
in both directions (raw inputs -> compressed merged block, compressed
inputs -> inflate then concat), and 4KB streaming-cursor refill
boundaries. Hit rate on the matrix: 124/126 slim terms take the
verbatim path. Byte-identity golden digests unchanged; full SPIMI ASAN
suite green (465 tests).
…truncated-spill error, frqzstd/V1-envelope goldens

Close the four non-blocker gaps from the adversarial review of the
streaming spill-merge batches (0 production code changes, all existing
golden literals unchanged):

- G3-b: FinishSingleSpillEmptyResidualByteIdentity{,DocsOnly} pin the
  production-reachable single-spill + empty-residual Finish, hitting
  MergeSingleInput x file cursor x CopyWholeStream chunked branch
  (BorrowStable==nullptr) byte-identical to a direct write; MergeStats
  asserts the path (single_input_segments==1, zero concat/reencode).
  DOCS_ONLY side checks the zero-length .prx no-op via filesystem size
  (openInput rejects empty files).
- G3-a: FinishTruncatedSpillFileThrowsCorrupt truncates _spill_0.frq to
  3 bytes (spill path pinned via inverted_index_spimi_spill_path) and
  asserts Finish rethrows doris::Exception cleanly through
  FINALLY_CLOSE + CleanupSpillFiles — first direct test of the
  SpillFileByteSource short-read corrupt branch.
- G1-a: golden matrix adds frqzstd_k7 (== frqzstd_direct digest) and
  frqzstd_docsonly_direct/k2 (equal pair) literals.
- G1-b: LegacyV1WholeTermZstdEnvelopeGolden feeds a V1 whole-term
  kCodeModeZstd envelope (V4-production-unreachable, shadow/debug only)
  into the merge: k=2 V1 spills (win_5000 df_i=2500 >= 512 forces the
  envelope, asserted on the input bytes), merged output byte-identical
  to a V0 direct write + digest pinned.

Wide ASAN SPIMI suite: 471 ran / 469 passed / 2 env-gated skipped,
zero ASAN errors.
…comparison

V2/CLucene retains per-doc norms (.nrm) for analyzed phrase-on fields, but Doris
fulltext MATCH is a filter and never consumes BM25 norms — the .nrm stream is
dead weight that inflates V2 idx sizes versus V4 (which omits norms
unconditionally). Add a runtime mBool config (default false, zero behavior
change) that drops norms on the shared CLucene write path (storage format V2 and
V3) when enabled; V4/SPIMI already omits norms on its own path.

Wire the SPIMI_RAM_DIR and SPIMI_OMIT_NORMS benchmark envs to the matching
configs (inverted_index_ram_dir_enable / inverted_index_v2_omit_norms) so UT
microbenchmarks and the E2E cluster measure under the same apples-to-apples
conditions, and print v2_omit_norms in the [BENCH-CONFIG] provenance line.

Add UT NormsOmittedWhenV2OmitNormsConfigOn covering the new config=true branch
(analyzed phrase-on field -> no .nrm), with RAII restore of the process-global
config so it cannot pollute other cases. Verified: 2/2 InvertedIndexWriterTest
norm cases pass under ASAN.
@airborne12

Copy link
Copy Markdown
Member Author

run buildall

The CI Clang Formatter uses clang-format 16, but this file was committed under
clang-format 20 (local ldb_toolchain), which wraps a designated struct-init and
an array literal differently. Reformat the two hunks to v16 to green the check.
No behavior change.
@airborne12

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.32% (1889/2443)
Line Coverage 64.38% (33945/52725)
Region Coverage 64.78% (17456/26948)
Branch Coverage 53.94% (9340/17316)

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.32% (1889/2443)
Line Coverage 64.39% (33952/52725)
Region Coverage 64.75% (17450/26948)
Branch Coverage 53.93% (9339/17316)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 0.00% (0/8) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 0.00% (0/8) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29523 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 15d5d93b2191e1acdade830cfe78abc7972d6e4c, data reload: false

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17716	4017	3972	3972
q2	q3	10885	1457	814	814
q4	4743	476	348	348
q5	8328	898	588	588
q6	334	177	134	134
q7	904	850	643	643
q8	11040	1696	1582	1582
q9	7131	4561	4506	4506
q10	6830	1827	1767	1767
q11	436	272	244	244
q12	643	424	288	288
q13	18150	3393	2834	2834
q14	270	258	242	242
q15	q16	823	778	711	711
q17	1006	884	995	884
q18	6997	5788	5560	5560
q19	1181	1294	1138	1138
q20	573	449	286	286
q21	6012	2853	2671	2671
q22	469	382	311	311
Total cold run time: 104471 ms
Total hot run time: 29523 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	4882	4681	4716	4681
q2	q3	4985	5225	4665	4665
q4	2355	2224	1413	1413
q5	4821	4658	4718	4658
q6	237	178	125	125
q7	2036	1794	1552	1552
q8	2447	2191	2035	2035
q9	7414	7433	7429	7429
q10	4731	4641	4209	4209
q11	535	380	349	349
q12	732	735	533	533
q13	3020	3351	2817	2817
q14	273	279	248	248
q15	q16	682	693	606	606
q17	1333	1288	1264	1264
q18	7485	6784	6909	6784
q19	1143	1148	1105	1105
q20	2210	2230	1962	1962
q21	5287	4578	4426	4426
q22	523	466	410	410
Total cold run time: 57131 ms
Total hot run time: 51271 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29578 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 2bafac2afd4f31ae8e62d66f9544d255cb89baca, data reload: false

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17948	4076	4054	4054
q2	q3	11003	1414	814	814
q4	4810	487	339	339
q5	8579	869	580	580
q6	360	173	138	138
q7	894	847	623	623
q8	10919	1613	1666	1613
q9	7057	4511	4578	4511
q10	6850	1821	1537	1537
q11	441	271	263	263
q12	645	434	291	291
q13	18154	3515	2842	2842
q14	271	259	241	241
q15	q16	814	781	715	715
q17	1013	926	1016	926
q18	7170	5828	5667	5667
q19	1166	1483	1125	1125
q20	520	406	265	265
q21	6124	2721	2756	2721
q22	453	375	313	313
Total cold run time: 105191 ms
Total hot run time: 29578 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	4843	4952	4706	4706
q2	q3	4913	5303	4665	4665
q4	2114	2255	1441	1441
q5	4885	4698	4704	4698
q6	254	188	127	127
q7	1850	1606	1446	1446
q8	2247	1953	1927	1927
q9	7388	7468	7388	7388
q10	4766	4700	4260	4260
q11	535	384	359	359
q12	728	744	524	524
q13	3006	3418	2831	2831
q14	277	285	262	262
q15	q16	675	696	621	621
q17	1293	1256	1257	1256
q18	7265	6736	6863	6736
q19	1104	1096	1097	1096
q20	2201	2234	1973	1973
q21	5308	4606	4438	4438
q22	543	449	420	420
Total cold run time: 56195 ms
Total hot run time: 51174 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 168480 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 15d5d93b2191e1acdade830cfe78abc7972d6e4c, data reload: false

query5	4323	621	477	477
query6	434	187	172	172
query7	4852	549	316	316
query8	363	210	187	187
query9	8753	4035	4013	4013
query10	440	308	262	262
query11	5916	2350	2187	2187
query12	158	106	96	96
query13	1273	615	429	429
query14	6308	5379	5451	5379
query14_1	4441	4389	4409	4389
query15	209	205	174	174
query16	1004	476	447	447
query17	1119	706	609	609
query18	2486	477	362	362
query19	216	188	149	149
query20	114	108	105	105
query21	215	144	119	119
query22	13575	13607	13393	13393
query23	17407	16598	16209	16209
query23_1	16299	16315	16378	16315
query24	7635	1773	1316	1316
query24_1	1315	1289	1312	1289
query25	561	459	399	399
query26	1303	331	173	173
query27	2630	541	331	331
query28	4519	2043	2005	2005
query29	1093	617	502	502
query30	309	238	200	200
query31	1115	1081	982	982
query32	98	68	60	60
query33	547	323	254	254
query34	1160	1117	674	674
query35	756	807	669	669
query36	1391	1386	1260	1260
query37	150	103	87	87
query38	3207	3116	2969	2969
query39	953	922	901	901
query39_1	869	888	859	859
query40	216	122	100	100
query41	71	63	61	61
query42	94	91	95	91
query43	314	316	271	271
query44	
query45	190	185	176	176
query46	1090	1219	738	738
query47	2368	2356	2222	2222
query48	383	427	280	280
query49	613	469	343	343
query50	999	344	251	251
query51	4356	4352	4303	4303
query52	87	85	74	74
query53	242	272	187	187
query54	260	224	191	191
query55	75	75	68	68
query56	259	231	208	208
query57	1414	1390	1310	1310
query58	247	211	209	209
query59	1599	1675	1428	1428
query60	278	241	224	224
query61	156	144	152	144
query62	690	648	590	590
query63	229	182	181	181
query64	2490	750	635	635
query65	
query66	1800	451	351	351
query67	29806	29700	28981	28981
query68	
query69	428	305	263	263
query70	992	951	985	951
query71	295	214	211	211
query72	2897	2590	2289	2289
query73	879	762	415	415
query74	5110	4932	4783	4783
query75	2641	2570	2240	2240
query76	2304	1147	747	747
query77	340	363	276	276
query78	12515	12354	11959	11959
query79	1269	1031	797	797
query80	532	471	380	380
query81	454	278	245	245
query82	249	159	123	123
query83	356	273	251	251
query84	
query85	854	515	411	411
query86	370	287	275	275
query87	3358	3315	3188	3188
query88	3613	2775	2746	2746
query89	405	379	335	335
query90	1948	188	187	187
query91	174	163	147	147
query92	66	61	52	52
query93	1391	1393	892	892
query94	543	354	319	319
query95	676	480	345	345
query96	1092	833	378	378
query97	2724	2712	2568	2568
query98	212	208	202	202
query99	1149	1165	1024	1024
Total cold run time: 249757 ms
Total hot run time: 168480 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 169091 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 2bafac2afd4f31ae8e62d66f9544d255cb89baca, data reload: false

query5	4295	630	483	483
query6	424	192	175	175
query7	4833	533	308	308
query8	366	212	202	202
query9	8739	4036	4049	4036
query10	434	310	248	248
query11	5956	2394	2153	2153
query12	152	102	97	97
query13	1318	606	431	431
query14	6375	5372	5070	5070
query14_1	4394	4386	4370	4370
query15	206	199	185	185
query16	1050	453	457	453
query17	1131	722	565	565
query18	2617	474	333	333
query19	192	176	136	136
query20	108	114	101	101
query21	218	137	118	118
query22	13662	13557	13357	13357
query23	17413	16593	16225	16225
query23_1	16256	16311	16326	16311
query24	7803	1778	1294	1294
query24_1	1301	1304	1304	1304
query25	544	433	371	371
query26	1306	311	166	166
query27	2704	540	331	331
query28	4441	2018	2020	2018
query29	1066	606	481	481
query30	309	247	190	190
query31	1105	1072	954	954
query32	108	58	58	58
query33	529	319	250	250
query34	1167	1168	655	655
query35	750	793	676	676
query36	1378	1396	1291	1291
query37	149	102	88	88
query38	3251	3138	3042	3042
query39	935	932	906	906
query39_1	868	892	860	860
query40	213	125	102	102
query41	64	63	59	59
query42	94	95	92	92
query43	319	325	274	274
query44	
query45	191	185	179	179
query46	1053	1224	761	761
query47	2356	2384	2233	2233
query48	369	416	304	304
query49	619	469	352	352
query50	985	346	259	259
query51	4359	4322	4227	4227
query52	87	86	75	75
query53	242	263	189	189
query54	266	230	191	191
query55	79	74	72	72
query56	243	226	210	210
query57	1433	1406	1323	1323
query58	251	223	214	214
query59	1567	1629	1443	1443
query60	277	241	229	229
query61	155	153	155	153
query62	718	658	583	583
query63	237	192	187	187
query64	2516	759	591	591
query65	
query66	1860	449	343	343
query67	29208	29672	29602	29602
query68	
query69	412	303	261	261
query70	959	974	964	964
query71	307	223	211	211
query72	2864	2639	2307	2307
query73	881	746	434	434
query74	5088	4957	4729	4729
query75	2649	2569	2251	2251
query76	2333	1150	798	798
query77	349	389	287	287
query78	12444	12486	11907	11907
query79	1231	1104	752	752
query80	548	497	421	421
query81	455	287	259	259
query82	254	163	123	123
query83	279	283	260	260
query84	
query85	872	580	502	502
query86	330	305	290	290
query87	3408	3403	3180	3180
query88	3637	2777	2727	2727
query89	411	392	331	331
query90	2153	182	181	181
query91	187	173	148	148
query92	64	65	59	59
query93	1443	1455	850	850
query94	546	376	296	296
query95	706	488	368	368
query96	1118	830	371	371
query97	2740	2696	2575	2575
query98	211	210	236	210
query99	1142	1177	1020	1020
Total cold run time: 250044 ms
Total hot run time: 169091 ms

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 1.29% (2/155) 🎉
Increment coverage report
Complete coverage report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants