Files
Jack 14fde1c1dc [refactor](be) Unify MATCH and SEARCH execution across inverted index formats (#68758)
### What problem does this PR solve?

Issue Number: None

Related PR: #67180, #67538, #67918

Problem Summary:

MATCH and SEARCH each had their own Boolean, phrase, term-expansion and
scoring executors, written separately for CLucene (V1/V2) and SNII
indexes. Every query optimization had to be implemented once per
executor, the formats drifted apart in behavior, and cross-field queries
could mix document IDs from different physical partitions.

This PR runs both on one engine:

- **One logical form.** MATCH (`query/logical`) and SEARCH
(`exprs/function/search`) lower their queries to the same logical nodes.
- **One query engine.** Boolean logic (AND/OR/NOT, minimum_should_match,
WAND), term expansion (prefix, wildcard, regexp), exact and sloppy
phrases and BM25 scoring are implemented once, independent of the index
format.
- **Formats behind an SPI.** CLucene and SNII only provide dictionaries,
postings, positions, norms, statistics and batched I/O through
`query/spi`. The old per-format executors are removed.
- **Layered source tree.** `be/src/storage/index/` is reorganized by
layer, and `build-support/check-header-deps.py` enforces the allowed
include directions.

Index formats and writers are unchanged, and existing indexes remain
readable. FE is unchanged.

#### Source layout

```text
be/src/storage/index/
├── index_*.h/.cpp        shared index files: file reader/writer, compound reader, storage formats
├── ann/  bloom_filter/  zone_map/
└── inverted/
    ├── analysis/         analyzers, tokenizers, char and token filters, normalizers, gram schemes
    ├── common/           descriptors, caches, statistics, profiles, single flight
    ├── reader/           text index reader, index selection, collection statistics
    ├── query/            format-independent query engine
    │   ├── logical/      MATCH lowering and logical nodes (gram/ builds gram queries)
    │   ├── engine/       Query/Weight/Scorer: boolean, term, phrase, expansion, WAND, union, collect
    │   ├── exec/         doc-id sets and sinks, term waves, chained conjunctions
    │   ├── phrase/       exact and sloppy phrase matchers
    │   ├── scoring/      BM25
    │   └── spi/          the source interface each format implements
    ├── clucene/          CLucene (V1/V2) reader, searcher, writer, compaction
    │   └── spi/          CLucene index source and postings cursor
    └── snii/             SNII: format/, encoding/, io/, bkd/, reader/, writer/, compaction/, query/

be/src/exprs/
├── vsearch.h/.cpp                     VSearchExpr, the SEARCH expression node
└── function/search/
    ├── function_search.*              SEARCH entry: Boolean assembly, DSL result cache, NULL handling
    ├── search_lowering.*              SEARCH DSL to logical nodes; binds each field to an index
    ├── function_search_leaf.*         compiles each leaf into a query on its bound index
    ├── search_predicate_collector.*   collects scored leaves for BM25 statistics
    └── variant_inverted_index_search.*  SEARCH on VARIANT subcolumns and NESTED groups
```

#### Performance

The 18 production SQL cases (V2 and SNII: point, range, absent-term and
phrase queries) were compared with master on fresh dual-BE pairs: 8
pairs, 16 samples per case and binary, alternating order. Every case is
within the 5% limit for both `InvertedIndexFilterTime` and client wall
time; the largest ratios are 1.032 and 1.038. SNII phrase cases take
25-60% less FilterTime. TPC-H, TPC-DS and ClickBench are within noise.

<details><summary>Per-case median ratios (PR / master)</summary>

Run on 2026-10-14 against master 7ac0eb2fdb on a busy shared host
(median 90% CPU), which widens the intervals; all 4,608 result checks
pass.

| Case | FilterTime | Client wall |
| --- | ---: | ---: |
| `v2/long/early/full` | 1.030 | 1.024 |
| `v2/long/early/range` | 1.018 | 0.999 |
| `v2/long/absent/full` | 1.032 | 1.014 |
| `v2/long/absent/range` | 1.018 | 0.995 |
| `v2/long/mid/full` | 1.006 | 1.038 |
| `v2/long/mid/range` | 1.013 | 1.012 |
| `v2/short/tiny` | 0.965 | 1.018 |
| `v2/search/scored_phrase` | 1.010 | 0.990 |
| `v2/search/phrase_2` | 0.954 | 0.951 |
| `snii/long/early/full` | 0.950 | 0.994 |
| `snii/long/early/range` | 0.961 | 1.022 |
| `snii/long/absent/full` | 0.728 | 0.847 |
| `snii/long/absent/range` | 0.891 | 0.983 |
| `snii/long/mid/full` | 0.821 | 0.924 |
| `snii/long/mid/range` | 0.975 | 1.007 |
| `snii/short/tiny` | 1.020 | 0.985 |
| `snii/search/scored_phrase` | 0.404 | 0.971 |
| `snii/search/phrase_2` | 0.746 | 0.986 |

</details>
2026-10-16 10:22:35 +08:00
..
…

BE compile benchmark (build.sh --compile-bench)

A timing framework for analyzing and optimizing the BE C++ build speed. It answers two questions:

  1. Which build phase is slow (gensrc / submodules / cmake configure / compile)?
  2. Which translation unit / header / template is slow, and why?

Usage

# One cold, cache-free benchmark build of the BE, then print + save a report
./build.sh --compile-bench

# Additionally collect clang -ftime-trace data (per-header / per-template costs)
COMPILE_BENCH_TRACE=ON ./build.sh --compile-bench

# The usual knobs still apply
BUILD_TYPE=Release ./build.sh --compile-bench -j 16

Results are stored per run in be/compile-bench-results/<UTC timestamp>/:

file content
report.txt the human-readable report (also printed at the end of the run)
summary.json machine-readable summary, used by report.py compare
meta.tsv commit, toolchain, -j, PCH on/off, ... of the run
phases.tsv raw phase timestamps
compile_log.jsonl one record per compiler/linker invocation (wall/user/sys/rss)
ninja_log.txt copy of the build dir's .ninja_log

Compare two runs (e.g. before/after an optimization attempt):

python3 build-support/compile-bench/report.py compare \
    be/compile-bench-results/<old_run> be/compile-bench-results/<new_run>

Re-generate a report from raw data (e.g. after an interrupted run, using the still-existing build dir):

python3 build-support/compile-bench/report.py be/compile-bench-results/<run> \
    --build-dir be/build_Release_compile_bench --build-status interrupted

How caches are kept out of the measurement

Benchmark numbers are only comparable when every run does the same cold work:

  • ccache is fully disabled: the timing wrapper replaces ccache as CMAKE_<LANG>_COMPILER_LAUNCHER, and CCACHE_DISABLE=1 is exported as a belt-and-braces measure for anything else that might call ccache.
  • A dedicated build dir be/build_<Type>_compile_bench is deleted and recreated on every run: no incremental objects, no CMake cache, no stale ninja state. Your normal be/build_<Type> dir is left untouched, so day-to-day incremental builds are not harmed by benchmarking.
  • BE-only scope: FE, cloud, java extensions, cdc client, UI and output packaging are forcibly skipped; the run ends right after the C++ build and the report, before install.
  • PCH (ENABLE_PCH) stays at its normal default on purpose — the PCH is generated inside the fresh build dir each run, so it is cold work, and it is part of the real build being optimized. Override with ENABLE_PCH=OFF to compare with/without PCH.

What the report contains

  • Phases: gensrc, contrib submodules, datasketches install, cmake configure, build — with durations and share of total wall time.
  • Build summary: TU count, sum of per-TU wall/cpu time, effective parallelism, slowest TU, most memory-hungry TU, link times with peak RSS.
  • Top N slowest translation units with wall/user/sys/maxrss per file.
  • Wall time by directory (top-level and second-level under be/src) — tells you which module to attack first.
  • Last finishers from .ninja_log — the critical-path tail the whole build waits on (typically the monster TUs and the final link).
  • With COMPILE_BENCH_TRACE=ON (clang only): top headers by inclusive parse time, top template instantiations, and a frontend vs backend split for the slowest TUs — this is what tells you why a file is slow (header/include cost vs template instantiation vs codegen).

Implementation notes

  • cc-timing-wrapper.py sits in the compiler-launcher slot, fork/execs the real compiler with inherited stdio, and records wall time plus rusage (user/sys/maxrss) per invocation into compile_log.jsonl. It is a strict pass-through: exit codes and diagnostics are unchanged, and without DORIS_COMPILE_BENCH_DIR set it execs straight through.
  • Linker launchers (CMAKE_<LANG>_LINKER_LAUNCHER) need CMake >= 3.21; with an older CMake, link times still show up via the ninja log, just without RSS.
  • -ftime-trace JSON files land next to the object files in the (transient) bench build dir and are aggregated immediately at the end of the run; expect roughly 1-2 GB of temporary JSON for a full BE build. Header/template times are inclusive (nested includes count into their parents), so they rank hotspots but do not add up to wall time.
  • Timings from a make (non-ninja) build lose only the "last finishers" section; per-TU data comes from the wrapper and is generator-independent.