전문 검색 색인 스키마

전문 검색(Full-Text Search) 색인 정의를 위한 스키마

전문 검색은 완전, 혹은 부분 일치만 허용하는 필드 검색과 달리 정의된 쿼리에 맞는 검색 후보를 점수화하여 최적의 결괏값을 제공합니다. 이를 이용하여 토큰의 유사도에 기반하여 검색하거나 벡터 검색을 통해 의미상 유사성에 기반하여 검색할 수 있습니다.

전문 검색 색인은 하나 이상 생성이 가능합니다. 정의된 필드와 옵션에 따라 색인 생성 비용과 저장 용량이 증가할 수 있습니다.

다음은 전문 검색 색인 생성을 위한 스키마의 예시입니다. 아래의 예시는 문서의 아이디를 의미하는 doc_id와 문서의 본문인 content, 그리고 본문의 임베딩 데이터인 embedding 필드로 구성되어 있습니다.

{
    "name": "sk_fts",
    "fields": [
        "doc_id",
        "content",
        "embedding"
    ],
    "unique": False,
    "index_type": "kFullTextSearchIndex",
    "options": {
        "doc_id": {
            "analyzer": {
                "type": "KeywordAnalyzer"
            },
            "index_options": "doc_freqs"
        },
        "content": {
          "analyzer": {
            "type": "StandardAnalyzer"
          },
          "index_options": "offsets"
        },
        "embedding": {
            "analyzer": {
                "type": "DenseVectorAnalyzer",
                "options": {
                    "index_type": "HNSW",
                    "dims": 768,
                    "m": 64,
                    "ef_construction": 200,
                    "ef_search": 32,
                    "metric": "inner_product",
                    "normalize": True,
                    "shards": 1
                }
            },
            "index_options": "doc_freqs"
        }
    }
}

전문 검색은 다음과 같이 정의합니다.

  • name: 색인명
  • fields: 전문 검색에 사용할 필드의 목록
  • index_type: kFullTextSearchIndex를 입력
  • options: 전문 검색에 사용할 analyzer, tokenizer 등을 정의

options은 다음과 같이 구성됩니다.

  • fields에서 명세한 필드명
  • analyzer: 명세한 필드를 분석할 analyzer를 선택
  • index_options: 색인 옵션
{
    "doc_id": {
        "index_options": "doc_freqs",
        "analyzer": {
            "type": "KeywordAnalyzer"
        }
    }
},

Nested Fields

만약 입력된 필드에 계층적인 데이터가 포함되어 있다면 이 데이터는 별도의 데이터베이스 정규화 과정을 거치지 않고 입력과 색인 생성이 가능합니다.

예를 들어 다음과 같이 attribute 필드에 authorprice로 구성된 계층 구조가 포함되어 있으며 별도의 처리 없이 입력이 가능합니다.

data = [
    {"doc_id": "1", "title": "Mastering Machine Learning", "attribute": {"author": "finn", "price": 21000}},
    {"doc_id": "2", "title": "Database System", "attribute": {"author": "jeapil", "price": 33000}},
]
doc_db.insert(collection_name, data)
)

이렇게 입력된 데이터는 다음과 같이 attribute.author의 형식으로 계층 구조에 접근할 수 있고 색인을 지정하여 빠르게 검색할 수 있습니다.

{
    "name": "sk_fts",
    "fields": [
        "doc_id",
        "title",
        "attribute.author"
        "attribute.price"
    ],
    "unique": False,
    "index_type": "kFullTextSearchIndex",
    "options": {
        "doc_id": {
            "analyzer": {
                "type": "KeywordAnalyzer"
            },
            "index_options": "doc_freqs"
        },
        "title": {
          "analyzer": {
            "type": "StandardAnalyzer"
          },
          "index_options": "offsets"
        },
        "attribute.author": {
          "analyzer": {
            "type": "KeywordAnalyzer"
          },
          "index_options": "doc_freqs"
        },
        "attribute.author": {
          "analyzer": {
            "type": "Int64Analyzer"
          },
          "index_options": "doc_freqs"
        }
    }
}

Index Options

색인 옵션에 따라 각 필드에 대한 점수 계산 방식이 달라집니다. 색인 옵션에서 선택 가능한 항목은 다음과 같습니다.

  • doc_freqs: 문서에서 출현한 빈도수만 사용, 점수 계산이 제외됨
  • term_freqs: tokenizer에 의해 분해된 토큰의 빈도수를 사용
  • positions: 토큰의 빈도수와 토큰의 위치를 함께 고려함. 근접 혹은 구문 검색에 활용
  • offsets: 문서 빈도, 토큰 빈도, 위치, 시작과 끝 문자들이 위치 값을 함께 저장, 하이라이트 기능의 속도 개선을 위해 사용

