Skip to content

Fix UTF-8 partial-match ranking#101

Open
Uzer007-admin wants to merge 2 commits into
Restream:masterfrom
Uzer007-admin:ft-ranking-utf8
Open

Fix UTF-8 partial-match ranking#101
Uzer007-admin wants to merge 2 commits into
Restream:masterfrom
Uzer007-admin:ft-ranking-utf8

Conversation

@Uzer007-admin

Copy link
Copy Markdown
Contributor

Summary

This PR fixes partial-match ranking for UTF-8 strings in the fast full-text index.

Problem

The previous ranking formula calculated the partial-match penalty using UTF-8 byte lengths:

wordBytes - patternBytes + bytesBeforePattern

For a valid substring match, this is equivalent to:

2 * bytesBeforePattern + bytesAfterPattern

As a result:

  • multibyte UTF-8 characters received an incorrect penalty;
  • unmatched prefixes were penalized twice for suffix matches;
  • ranking depended on the UTF-8 byte width instead of the number of characters.

Solution

The penalty is now calculated from the number of unmatched Unicode characters:

max(wordChars - patternChars, 0)

The Unicode character count of each unique indexed word is calculated during indexing and cached as a 16-bit value. This avoids repeated UTF-8 decoding in the query hot path.

The cache is kept synchronized during index clearing and recommit operations, and its capacity is included in memory statistics.

Behavior changes

This intentionally changes the ranking of:

  • partial matches containing multibyte UTF-8 characters;
  • some ASCII suffix matches that were affected by the previous extra positional penalty.

There are no public API, configuration, or storage format changes.

The additional memory cost is approximately two bytes per unique indexed word, based on vector capacity.
s PR.

Calculate partial-match penalties from unmatched Unicode characters and cache character counts for indexed words.
if (words.empty()) {
words.reserve(words_um.size());
}
holder.ReserveWords(words.size() + words_um.size());

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.

words.size() + words_um.size() does not looks correct for the cases, when words already contain something. Actually, current logic may overreserve here up to 2 times

Comment thread cpp_src/core/ft/ft_fast/dataholder.cc Outdated
for (auto& w : words_) {
res += sizeof(w) + w.vids.heap_size();
}
res += wordsCharsLen_.capacity() * sizeof(wordsCharsLen_[0]);

@MadSchemas MadSchemas Jul 21, 2026

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.

wordsCharsLen_[0] is unsafe from ASAN/Debug standpoint. Size of value_type would be better

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.

2 participants