텍스트 인덱스 생성
tokenizer 인수는 토크나이저를 지정합니다.
splitByNonAlpha는 ASCII 영숫자가 아닌 문자를 기준으로 문자열을 분할합니다(함수 splitByNonAlpha도 참조).splitByString(S)는 사용자 정의 구분자 문자열S를 기준으로 문자열을 분할합니다(함수 splitByString도 참조). 구분자는 선택적 매개변수로 지정할 수 있습니다. 예를 들어tokenizer = splitByString([', ', '; ', '\n', '\\'])와 같습니다. 각 문자열은 여러 문자로 이루어질 수 있습니다(예시의', '). 명시적으로 지정하지 않으면(예:tokenizer = splitByString) 기본 구분자 목록은 단일 공백 문자[' ']입니다.ngrams(N)는 문자열을 같은 크기의N-그램으로 분할합니다(함수 ngrams도 참조). n-그램 길이는 2에서 8 사이의 선택적 정수 매개변수로 지정할 수 있습니다. 예를 들어tokenizer = ngrams(3)와 같습니다. 명시적으로 지정하지 않으면(예:tokenizer = ngrams) 기본 n-그램 크기는 3입니다.array는 토큰화를 수행하지 않습니다. 즉, 각 행의 값 자체가 하나의 토큰이 됩니다(함수 array도 참조).sparseGrams(min_length, max_length, min_cutoff_length)— sparseGrams 함수와 동일한 알고리즘을 사용해 문자열을min_length길이의 모든 n-그램과max_length까지의 더 긴 일부 n-그램으로 분할합니다. 여기서max_length는 포함됩니다.min_cutoff_length를 지정하면 길이가min_cutoff_length이상인 N-그램만 인덱스에 저장됩니다. 고정 길이 N-그램만 생성하는ngrams(N)와 달리,sparseGrams는 지정된 범위 내에서 가변 길이 N-그램 집합을 생성하므로 텍스트 문맥을 더 유연하게 표현할 수 있습니다. 예를 들어tokenizer = sparseGrams(3, 5, 4)는 입력 문자열에서 3-, 4-, 5-그램을 생성하고, 이 중 4-그램과 5-그램만 인덱스에 저장합니다.
splitByString 토크나이저는 분할 구분자를 왼쪽에서 오른쪽 순서로 적용합니다.
이로 인해 모호성이 생길 수 있습니다.
예를 들어 구분자 문자열이 ['%21', '%']이면 %21abc는 ['abc']로 토큰화되지만, 구분자 문자열의 순서를 ['%', '%21']로 바꾸면 ['21abc']가 출력됩니다.
대부분의 경우 더 긴 구분자가 먼저 일치하도록 하는 것이 좋습니다.
일반적으로는 구분자 문자열을 길이가 긴 순서대로 전달하면 됩니다.
구분자 문자열이 prefix code를 이루는 경우에는 임의의 순서로 전달해도 됩니다.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_block_frontcoding_compression (기본값: 1)은 딕셔너리 블록이 압축 방식으로 프론트 코딩을 사용하는지 여부를 지정합니다.선택적 매개변수 max_cardinality_for_embedded_postings (기본값: 16)는 포스팅 리스트를 딕셔너리 블록에 내장할지 결정하는 카디널리티 임계값을 지정합니다.선택적 매개변수 bloom_filter_false_positive_rate (기본값: 0.1)는 딕셔너리 블룸 필터의 위양성 비율을 지정합니다.텍스트 인덱스 사용
지원되는 함수
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의 왼쪽과 오른쪽에 있는 공백은 해당 용어를 토큰으로 추출할 수 있게 합니다.
startsWith 및 endsWith
LIKE와 마찬가지로 startsWith 및 endsWith 함수도 검색어에서 완전한 토큰을 추출할 수 있는 경우에만 텍스트 인덱스를 사용할 수 있습니다.
예시:
clickhouse만 토큰으로 간주됩니다.
support는 support, supports, supporting 등에 일치할 수 있으므로 토큰이 아닙니다.
clickhouse supports로 시작하는 모든 행을 찾으려면 검색 패턴 끝에 공백을 추가하십시오:
endsWith도 앞에 공백을 넣어 사용해야 합니다:
hasToken and hasTokenOrNull
hasToken 및 hasTokenOrNull은 text 인덱스와 함께 사용할 때 가장 높은 성능을 제공하는 함수입니다.
hasAnyTokens 및 hasAllTokens
has
mapContains
mapContainsKey)은 맵의 키에 있는 단일 토큰과 일치하는지 확인합니다.
예시:
operator[]
Array(T) 및 Map(K, V)를 사용하는 예를 확인할 수 있습니다.
텍스트 인덱스의 Array 및 Map 지원 예시
Array(String) 인덱싱
clickhouse)가 포함된 게시물을 찾기 위해 모든 항목을 스캔해야 합니다:
keywords 배열을 전부 검사해야 하므로 점점 더 느려집니다.
이 성능 문제를 해결하기 위해 keywords에 텍스트 인덱스를 정의할 수 있습니다. 이렇게 하면 모든 키워드를 사전 처리하는 검색 최적화 구조가 생성되어 즉시 조회할 수 있습니다:
중요: 텍스트 인덱스를 추가한 후에는 기존 데이터에도 인덱스를 다시 빌드해야 합니다:
맵 인덱싱
- rate limiting이 적용된 모든 로그를 찾습니다:
- 특정 IP의 모든 logs를 찾습니다:
중요: 텍스트 인덱스를 추가한 후에는 기존 데이터에 대해 인덱스를 다시 빌드해야 합니다:
- 속도 제한이 적용된 모든 요청을 찾습니다:
- 특정 IP에서 생성된 모든 로그를 찾습니다:
구현
인덱스 레이아웃
- 각 토큰을 포스팅 리스트에 매핑하는 딕셔너리
- 그리고 각각이 행 번호 집합을 나타내는 포스팅 리스트 집합입니다.
dictionary_block_size로 설정할 수 있습니다).
딕셔너리 블록 파일(.dct)은 하나의 파트에 있는 모든 인덱스 그래뉼의 모든 딕셔너리 블록으로 구성됩니다.
인덱스 그래뉼 파일 (.idx)
인덱스 그래뉼 파일에는 각 딕셔너리 블록마다 블록의 첫 번째 토큰, 딕셔너리 블록 파일 내 상대 오프셋, 그리고 블록의 모든 토큰에 대한 블룸 필터가 포함됩니다.
이 희소 인덱스 구조는 ClickHouse의 희소 프라이머리 키 인덱스)와 유사합니다.
블룸 필터를 사용하면 검색 중인 토큰이 딕셔너리 블록에 없을 경우 해당 딕셔너리 블록을 미리 건너뛸 수 있습니다.
포스팅 리스트 파일 (.pst)
모든 토큰의 포스팅 리스트는 포스팅 리스트 파일에 순차적으로 배치됩니다.
공간을 절약하면서도 빠른 intersect 및 union 연산을 지원하기 위해 포스팅 리스트는 roaring bitmaps로 저장됩니다.
포스팅 리스트의 카디널리티가 16보다 작으면(매개변수 max_cardinality_for_embedded_postings로 설정 가능) 딕셔너리에 내장됩니다.
직접 읽기
- query_plan_direct_read_from_text_index 설정(기본값: 1): 직접 읽기를 기본적으로 활성화할지 지정합니다.
- use_skip_indexes_on_data_read 설정(기본값: 1): 직접 읽기를 사용하기 위한 또 다른 필수 조건입니다. ClickHouse 데이터베이스에서 compatibility < 25.10인 경우
use_skip_indexes_on_data_read가 비활성화되므로, compatibility 설정 값을 높이거나SET use_skip_indexes_on_data_read = 1을 명시적으로 설정해야 합니다.
ALTER TABLE ... MATERIALIZE INDEX를 사용하십시오).
지원되는 함수
직접 읽기 최적화는 hasToken, hasAllTokens, hasAnyTokens 함수를 지원합니다.
이 함수들은 AND, OR, NOT 연산자로 조합할 수도 있습니다.
WHERE 절에는 추가로 텍스트 검색 함수가 아닌 필터(텍스트 컬럼 또는 다른 컬럼에 대한 필터)도 포함될 수 있습니다. 이 경우에도 직접 읽기 최적화는 사용되지만 효과는 다소 떨어집니다(지원되는 텍스트 검색 함수에만 적용되기 때문입니다).
쿼리가 직접 읽기를 사용하는지 확인하려면 EXPLAIN PLAN actions = 1로 쿼리를 실행하십시오.
예를 들어, 직접 읽기가 비활성화된 쿼리는 다음과 같습니다.
query_plan_direct_read_from_text_index = 1로 실행하면
__text_index_<index_name>_<function_name>_<id>이 포함되어 있습니다.
이 컬럼이 있으면 직접 읽기가 사용된 것입니다.
예시: Hacker News 데이터셋
hackernews 테이블에 삽입해 보겠습니다:
ALTER TABLE을 사용해 comment 컬럼에 텍스트 인덱스를 추가한 뒤 이를 구체화합니다:
hasToken, hasAnyTokens, hasAllTokens 함수를 사용해 쿼리를 실행해 보겠습니다.
다음 예시에서는 표준 인덱스 스캔과 직접 읽기 최적화 간의 큰 성능 차이를 확인할 수 있습니다.
1. hasToken 사용하기
hasToken은 텍스트에 특정한 단일 토큰이 포함되어 있는지 확인합니다.
대소문자를 구분하는 토큰 ‘ClickHouse’를 검색합니다.
직접 읽기 비활성화 (표준 스캔)
기본적으로 ClickHouse는 스킵 인덱스를 사용해 그래뉼을 필터링한 다음, 해당 그래뉼의 컬럼 데이터를 읽습니다.
직접 읽기를 비활성화하면 이 동작을 시뮬레이션할 수 있습니다.
2. hasAnyTokens 사용하기
hasAnyTokens는 텍스트에 지정된 토큰(token) 중 하나 이상이 포함되어 있는지 확인합니다.
‘love’ 또는 ‘ClickHouse’가 포함된 댓글을 검색하겠습니다.
직접 읽기 비활성화(표준 스캔)
3. hasAllTokens 사용하기
hasAllTokens는 텍스트에 지정된 모든 token이 포함되어 있는지 확인합니다.
‘love’와 ‘ClickHouse’가 모두 포함된 댓글을 검색하겠습니다.
직접 읽기 비활성화됨 (표준 스캔)
직접 읽기가 비활성화된 경우에도 표준 스킵 인덱스는 여전히 효과적입니다.
28.7M행을 147.46K행으로 줄여 주지만, 여전히 컬럼에서 57.03 MB를 읽어야 합니다.
4. 복합 검색: OR, AND, NOT, …
hasAnyTokens(comment, ['ClickHouse', 'clickhouse']) 구문을 사용하는 것이 더 효율적이며 권장됩니다.