Analyzer

Analyzer는 입력된 텍스트를 분석하는 역할을 합니다. 일반적으로 문자를 정규화하고 색인을 위한 토큰화 그리고 불용어 제거를 위한 토큰 필터 등의 작업을 조합하여 수행합니다.

Analyzer는 다음 하위 모듈이 통합되어 동작합니다.

각각의 analyzer는 다음과 같은 별명을 가지고 있습니다.

항목별명
CustomAnalyzercustom
KeywordAnalyzerkeyword
StandardAnalyzerstandard
StandardCJKAnalyzerstandard_cjk
DenseVectorAnalyzerdense_vector
RegexAnalyzerregex
DateTimeAnalyzerdatetime
BooleanAnalyzerboolean
Int64Analyzerint64
Float64Analyzerfloat64

따라서 다음 예시는

{
    "type": "KeywordAnalyzer"
}

다음 코드와 동일한 의미를 가집니다.

{
    "type": "keyword"
}

CustomAnalyzer

Tokenizer, Character Filter, Token Filter를 원하는 형식으로 구성할 수 있는 분석기 입니다.

options내용타입
char_filtersCharacter Filterstring | object[]
tokenizerTokenizerstring | object[]
token_filtersToken Filterstring | object[]

다음은 옵션 정의 예시입니다.

{
    "type": "CustomAnalyzer",
    "options": {
        "char_filters": [
            "LowerCaseCharacterFilter"
        ],
        "tokenizer": "ICUWordTokenizer",
        "token_filters": [
            "NormalizationTokenFilter",
            {
                "type": "SnowballTokenFilter",
                "options": {
                    "language": "english"
                }
            }
        ]
    }
}

KeywordAnalyzer

전달된 문자열을 하나의 토큰으로 다룹니다. 이 분석기는 분류코드, 카테고리 등을 텍스트를 알고 있고 전체 텍스트를 입력하여 검색하고자 할 때 적합합니다.

options내용타입
case_insensitive대소문자 구별하지 않음boolean
{
    "type": "KeywordAnalyzer"
    "options": {
        "case_insensitive": True
    }
}

StandardAnalyzer

단어 단위로 검색할 때 적합한 분석기입니다. 대소문자 필터가 적용되어 대소문자 구별없이 검색이 가능합니다.

options내용타입
char_filtersCharacter Filterstring | object[]
tokenizerTokenizerstring | object[]
stopwords_filterStopWordsTokenFilterdict
snowball_filterSnowballTokenFilterdict

아래는 CustomAnalyzer에 대응하는 분석기 옵션의 기본값입니다.

{
    "type": "CustomAnalyzer"
    "options": {
        "char_filters": [
            "LowerCaseCharacterFilter", "NormalizationCharacterFilter"
        ],
        "tokenizer": "ICUWordTokenizer",
        "token_filters": [
            {
                "type": "StopWordsTokenFilter",
                "options": {
                    "language": "english"
                }
            },
            {
                "type": "SnowballTokenFilter",
                "options": {
                    "language": "english"
                }
            }
        ]
    }
}

stopwords_filter, snowball_filter는 다음과 같은 형식으로 활성화 할 수 있습니다.

{
    "options": {
        "stopwords_filter": {
          "enabled": True
        },
        "snowball_filter": {
          "enabled": True
        }
    }
}

StandardCJKAnalyzer

형태소 혹은 n-gram 단위로 검색할 때 적합한 분석기입니다. 기본적으로 StandardAnalyzer와 분석기 옵션의 기본 동작이 동일합니다. 하지만 한국어와 같은 굴절어를 위해 NGramTokenFilterByteLengthTokenFilter를 선택하여 구성할 수 있습니다.

options내용타입
char_filtersCharacter Filterstring | object[]
tokenizerTokenizerstring | object[]
ngram_filterNGramTokenFilterdict
ngram_offsetsn-gram의 offset 사용 여부bool
stopwords_filterStopWordsTokenFilterdict
snowball_filterSnowballTokenFilterdict
byte_length_filterByteLengthTokenFilterdict

다음은 ngram_filterbyte_length_filter를 정의한 예시입니다.

