Autocomplete
출처: 문서
본문
자동 완성
자동 완성은 사용자가 입력하는 동안 제안 사항을 보여줍니다.
예를 들어, 사용자가 "pop"을 입력하면 OpenSearch는 "popcorn"이나 "popsicles" 같은 제안을 제공합니다. 이러한 제안은 사용자의 의도를 미리 파악하여 가능한 검색어를 더 빨리 찾을 수 있도록 안내합니다.
OpenSearch를 사용하면 키 입력마다 업데이트되고, 몇 가지 관련 제안을 제공하며, 오타를 허용하는 자동 완성을 설계할 수 있습니다.
다음 방법 중 하나를 사용하여 자동 완성을 구현하세요.
접두사 일치는 쿼리 시간에 이루어지는 반면, 다른 세 가지 방법은 인덱스 시간에 이루어집니다. 모든 방법은 다음 섹션에서 설명합니다.
접두사 일치
접두사 일치는 쿼리 문자열의 마지막 용어와 일치하는 문서를 찾습니다.
예를 들어, 사용자가 검색 UI에 "qui"를 입력했다고 가정해 보세요. 이 구문을 자동 완성하려면 match_phrase_prefix 쿼리를 사용하여 "qui" 접두사로 시작하는 모든 text_entry 필드 값을 검색합니다.
GET shakespeare/_search
{
"query": {
"match_phrase_prefix": {
"text_entry": {
"query": "qui",
"slop": 3
}
}
}
}
단어 순서와 상대적 위치를 유연하게 하려면 slop 값을 지정하세요. slop 옵션에 대한 자세한 내용은 Slop을 참조하세요.
접두사 일치는 특별한 매핑이 필요하지 않습니다. 데이터를 있는 그대로 사용합니다.
하지만 상당히 리소스 집약적인 작업입니다. a 접두사는 수십만 개의 용어와 일치할 수 있으며 사용자에게 유용하지 않을 수 있습니다.
접두사 확장의 영향을 제한하려면 max_expansions를 적절한 숫자로 설정하세요.
GET shakespeare/_search
{
"query": {
"match_phrase_prefix": {
"text_entry": {
"query": "qui",
"slop": 3,
"max_expansions": 10
}
}
}
}
쿼리가 확장될 수 있는 최대 용어 수입니다. 쿼리는 fuzziness에 지정된 거리 내에 있는 일치하는 용어 수로 검색어를 "확장"합니다.
쿼리 시간 자동 완성을 구현하는 쉬움은 성능 저하라는 대가를 따릅니다. 대규모로 이 기능을 구현할 때는 인덱스 시간 솔루션을 권장합니다. 인덱스 시간 솔루션을 사용하면 인덱싱 속도가 느려질 수 있지만, 이는 모든 쿼리가 아닌 한 번만 지불하는 비용입니다. 에지 n-그램, 입력 시 검색 및 완성 제안기 방법은 인덱스 시간 솔루션입니다.
에지 n-그램 일치
인덱싱 중에 에지 n-그램은 단어를 n개의 문자 시퀀스로 분할하여 부분 검색어를 더 빠르게 조회할 수 있도록 지원합니다.
"quick"이라는 단어를 n-그램화하면 결과는 n 값에 따라 달라집니다.
| n | 유형 | n-그램 |
|---|---|---|
| 1 | 유니그램 | [ q, u, i, c, k ] |
| 2 | 바이그램 | [ qu, ui, ic, ck ] |
| 3 | 트라이그램 | [ qui, uic, ick ] |
| 4 | 포-그램 | [ quic, uick ] |
| 5 | 파이브-그램 | [ quick ] |
자동 완성은 검색 구문의 시작 n-그램만 필요하므로 OpenSearch는 에지 n-그램이라는 특수한 유형의 n-그램을 사용합니다.
"quick"이라는 단어를 에지 n-그램화하면 다음과 같은 결과가 나옵니다.
qququiquicquick
이는 사용자가 입력하는 순서와 동일합니다.
에지 n-그램을 사용하도록 필드를 구성하려면 edge_ngram 필터가 있는 자동 완성 분석기를 생성하세요.
PUT shakespeare
{
"mappings": {
"properties": {
"text_entry": {
"type": "text",
"analyzer": "autocomplete"
}
}
},
"settings": {
"analysis": {
"filter": {
"edge_ngram_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 20
}
},
"analyzer": {
"autocomplete": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"edge_ngram_filter"
]
}
}
}
}
}
이 예제는 인덱스를 생성하고 에지 n-그램 필터와 분석기를 인스턴스화합니다.
edge_ngram_filter는 최소 n-그램 길이 1(단일 문자)과 최대 길이 20으로 에지 n-그램을 생성합니다. 따라서 최대 20글자 단어에 대한 제안을 제공합니다.
autocomplete 분석기는 문자열을 개별 용어로 토큰화하고, 용어를 소문자로 변환한 다음, edge_ngram_filter를 사용하여 각 용어에 대한 에지 n-그램을 생성합니다.
analyze 작업을 사용하여 이 분석기를 테스트하세요.
POST shakespeare/_analyze
{
"analyzer": "autocomplete",
"text": "quick"
}
에지 n-그램을 토큰으로 반환합니다.
qququiquicquick
검색 시간에는 standard 분석기를 사용하세요. 그렇지 않으면 검색 쿼리가 에지 n-그램으로 분할되어 q, u, i와 일치하는 모든 결과를 얻게 됩니다.
인덱스 시간과 쿼리 시간에 다른 분석기를 사용하는 몇 안 되는 경우 중 하나입니다.
GET shakespeare/_search
{
"query": {
"match": {
"text_entry": {
"query": "qui",
"analyzer": "standard"
}
}
}
}
응답에는 일치하는 문서가 포함됩니다.
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 533,
"relation": "eq"
},
"max_score": 9.712725,
"hits": [
{
"_index": "shakespeare",
"_id": "22006",
"_score": 9.712725,
"_source": {
"type": "line",
"line_id": 22007,
"play_name": "Antony and Cleopatra",
"speech_number": 12,
"line_number": "5.2.44",
"speaker": "CLEOPATRA",
"text_entry": "Quick, quick, good hands."
}
},
{
"_index": "shakespeare",
"_id": "54665",
"_score": 9.712725,
"_source": {
"type": "line",
"line_id": 54666,
"play_name": "Loves Labours Lost",
"speech_number": 21,
"line_number": "5.1.52",
"speaker": "HOLOFERNES",
"text_entry": "Quis, quis, thou consonant?"
}
}
...
]
}
}
또는 매핑 자체에서 search_analyzer를 지정할 수 있습니다.
"mappings": {
"properties": {
"text_entry": {
"type": "text",
"analyzer": "autocomplete",
"search_analyzer": "standard"
}
}
}
완성 제안기
완성 제안기는 제안 목록을 받아서 유한 상태 변환기(FST)라는 최적화된 데이터 구조로 구축합니다. 이는 본질적으로 그래프입니다. 이 데이터 구조는 메모리에 상주하며 빠른 접두사 조회에 최적화되어 있습니다. FST에 대한 자세한 내용은 Wikipedia를 참조하세요.
사용자가 입력함에 따라 완성 제안기는 일치하는 경로를 따라 FST 그래프를 한 문자씩 이동합니다. 사용자 입력이 끝나면 나머지 끝 부분을 검사하여 제안 목록을 생성합니다.
완성 제안기는 자동 완성 솔루션을 가능한 한 효율적으로 만들고 제안을 명시적으로 제어할 수 있게 해줍니다.
인덱스에 FST와 유사한 데이터 구조를 저장하는 completion이라는 전용 필드 유형을 사용하세요.
PUT shakespeare
{
"mappings": {
"properties": {
"text_entry": {
"type": "completion"
}
}
}
}
제안을 받으려면 suggest 매개변수와 함께 search 엔드포인트를 사용하세요.
GET shakespeare/_search
{
"suggest": {
"autocomplete": {
"prefix": "To be",
"completion": {
"field": "text_entry"
}
}
}
}
"to be"라는 구문은 text_entry 필드의 FST와 접두사 일치됩니다.
{
"took" : 29,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"autocomplete" : [
{
"text" : "To be",
"offset" : 0,
"length" : 5,
"options" : [
{
"text" : "To be a comrade with the wolf and owl,--",
"_index" : "shakespeare",
"_id" : "50652",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 50653,
"play_name" : "King Lear",
"speech_number" : 68,
"line_number" : "2.4.230",
"speaker" : "KING LEAR",
"text_entry" : "To be a comrade with the wolf and owl,--"
}
},
{
"text" : "To be a make-peace shall become my age:",
"_index" : "shakespeare",
"_id" : "78566",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 78567,
"play_name" : "Richard II",
"speech_number" : 20,
"line_number" : "1.1.160",
"speaker" : "JOHN OF GAUNT",
"text_entry" : "To be a make-peace shall become my age:"
}
},
{
"text" : "To be a party in this injury.",
"_index" : "shakespeare",
"_id" : "75259",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 75260,
"play_name" : "Othello",
"speech_number" : 57,
"line_number" : "5.1.93",
"speaker" : "IAGO",
"text_entry" : "To be a party in this injury."
}
},
{
"text" : "To be a preparation gainst the Polack;",
"_index" : "shakespeare",
"_id" : "33591",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 33592,
"play_name" : "Hamlet",
"speech_number" : 17,
"line_number" : "2.2.67",
"speaker" : "VOLTIMAND",
"text_entry" : "To be a preparation gainst the Polack;"
}
},
{
"text" : "To be a public spectacle to all:",
"_index" : "shakespeare",
"_id" : "3709",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 3710,
"play_name" : "Henry VI Part 1",
"speech_number" : 6,
"line_number" : "1.4.41",
"speaker" : "TALBOT",
"text_entry" : "To be a public spectacle to all:"
}
}
]
}
]
}
}
반환할 제안 수를 지정하려면 size 매개변수를 사용하세요.
GET shakespeare/_search
{
"suggest": {
"autocomplete": {
"prefix": "To n",
"completion": {
"field": "text_entry",
"size": 3
}
}
}
}
최대 3개의 문서가 반환됩니다.
{
"took" : 4109,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"autocomplete" : [
{
"text" : "To n",
"offset" : 0,
"length" : 4,
"options" : [
{
"text" : "To NESTOR",
"_index" : "shakespeare",
"_id" : "99707",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 99708,
"play_name" : "Troilus and Cressida",
"speech_number" : 3,
"line_number" : "",
"speaker" : "ULYSSES",
"text_entry" : "To NESTOR"
}
},
{
"text" : "To name the bigger light, and how the less,",
"_index" : "shakespeare",
"_id" : "91884",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 91885,
"play_name" : "The Tempest",
"speech_number" : 91,
"line_number" : "1.2.394",
"speaker" : "CALIBAN",
"text_entry" : "To name the bigger light, and how the less,"
}
},
{
"text" : "To nature none more bound; his training such,",
"_index" : "shakespeare",
"_id" : "40510",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 40511,
"play_name" : "Henry VIII",
"speech_number" : 18,
"line_number" : "1.2.126",
"speaker" : "KING HENRY VIII",
"text_entry" : "To nature none more bound; his training such,"
}
}
]
}
]
}
}
suggest 매개변수는 접두사 일치만 사용하여 제안을 찾습니다.
예를 들어, "To be, or not to be" 문서는 결과에 포함되지 않습니다. 특정 문서를 제안으로 반환하려면 직접 선별된 제안을 추가하고 가중치를 부여하여 제안의 우선순위를 지정할 수 있습니다.
입력 제안이 있는 문서를 인덱싱하고 가중치를 할당하세요.
PUT shakespeare/_doc/1?refresh=true
{
"text_entry": {
"input": [
"To n", "To be, or not to be: that is the question:"
],
"weight": 10
}
}
동일한 검색을 수행합니다.
GET shakespeare/_search
{
"suggest": {
"autocomplete": {
"prefix": "To n",
"completion": {
"field": "text_entry",
"size": 3
}
}
}
}
인덱싱된 문서가 첫 번째 결과로 표시됩니다.
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"autocomplete" : [
{
"text" : "To n",
"offset" : 0,
"length" : 4,
"options" : [
{
"text" : "To n",
"_index" : "shakespeare",
"_id" : "1",
"_score" : 10.0,
"_source" : {
"text_entry" : {
"input" : [
"To n",
"To be, or not to be: that is the question:"
],
"weight" : 10
}
}
},
{
"text" : "To NESTOR",
"_index" : "shakespeare",
"_id" : "99707",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 99708,
"play_name" : "Troilus and Cressida",
"speech_number" : 3,
"line_number" : "",
"speaker" : "ULYSSES",
"text_entry" : "To NESTOR"
}
},
{
"text" : "To name the bigger light, and how the less,",
"_index" : "shakespeare",
"_id" : "91884",
"_score" : 1.0,
"_source" : {
"type" : "line",
"line_id" : 91885,
"play_name" : "The Tempest",
"speech_number" : 91,
"line_number" : "1.2.394",
"speaker" : "CALIBAN",
"text_entry" : "To name the bigger light, and how the less,"
}
}
]
}
]
}
}
fuzzy 매개변수를 지정하여 쿼리에서 철자 오류를 허용할 수도 있습니다.
GET shakespeare/_search
{
"suggest": {
"autocomplete": {
"prefix": "rosenkrantz",
"completion": {
"field": "text_entry",
"size": 3,
"fuzzy" : {
"fuzziness" : "AUTO"
}
}
}
}
}
결과는 올바른 철자와 일치합니다.
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"autocomplete" : [
{
"text" : "rosenkrantz",
"offset" : 0,
"length" : 11,
"options" : [
{
"text" : "ROSENCRANTZ:",
"_index" : "shakespeare",
"_id" : "35196",
"_score" : 5.0,
"_source" : {
"type" : "line",
"line_id" : 35197,
"play_name" : "Hamlet",
"speech_number" : 2,
"line_number" : "4.2.1",
"speaker" : "HAMLET",
"text_entry" : "ROSENCRANTZ:"
}
}
]
}
]
}
}
정규 표현식을 사용하여 완성 제안기 쿼리의 접두사를 정의할 수 있습니다.
GET shakespeare/_search
{
"suggest": {
"autocomplete": {
"prefix": "rosen*",
"completion": {
"field": "text_entry",
"size": 3
}
}
}
}
자세한 내용은 completion 필드 유형 문서를 참조하세요.
입력 시 검색
OpenSearch에는 입력 시 검색 기능에 최적화되고 접두사 및 중위 완성을 모두 사용하여 용어를 일치시킬 수 있는 전용 search_as_you_type 필드 유형이 있습니다. search_as_you_type 필드는 사전에 사용자 정의 분석기를 설정하거나 제안을 인덱싱할 필요가 없습니다.
먼저 필드를 search_as_you_type으로 매핑하세요.
PUT shakespeare
{
"mappings": {
"properties": {
"text_entry": {
"type": "search_as_you_type"
}
}
}
}
문서를 인덱싱하면 OpenSearch가 자동으로 n-그램과 에지 n-그램을 생성하고 저장합니다. 예를 들어, that is the question 문자열을 생각해 보세요. 먼저 표준 분석기를 사용하여 용어로 분할되고, 용어는 text_entry 필드에 저장됩니다.
[
"that",
"is",
"the",
"question"
]
이러한 용어를 저장하는 것 외에도 이 필드에 대한 다음 2-그램이 text_entry._2gram 필드에 저장됩니다.
[
"that is",
"is the",
"the question"
]
이 필드에 대한 다음 3-그램이 text_entry._3gram 필드에 저장됩니다.
[
"that is the",
"is the question"
]
마지막으로 에지 n-그램 토큰 필터가 적용된 후 결과 용어가 text_entry._index_prefix 필드에 저장됩니다.
[
"t",
"th",
"tha",
"that",
...
]
그런 다음 multi-match 쿼리의 bool_prefix 유형을 사용하여 용어를 순서에 관계없이 일치시킬 수 있습니다.
GET shakespeare/_search
{
"query": {
"multi_match": {
"query": "uncle what",
"type": "bool_prefix",
"fields": [
"text_entry",
"text_entry._2gram",
"text_entry._3gram"
]
}
},
"size": 3
}
쿼리와 동일한 순서로 단어가 나타나는 문서가 결과에서 더 높은 순위를 받습니다.
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4759,
"relation" : "eq"
},
"max_score" : 10.437667,
"hits" : [
{
"_index" : "shakespeare",
"_id" : "2817",
"_score" : 10.437667,
"_source" : {
"type" : "line",
"line_id" : 2818,
"play_name" : "Henry IV",
"speech_number" : 5,
"line_number" : "5.2.31",
"speaker" : "HOTSPUR",
"text_entry" : "Uncle, what news?"
}
},
{
"_index" : "shakespeare",
"_id" : "37085",
"_score" : 9.437667,
"_source" : {
"type" : "line",
"line_id" : 37086,
"play_name" : "Henry V",
"speech_number" : 26,
"line_number" : "1.2.262",
"speaker" : "KING HENRY V",
"text_entry" : "What treasure, uncle?"
}
},
{
"_index" : "shakespeare",
"_id" : "79274",
"_score" : 9.358302,
"_source" : {
"type" : "line",
"line_id" : 79275,
"play_name" : "Richard II",
"speech_number" : 29,
"line_number" : "2.1.187",
"speaker" : "KING RICHARD II",
"text_entry" : "Why, uncle, whats the matter?"
}
}
]
}
}
용어를 순서대로 일치시키려면 match_phrase_prefix 쿼리를 사용할 수 있습니다.
GET shakespeare/_search
{
"query": {
"match_phrase_prefix": {
"text_entry": "uncle wha"
}
},
"size": 3
}
응답에는 접두사와 일치하는 문서가 포함됩니다.
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 6,
"relation" : "eq"
},
"max_score" : 16.37664,
"hits" : [
{
"_index" : "shakespeare",
"_id" : "2817",
"_score" : 16.37664,
"_source" : {
"type" : "line",
"line_id" : 2818,
"play_name" : "Henry IV",
"speech_number" : 5,
"line_number" : "5.2.31",
"speaker" : "HOTSPUR",
"text_entry" : "Uncle, what news?"
}
},
{
"_index" : "shakespeare",
"_id" : "6789",
"_score" : 16.37664,
"_source" : {
"type" : "line",
"line_id" : 6790,
"play_name" : "Henry VI Part 2",
"speech_number" : 60,
"line_number" : "1.3.202",
"speaker" : "KING HENRY VI",
"text_entry" : "Uncle, what shall we say to this in law?"
}
},
{
"_index" : "shakespeare",
"_id" : "7877",
"_score" : 16.37664,
"_source" : {
"type" : "line",
"line_id" : 7878,
"play_name" : "Henry VI Part 2",
"speech_number" : 13,
"line_number" : "3.2.28",
"speaker" : "KING HENRY VI",
"text_entry" : "Where is our uncle? whats the matter, Suffolk?"
}
}
]
}
}
마지막으로 마지막 용어를 접두사가 아닌 정확히 일치시키려면 match_phrase 쿼리를 사용할 수 있습니다.
GET shakespeare/_search
{
"query": {
"match_phrase": {
"text_entry": "uncle what"
}
},
"size": 5
}
응답에는 정확히 일치하는 항목이 포함됩니다.
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 14.437452,
"hits" : [
{
"_index" : "shakespeare",
"_id" : "2817",
"_score" : 14.437452,
"_source" : {
"type" : "line",
"line_id" : 2818,
"play_name" : "Henry IV",
"speech_number" : 5,
"line_number" : "5.2.31",
"speaker" : "HOTSPUR",
"text_entry" : "Uncle, what news?"
}
},
{
"_index" : "shakespeare",
"_id" : "6789",
"_score" : 9.461917,
"_source" : {
"type" : "line",
"line_id" : 6790,
"play_name" : "Henry VI Part 2",
"speech_number" : 60,
"line_number" : "1.3.202",
"speaker" : "KING HENRY VI",
"text_entry" : "Uncle, what shall we say to this in law?"
}
},
{
"_index" : "shakespeare",
"_id" : "100955",
"_score" : 8.947967,
"_source" : {
"type" : "line",
"line_id" : 100956,
"play_name" : "Troilus and Cressida",
"speech_number" : 28,
"line_number" : "3.2.98",
"speaker" : "CRESSIDA",
"text_entry" : "Well, uncle, what folly I commit, I dedicate to you."
}
}
]
}
}
이전 match_phrase 쿼리의 텍스트를 수정하고 마지막 문자를 생략하면 이전 응답의 문서가 하나도 반환되지 않습니다.
GET shakespeare/_search
{
"query": {
"match_phrase": {
"text_entry": "uncle wha"
}
}
}
결과는 비어 있습니다.
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}
자세한 내용은 search_as_you_type 필드 유형 문서를 참조하세요.