テキスト索引の作成
tokenizer 引数では、使用するトークナイザーを指定します。
splitByNonAlphaは、ASCII の英数字以外の文字で文字列を分割します (関数 splitByNonAlpha も参照) 。splitByString(S)は、ユーザー定義の区切り文字列Sで文字列を分割します (関数 splitByString も参照) 。 区切り文字は省略可能なパラメータで指定できます。たとえば、tokenizer = splitByString([', ', '; ', '\n', '\\'])のようにします。 なお、各文字列は複数の文字で構成できます (例の', 'など) 。 明示的に指定しない場合のデフォルトの区切り文字リスト (たとえばtokenizer = splitByString) は、単一の空白文字[' ']です。ngrams(N)は、文字列を同じ長さのN-gram に分割します (関数 ngrams も参照) 。 ngram の長さは、2 から 8 までの省略可能な整数パラメータで指定できます。たとえば、tokenizer = ngrams(3)のようにします。 明示的に指定しない場合のデフォルトの ngram サイズ (たとえばtokenizer = ngrams) は 3 です。arrayはトークン化を行いません。つまり、各行の値がそのまま 1 つのトークンになります (関数 array も参照) 。sparseGrams(min_length, max_length, min_cutoff_length)— sparseGrams 関数と同じアルゴリズムを使って、文字列をmin_lengthのすべての ngram と、それより長くmax_length以下のいくつかの ngram に分割します。min_cutoff_lengthを指定した場合、長さがmin_cutoff_length以上の N-gram のみが索引に保存されます。固定長の N-gram だけを生成するngrams(N)とは異なり、sparseGramsは指定した範囲内で可変長の N-gram の集合を生成するため、テキストコンテキストをより柔軟に表現できます。たとえば、tokenizer = sparseGrams(3, 5, 4)は入力文字列から 3-gram、4-gram、5-gram を生成し、そのうち 4-gram と 5-gram だけを索引に保存します。
splitByString トークナイザーは、区切り文字を左から右の順に適用します。
そのため、あいまいさが生じることがあります。
たとえば、区切り文字列 ['%21', '%'] を指定すると、%21abc は ['abc'] としてトークン化されます。一方、区切り文字列の順序を ['%', '%21'] に入れ替えると、['21abc'] が出力されます。
多くの場合は、より長い区切り文字が先にマッチするようにするのが望ましいでしょう。
これは通常、区切り文字列を長い順に渡すことで実現できます。
区切り文字列がたまたま プレフィックスコード を構成している場合は、任意の順序で渡せます。preprocessor は、トークン化の前に入力文字列を変換する式です。
プリプロセッサ引数の典型的な用途は次のとおりです。
- 大文字と小文字を区別しないマッチングを可能にするために、入力文字列を小文字化 (または大文字化) すること。たとえば lower、lowerUTF8 です。最初の例を参照してください。
- UTF-8 の正規化。たとえば normalizeUTF8NFC、normalizeUTF8NFD、normalizeUTF8NFKC、normalizeUTF8NFKD、toValidUTF8 です。
- 不要な文字や部分文字列を削除または変換すること。たとえば extractTextFromHTML、substring、idnaEncode です。
INDEX idx(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = lower(col))INDEX idx(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = substringIndex(col, '\n', 1))INDEX idx(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = lower(extractTextFromHTML(col))
省略可能な高度なパラメーター
省略可能な高度なパラメーター
以下の高度なパラメーターのデフォルト値は、ほぼすべての状況で問題なく機能します。
これらを変更することは推奨しません。省略可能なパラメーター
dictionary_block_size (デフォルト: 128) は、Dictionary ブロックのサイズを行数で指定します。省略可能なパラメーター dictionary_block_frontcoding_compression (デフォルト: 1) は、Dictionary ブロックで圧縮に front coding を使用するかどうかを指定します。省略可能なパラメーター max_cardinality_for_embedded_postings (デフォルト: 16) は、posting list を Dictionary ブロックに埋め込むカーディナリティのしきい値を指定します。省略可能なパラメーター bloom_filter_false_positive_rate (デフォルト: 0.1) は、Dictionary のブルームフィルターの偽陽性率を指定します。テキスト索引の使用
対応している関数
WHERE句でテキスト関数を使用する場合は、テキスト索引を利用できます。
= と !=
= (equals) と != (notEquals ) は、指定した検索語全体に一致します。
例:
= と != をサポートしていますが、等値検索と不等値検索が有効なのは、索引が各行の値全体を保存する array トークナイザーを使用する場合のみです。
IN and NOT IN
IN (in) とNOT IN (notIn) は、関数equalsおよびnotEqualsと似ていますが、検索語のすべてに一致するか (IN) 、どれにも一致しない場合 (NOT IN) に使用します。
例:
= と != の場合と同じ制約が適用されます。つまり、IN と NOT IN が意味を持つのは、array トークナイザーと組み合わせた場合に限られます。
LIKE、NOT LIKE、match
現在、これらの関数がフィルタリングにテキスト索引を使用するのは、索引のトークナイザーが
splitByNonAlpha または ngrams の場合に限られます。LIKE like、NOT LIKE (notLike) 、および match 関数をテキスト索引と組み合わせて使用するには、ClickHouse が検索語から完全なトークンを抽出できる必要があります。
例:
support は、この例では support、supports、supporting などに一致する可能性があります。
この種のクエリは部分文字列クエリであり、テキスト索引で高速化することはできません。
LIKE クエリでテキスト索引を活用するには、LIKE パターンを次のように書き換える必要があります。
support の左右に空白を入れることで、その語を token として抽出できるようになります。
startsWith と endsWith
LIKE と同様に、関数 startsWith と endsWith でテキスト索引を使用できるのは、検索語から完全なトークンを抽出できる場合に限られます。
例:
clickhouse のみです。
support は support、supports、supporting などに一致しうるため、トークンではありません。
clickhouse supports で始まるすべての行を見つけるには、検索パターンの末尾に半角スペースを付けてください:
endsWith も先頭にスペースを付けて使用する必要があります。
hasToken and hasTokenOrNull
hasToken と hasTokenOrNull は、text 索引で使用できる関数の中で最も高性能です。
hasAnyTokens and hasAllTokens
has
mapContains
mapContainsKey のエイリアス) は、マップのキー内の単一のトークンにマッチします。
例:
operator[]
Array(T) と Map(K, V) でテキスト索引を使用する方法については、以下の例を参照してください。
テキスト索引における Array と Map のサポート例
Array(String) への索引作成
clickhouse) を含む投稿を見つけるには、全件をスキャンする必要があります:
keywords 配列をすべて調べる必要があるため、処理はますます遅くなります。
このパフォーマンス上の問題を解決するには、keywords に対してテキスト索引を定義します。これにより、すべての keywords を事前処理する検索向けに最適化された構造が作成され、高速なルックアップが可能になります。
重要: テキスト索引を追加した後、既存データに対しては索引を再構築する必要があります。
Map の索引付け
- レート制限に関するすべてのログを検索します:
- 特定のIPからのすべてのログを検索します。
重要: テキスト索引を追加したら、既存データに対して再構築する必要があります。
- レート制限されたリクエストをすべて見つけます。
- 特定のIPからのすべてのログを検索します:
実装
索引レイアウト
- 各トークンをポスティングリストに対応付ける辞書
- それぞれが行番号の集合を表すポスティングリストの集合
dictionary_block_size で設定できます) 。
辞書ブロックファイル (.dct) は、パート内のすべてのインデックスグラニュールにある辞書ブロックをまとめたものです。
インデックスグラニュールファイル (.idx)
インデックスグラニュールファイルには、各辞書ブロックについて、そのブロックの先頭トークン、辞書ブロックファイル内での相対オフセット、およびそのブロック内の全トークンに対する bloom filter が含まれます。
このスパースインデックス構造は、ClickHouse のスパースな主キー索引)に似ています。
bloom filter により、検索対象のトークンが辞書ブロックに含まれていない場合、その辞書ブロックを早い段階でスキップできます。
ポスティングリストファイル (.pst)
すべてのトークンのポスティングリストは、ポスティングリストファイル内に順次配置されます。
容量を節約しつつ、高速な積集合およびユニオン演算を可能にするため、ポスティングリストは roaring bitmaps として格納されます。
ポスティングリストのカーディナリティが 16 未満の場合 (パラメータ max_cardinality_for_embedded_postings で設定可能) 、そのポスティングリストは辞書に埋め込まれます。
Direct read
- 設定 query_plan_direct_read_from_text_index (デフォルト: 1) は、direct read が通常有効かどうかを指定します。
- 設定 use_skip_indexes_on_data_read (デフォルト: 1) は、direct read のもう 1 つの前提条件です。compatibility < 25.10 の ClickHouse データベースでは、
use_skip_indexes_on_data_readが無効になっている点に注意してください。そのため、compatibility 設定の値を引き上げるか、SET use_skip_indexes_on_data_read = 1を明示的に設定する必要があります。
ALTER TABLE ... MATERIALIZE INDEX を使用します) 。
サポートされる関数
direct read 最適化では、hasToken、hasAllTokens、hasAnyTokens 関数をサポートしています。
これらの関数は、AND、OR、NOT 演算子で組み合わせることもできます。
WHERE clause には、追加の非テキスト検索関数フィルタ (テキストカラムまたは他のカラムに対するもの) を含めることもできます。その場合でも direct read 最適化は使用されますが、効果は低下します (適用されるのはサポートされているテキスト検索関数に対してのみです) 。
クエリで direct read が使われていることを確認するには、EXPLAIN PLAN actions = 1 を付けてクエリを実行してください。
たとえば、direct read を無効にしたクエリは次のとおりです。
query_plan_direct_read_from_text_index = 1 を指定して同じクエリを実行した場合は
__text_index_<index_name>_<function_name>_<id> が含まれます。
このカラムが存在する場合は、direct read が使用されます。
例: Hacker News データセット
hackernewsテーブルに挿入しましょう。
ALTER TABLE を使用して comment カラムにテキスト索引を追加し、その後これを実体化します:
hasToken、hasAnyTokens、hasAllTokens 関数を使ってクエリを実行してみましょう。
以下の例では、標準的な索引スキャンと direct read 最適化の間にある大きな性能差を確認できます。
1. hasToken の使用
hasToken は、テキストに特定の単一トークンが含まれているかどうかを確認します。
大文字と小文字を区別するトークン「ClickHouse」を検索します。
Direct read 無効 (標準スキャン)
デフォルトでは、ClickHouse はスキップ索引を使ってグラニュールを絞り込み、その後、それらのグラニュールのカラムデータを読み取ります。
この動作は、direct read を無効にすることで再現できます。
2. hasAnyTokens を使う
hasAnyTokens は、テキストに指定したトークンのうち少なくとも 1 つが含まれているかどうかを確認します。
‘love’ または ‘ClickHouse’ のいずれかを含むコメントを検索します。
Direct read 無効 (標準スキャン)
3. hasAllTokens の使用
hasAllTokens は、テキストに指定したすべてのトークンが含まれているかどうかを確認します。
‘love’ と ‘ClickHouse’ の両方を含むコメントを検索します。
Direct read 無効 (標準スキャン)
direct read を無効にしていても、標準のスキップ索引は引き続き有効です。
28.7M 行を 147.46K 行まで絞り込めますが、それでもカラムから 57.03 MB を読み取る必要があります。
4. 複合検索: OR, AND, NOT, …
hasAnyTokens(comment, ['ClickHouse', 'clickhouse']) のほうが、より効率的で推奨される構文です。