{
    "type": "StandardCJKAnalyzer"
    "options": {
        "ngram_filter": {
            "min_size": 1,
            "max_size": 6
        },
        "byte_length_filter": {
            "min_length": 2,
            "max_length": 0
        }
    }
}

DenseVectorAnalyzer

벡터검색을 위한 색인입니다. 여기서 필수값은 차원인 dims이며 이는 임베딩 모델이 출력하는 차원과 일치해야 합니다. 여기에 m, ef_construction, ef_search 등을 조절하여 검색 시간과 성능을 조율할 수 있습니다.

다음은 옵션 정의 예시입니다.

{
    "type": "DenseVectorAnalyzer",
    "options": {
        "index_type": "HNSW",
        "dims": 768,
        "m": 64,
        "ef_construction": 200,
        "ef_search": 32,
        "metric": "inner_product",
        "normalize": True,
        "shards": 1
    }
}

options에서 선택할 수 있는 항목은 다음과 같습니다.

options내용가능한 입력값타입기본값
index_type색인 종류HNSW: 그래프 기반 색인
IVF_HNSW: 역색인이 포함된 그래프 기반 색인
strHNSW
dims차원수(필수 입력)int
m이웃 노드 수int32
metric거리 함수inner_product: 내적
L2: 유클리드
strinner_product
normalize정규화 여부boolFalse
shards샤드 크기int1
ef_construction색인에 사용되는 동적 큐의 크기int40
ef_search검색에 사용되는 동적 큐의 크기int16
num_inv_lists역색인의 수, IVF_HNSW의 옵션int65536
num_bits해싱된 백터의 정밀도, IVF_HNSW의 옵션int8
num_probe검색할 역색인의 수, IVF_HNSW의 옵션int10
quantizer양자화 비트수flat, 4bit, 6bit, 8bit, 8bit_direct, 16bitstr8bit
encoding인코딩 데이터 타입0: float16
1: float32
int1
training_set_size색인 클러스터 학습에 사용되는 샘플 수int10000

quantizer가 활성화 되는 경우 mdims % m == 0 조건을 만족해야 합니다.

RegexAnalyzer

기본적인 구성은 StandardAnalyzer와 같습니다. 이 분석기는 tokenizer가 RegexTokenizer로 선택되어 정규식으로 이용한 토큰 분리가 가능합니다.

options내용타입
patterns토큰 분리를 위한 정규식string
trim분리된 토큰에서 제거할 문자string

다음은 옵션 정의 예시입니다.

{
    "type": "RegexAnalyzer",
    "options": {
        "tokenizer": {
            "type": "RegexTokenizer",
            "options": {
                "patterns": [
                    "(\\b|\\$)\\d+(,\\d+)*[\\-\\w%]*",
                    "[a-zA-Zㄱ-ㅎ가-힣][&0-9a-zA-Zㄱ-ㅎ가-힣]*"
                ],
                "trim": ",- "
            }
        }
    }
}

DateTimeAnalyzer

ISO 8601 (opens in a new tab) 형식의 문자열을 UNIX time stamp 형식으로 변환합니다.

BooleanAnalyzer

문자열을 bool 타입으로 변환합니다.

Int64Analyzer

문자열을 int64 타입으로 변환합니다.

Float64Analyzer

문자열을 float64 타입으로 변환합니다.

Tokenizer

tokenizer는 입력된 문자열을 토큰으로 분리하여 반환하는 모듈입니다.

각각의 tokenizer는 다음과 같은 별명을 가지고 있습니다.

항목별명
KeywordTokenizerkeyword
WhitespaceTokenizerwhitespace
ICUWordTokenizericu
DelimiterTokenizerdelimiter
DateTimeTokenizerdatetime
NGramTokenizerngram
MeCabTokenizermecab
RegexTokenizerregex
PathHierarchyTokenizerpath_hierarchy
ReversePathHierarchyTokenizerreverse_path_hierarchy

KeywordTokenizer

입력된 문자열을 하나의 토큰으로 변환합니다.

WhitespaceTokenizer

입력된 문자열을 공백 단위로 분리된 토큰을 반환합니다.

ICUWordTokenizer

Unicode 문자가 고려된 단어 단위로 분리된 토큰을 반환합니다.

DelimiterTokenizer

delimiter에 정의된 문자를 기준으로 토큰이 분할됩니다.

다음과 같은 Tokenizer 정의가 있다면

{
    "type": "DelimiterTokenizer",
    "options": {
        "delimiter": ", "
    }
}

