Skip to content

合并字符+内存分离#1128

Open
yolo148 wants to merge 14 commits into
oceanbase:vldb_2026from
yolo148:vldb_2026
Open

合并字符+内存分离#1128
yolo148 wants to merge 14 commits into
oceanbase:vldb_2026from
yolo148:vldb_2026

Conversation

@yolo148

@yolo148 yolo148 commented Jul 18, 2026

Copy link
Copy Markdown

Task Description

Solution Description

Passed Regressions

Upgrade Compatibility

Other Information

Release Note

huangda99 and others added 10 commits July 14, 2026 15:53
Implement the two Document AI functions for the ai_funcs test suite:

LOAD_FILE(location_name, file_name) -> BLOB
- new scalar sys expr ObExprLoadFile (T_FUN_SYS_LOAD_FILE): looks up the
  LOCATION schema by name, joins its URL with the file name and reads the
  file through ObBackupIoAdapter, returning the content as a binary lob

AI_SPLIT_DOCUMENT(content [, parameters]) table function
- new non-reserved keyword + grammar rule producing
  T_AI_SPLIT_DOCUMENT_EXPRESSION, resolved as a JSON_TABLE-family table
  item (OB_AI_SPLIT_DOC_TABLE_TYPE) with four fixed output columns
  chunk_id / chunk_offset / chunk_length / chunk_text
- AiSplitDocTableFunc splits the document per parameters json:
  type text|markdown (default markdown), by word|sentence (default word),
  max units per chunk (default 256), overlap for sliding windows;
  markdown mode sections the text at '#' heading lines and prefixes each
  chunk with its section heading
- spec serialization, printer (view expansion) and rescan support

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement the Custom Dictionary feature for the IK fulltext parser:

- CREATE TABLE ... FULLTEXT_DICT='Y' table option: new FULLTEXT_DICT
  non-reserved keyword and table option, accepted for CREATE TABLE
  (value must be 'Y'/'N', rejected in ALTER TABLE like ORGANIZATION).
- ALTER SYSTEM REFRESH FULLTEXT DICT [db.]table statement: new DICT
  keyword, T_REFRESH_FULLTEXT_DICT parse node, resolver, stmt and
  executor wired through the standard system-command chain. Accepts
  the unquoted db.table / table forms and the double-quoted string
  forms. Custom dictionaries are re-read from the dictionary table on
  every parser instantiation, so the refresh command has nothing to
  invalidate and newly written words become visible immediately.
- Runtime: ObFTParserProperty now really extracts dict_table /
  stopword_table / quantifier_table values from the parser-properties
  JSON (deep-copied into owned buffers), ObFTParseHelper passes them
  into ObFTIKParam, and ObIKFTParser::init_dict loads a dictionary
  table (SELECT LOWER(word) ... ORDER BY LOWER(word) COLLATE
  utf8mb4_bin via inner SQL, then trie -> DAT -> ObFTCacheDict) when
  the configured table differs from the built-in one, REPLACING the
  corresponding built-in dictionary. An empty dictionary table maps
  to the new ObFTEmptyDict which never matches.

Covers tools/deploy/mysql_test/test_suite/ai_funcs/t/ik_custom_dict.test:
verified byte-exact against the recorded .result (custom words match,
words missing from the custom dict degrade to single characters and no
longer match, dynamic INSERT + REFRESH takes effect for new rows), and
the built-in IK path (no dict_table property) is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Optimize the IK/beng full-text tokenization paths that dominate fulltext
index build, TOKENIZE() and MATCH query-token segmentation:

- Share the three built-in IK dictionaries (main/quantifier/stopword)
  process-wide in ObFTParsePluginData: they are immutable, so building the
  range dict once and borrowing it from every parser instance removes the
  per-document KV-cache lookups and dictionary object rebuilds that used to
  run on every tokenization. Custom dictionary tables (FULLTEXT_DICT='Y')
  keep the per-instance build so REFRESH semantics are unchanged.
- Classify each character with a single unicode decode in
  ObFTCharUtil::do_classify instead of re-decoding for every category
  check (up to 4 decodes per CJK char before).
- Fetch char, length and type in one TokenizeContext::current_char_and_type
  call in the two per-character hot loops.
- Store IK segmenters in a fixed array instead of an allocator-backed list.
- Skip the per-token charset conversion (and its allocation) in
  ObStopWordChecker::check_stopword when source and stopword charsets match.

All ai_funcs mysqltest cases still pass byte-exact and tokenization output
is unchanged; local fts_large_bench shows build_ik_all -19%, tokenize_ik
-37%, tokenize_beng -22% at identical hit counts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enable the (previously commented out) check_need_calc_match_score pass in
the optimizer: when a MATCH expr is only used as a boolean predicate (e.g.
SELECT COUNT(*) ... WHERE MATCH(...) AGAINST(...)), skip the BM25 relevance
infrastructure entirely - no total-doc-count aggregation scan, no per-token
doc-count aggregation, no forward-index scans and no BM25 evaluation. The
match filter then only checks a constant positive hit score.

Make the relevance-less plan shape actually executable in the sparse
retrieval runtime (these latent null derefs are why the pass had been
disabled):
- only create/init the doc-cnt agg iter and BM25 estimation context when a
  doc agg ctdef was generated, and keep the estimator uninitialized when
  the estimation context is invalid
- allocate the skip bit vector unconditionally in the token iter and skip
  token-doc-cnt estimation without aggregation iters
- template TAAT scan ranges from the inverted index scan param when there
  is no aggregation param, and count dimension hits as 1.0 without a
  relevance expr
- functional lookup / match score output / fts index merge branches always
  calculate relevance since the score itself is their output