아래와 같은 문자열은

입력 문자열
Aeca API, Documents

다음과 같이 분해됩니다.

분해 결과
["Aeca", "API", "Documents"]

DateTimeTokenizer

ISO 8601 (opens in a new tab) 형식의 문자열을 UNIX time stamp 형식으로 변환된 토큰을 반환합니다.

NGramTokenizer

입력된 문자열을 n-gram (opens in a new tab)으로 분리합니다. 한국어와 같은 굴절어를 형태소 분석기를 사용하지 않고 검색할 때 유용합니다.

다음과 같은 tokenizer 정의가 있다면

{
    "type": "NGramTokenizer",
    "options": {
        "min_size": 2,
        "max_size": 4
    }
}

아래와 같은 문자열은

입력 문자열
코그니카

다음과 같이 분해됩니다.

분해 결과
["코그", "그니", "니카", "코그니", "그니카", "코그니카"]

옵션에 따라 토큰의 수가 크게 늘어날 수 있습니다.

MeCabTokenizer

MeCab 기반의 형태소 분석기를 사용하여 분리된 형태소를 토큰으로 반환합니다. 현재는 한국어 (opens in a new tab)만 지원하고 있습니다.

RegexTokenizer

정규식을 이용하여 토큰을 분리합니다.

다음은 옵션 정의 예시입니다.

{
    "type": "RegexTokenizer",
    "options": {
        "patterns": [
            "(\\b|\\$)\\d+(,\\d+)*[\\-\\w%]*",
            "[a-zA-Zㄱ-ㅎ가-힣][&0-9a-zA-Zㄱ-ㅎ가-힣]*"
        ],
        "trim": ",- "
    }
}

사용 가능한 옵션은 다음과 같습니다.

  • patterns: 분리할 토큰의 패턴
  • trim: 분리된 토큰에서 제거할 문자

PathHierarchyTokenizer

파일 경로와 같이 계층적인 구조를 가진 문자열을 delimiter를 기준으로 분리된 토큰을 반환합니다.

{
    "type": "PathHierarchyTokenizer",
    "options": {
        "delimiter": "/"
    }
}

아래와 같은 문자열은

입력 문자열
a/b/c

다음과 같이 분해됩니다.

분해 결과
["a", "a/b", "a/b/c"]

ReversePathHierarchyTokenizer

기본적인 동작은 PathHierarchyTokenizer와 유사하지만 반환하는 계층의 순서는 반대로 동작합니다.

{
    "type": "ReversePathHierarchyTokenizer",
    "options": {
        "delimiter": "/"
    }
}

아래와 같은 문자열은

입력 문자열
a/b/c

다음과 같이 분해됩니다.

분해 결과
["a/b/c", "a/b", "a"]

Character Filter

일반적으로 Character filter는 입력된 문자열을 정규화 하기 위한 목적으로 사용합니다.

각각의 Character filter는 다음과 같은 별명을 가지고 있습니다.

항목별명
LowerCaseCharacterFilterlower_case
UpperCaseCharacterFilterupper_case
NormalizationCharacterFilternormalization

LowerCaseCharacterFilter

입력된 문자열을 소문자로 변환합니다.

UpperCaseCharacterFilter

입력된 문자열을 대문자로 변환합니다.

NormalizationCharacterFilter

입력된 문자열을 ICU transforms (opens in a new tab) 룰에 따라 정규화 합니다.

변환을 위한 기본룰은 다음과 같습니다.

NFD; [:Mn:] Remove; NFC

Token Filter

토큰 필터는 분리된 토큰을 변환하거나 제거하는 기능을 가지고 있습니다.

각각의 token filter는 다음과 같은 별명을 가지고 있습니다.

항목별명
LowerCaseTokenFilterlower_case
UpperCaseTokenFilterupper_case
NormalizationTokenFilternormalization
ASCIIFoldingTokenFilterascii_folding
CharLengthTokenFilterchar_length
ByteLengthTokenFilterbyte_length
NGramTokenFilterngram
EdgeNGramTokenFilteredge_ngram
StopWordsTokenFilterstopwords
DoubleMetaphoneTokenFilterdouble_metaphone
ShingleTokenFiltershingle
SnowballTokenFiltersnowball
WordDelimiterTokenFilterword_delimiter

LowerCaseTokenFilter

입력된 토큰을 소문자로 변환합니다.

UpperCaseTokenFilter

입력된 토큰을 대문자로 변환합니다.

NormalizationTokenFilter

입력된 토큰을 ICU transforms (opens in a new tab) 룰에 따라 정규화 합니다.

변환을 위한 기본룰은 다음과 같습니다.

NFD; [:Mn:] Remove; NFC

ASCIIFoldingTokenFilter

라틴 계열의 문자를 ASCII에서 표현할 수 있는 문자로 변환합니다. 예를 들어 CaféCafe로 변홥합니다.

CharLengthTokenFilter

토큰의 문자 길이를 기준으로 토큰을 제외합니다.

다음과 같이 정의 했을 때 토큰은 최소 2자 이상 최대 4자까지 허용됩니다.

{
    "type": "CharLengthTokenFilter",
    "options": {
        "min_length": 2,
        "max_length": 4,
    }
}

아래와 같은 토큰이 존재할 때

입력 토큰
["다람쥐", "헌", "쳇바퀴에", "타고파"]

다음과 같이 출력 됩니다.

출력 토큰
["다람쥐", "쳇바퀴에", "타고파"]

ByteLengthTokenFilter

동작은 CharLengthTokenFilter와 동일 하지만 토큰의 문자열 길이가 아닌 바이트 단위로 계산합니다.

NGramTokenFilter

입력된 토큰을 n-gram (opens in a new tab)으로 분리합니다.

다음과 같은 정의가 있다면

{
    "type": "NGramTokenFilter",
    "options": {
        "min_size": 2,
        "max_size": 4,
    }
}

아래와 같은 토큰은

입력 토큰
["코그니카"]

다음과 같이 분해됩니다.

출력 토큰
["코그", "그니", "니카", "코그니", "그니카", "코그니카"]

옵션에 따라 토큰의 수가 크게 늘어날 수 있습니다.

EdgeNGramTokenFilter

NGramTokenFilter와 유사하게 입력된 토큰을 n-gram (opens in a new tab)으로 분리합니다. 하지만 토큰 시작 위치부터 지정된 길이까지의 n-gram만 생성합니다.

다음과 같은 정의가 있다면

{
    "type": "EdgeNGramTokenFilter",
    "options": {
        "min_size": "2",
        "max_size": "3",
    }
}

아래와 같은 토큰은

입력 토큰
["코그니카", "데이터베이스는"]

다음과 같이 분해됩니다.

출력 토큰
["코그", "코그니", "데이", "데이터"]

StopWordsTokenFilter

불용어에 해당되는 토큰을 제거합니다.

다음은 옵션 정의 예시입니다.

{
    "type": "StopWordsTokenFilter",
    "options": {
        "language": "english"
    }
}

사용 가능한 옵션은 다음과 같습니다.

  • language: 언어
    • english: 영어

DoubleMetaphoneTokenFilter

Double Metaphone (opens in a new tab) 방법에 따라 입력된 토큰을 음소 유사성 가진 토큰으로 정규화합니다.

ShingleTokenFilter

n-gram (opens in a new tab) 형식으로 인접한 토큰을 병합하여 새로운 토큰을 추가합니다.

예를 들어 다음과 같이 옵션을 정의하고

{
    "type": "ShingleTokenFilter",
    "options": {
        "min_size": 2,
        "max_size": 3,
        "output_unigrams": True,
        "seperator": " ",
    }
}

다음과 같이 입력된 토큰은

입력 토큰
["애플", "컴퓨터", "신규", "제품"]

다음과 같이 변환 됩니다.

출력 토큰
[
    "애플", "애플 컴퓨터", "애플 컴퓨터 신규",
    "컴퓨터", "컴퓨터 신규", "컴퓨터 신규 제품",
    "신규", "신규 제품",
    "제품"
]

사용 가능한 옵션은 다음과 같습니다.

  • min_size: n-gram 최소 크기
  • max_size: n-gram 최대 크기
  • output_unigrams: unigram 출력 여부
  • seperator: 두 토큰을 병합할 때 입력할 구분자

SnowballTokenFilter

Snowball stemmer (opens in a new tab)를 사용하여 어간으로 변환합니다.

다음은 옵션 정의 예시입니다.

{
    "type": "SnowballTokenFilter",
    "options": {
        "language": "english"
    }
}

사용 가능한 옵션은 다음과 같습니다.

  • language: 언어
    • danish
    • dutch
    • english
    • finnish
    • french
    • german
    • hungarian
    • italian
    • norwegian
    • portuguese
    • romanian
    • russian
    • spanish
    • swedish