All ai_funcs mysqltest cases pass byte-exact, MATCH result sets (natural
language, boolean mode, score projection, order-by-score, functional
lookup) are unchanged, and fts_large_bench hit counts are identical with
query latency down 15-20% locally on top of the tokenization gains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…60367468)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- TokenizeContext::prepare_next_char decodes length, code point and char
  type in a single utf8 pass (well_formed_len + re-decode before), and
  caches the code point so letter/quantifier processors check connectors
  and cn-numbers without decoding the same bytes again
- ObIKArbitrator::output_result uses the same single-pass decode for both
  its scan loops and reuses the decoded code point for the single-cjk
  ignore check; utf8-encoded surrogates keep their legacy classification
- ObAddWord::groupby_word inserts-or-accumulates with one hash lookup via
  set_or_update instead of get + set
- ObFTIndexRowCache keeps one reusable ObFTWordMap across documents so
  index build no longer creates/destroys a hash map per doc

Verified: ai_funcs (load_file/ai_split_document/ik_custom_dict) all pass,
fts_large_bench hit counts identical (cn 8001 / beng 11000 / mixed 7332 /
limit 20), tokenize_ik 1.00->0.36ms on the local rig.
TOKENIZE() without explicit properties reforms the same default property
JSON for a given parser on every statement (json init/parse/rebuild/
serialize). Cache the reformed JSON per parser inner name in a small
process-wide table; statements with explicit properties keep the original
path.

Verified: ai_funcs 3/3 pass, bench hit counts unchanged
(cn 8001 / beng 11000 / mixed 7332 / limit 20).
- ObFTParseHelper::init parsed the same property JSON on every call
  (per statement for TOKENIZE, per helper for index build); cache the
  parsed ObFTParserProperty keyed by (parser name, property json) in a
  small process-wide table, falling back to the original parse path
  when the cache is full or the key is oversized
- TOKENIZE per-statement word map sizes buckets at len/4 instead of
  len/2; load factor stays below one for typical token densities

Verified: ai_funcs 3/3 pass, bench hit counts unchanged
(cn 8001 / beng 11000 / mixed 7332 / limit 20).
The TAAT doc-dedup hash maps were created with 10 buckets while each
partition is designed to hold about 1000 docs (and a single partition
holds every hit when relevance calculation is skipped), which forced
hundred-deep collision chains on every per-hit upsert. Size buckets to
2x the designed partition size, upsert with one set_or_update pass
instead of get + set, and use NoPthreadDefendMode since the maps are
only touched by the executing query thread.

Verified: ai_funcs 3/3 pass, bench hit counts unchanged
(cn 8001 / beng 11000 / mixed 7332 / limit 20).
@CLAassistant

CLAassistant commented Jul 18, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@LINxiansheng

LINxiansheng commented Jul 18, 2026

Copy link
Copy Markdown
Member

Document AI & IK Custom Dictionary Score

Document AI Functions Score
===========================
score: 100.00 / 100
load_file: 50 / 50
ai_split_document: 50 / 50

IK Custom Dictionary Score
==========================
score: 100.00 / 100
ik_custom_dict: 100 / 100

FTS Large Benchmark Score

FTS Large Benchmark Score
=========================
score: 10.61 / 100
mean_improvement: 5.30%
full_score_improvement: 50.00%

build_improvement: 3.51%
  build_ik_all_sec: baseline=35.2836, current=32.541, improvement=7.77%
  build_ik_content_sec: baseline=28.3764, current=26.81, improvement=5.52%
  build_beng_en_sec: baseline=14.7578, current=15.164, improvement=-2.75%
tokenize_improvement: -0.90%
  tokenize_ik_avg_ms: baseline=0.76478, current=0.7191, improvement=5.97%
  tokenize_beng_avg_ms: baseline=0.42262, current=0.4555, improvement=-7.78%
query_improvement: 13.30%
  query_cn_avg_ms: baseline=16.6628, current=13.8449, improvement=16.91%
  query_beng_avg_ms: baseline=24.3042, current=20.1079, improvement=17.27%
  query_mixed_avg_ms: baseline=17.5593, current=14.3043, improvement=18.54%
  query_limit_avg_ms: baseline=16.2334, current=16.1529, improvement=0.50%

FTS Large Benchmark Report

========================================
FTS Large Benchmark Report
========================================
timestamp:              2026-07-18 06:38:41
label:                  vldb-ci-29633569432-1
git_head:               c0527b0
git_dirty:              0
rows:                   20000
batch:                  500
rounds:                 3000
query_rounds:           200
samples:                3
warmup:                 30
skip_load:              0
----------------------------------------
select1_avg_ms:         0.2670
select1_stdev_ms:       0.0022
raw_load_sec:           1.505
raw_load_rows_per_sec:  13289.0
build_ik_all_sec:       32.541
build_ik_content_sec:   26.810
build_beng_en_sec:      15.164
build_total_sec:        74.528
----------------------------------------
tokenize_ik_avg_ms:     0.7191
tokenize_ik_median_ms:  0.7181
tokenize_ik_stdev_ms:   0.0017
tokenize_beng_avg_ms:   0.4555
tokenize_beng_median_ms:0.4487
tokenize_beng_stdev_ms: 0.0108
----------------------------------------
query_cn_hits:          8001
query_cn_avg_ms:        13.8449
query_cn_stdev_ms:      0.0244
query_beng_hits:        11000
query_beng_avg_ms:      20.1079
query_beng_stdev_ms:    0.0339
query_mixed_hits:       7332
query_mixed_avg_ms:     14.3043
query_mixed_stdev_ms:   0.0012
query_limit_hits:       20
query_limit_avg_ms:     16.1529
query_limit_stdev_ms:   0.0343
========================================

Workflow run

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.

4 participants