블로그

인덱싱하지 않은 데이터에 풀텍스트 검색을 제공하는 Elasticsearch ES|QL

MATCH와 TO_TEXT는 인덱싱하지 않은 데이터에 풀텍스트 검색을 제공합니다. ES|QL에서 계산된 열, 매핑되지 않은 필드 및 페더레이션된 소스를 검색합니다.

Get hands-on with Elasticsearch: Dive into our sample notebooks in the Elasticsearch Labs repo, start a free cloud trial, or try Elastic on your local machine now.

ES|QL MATCH는 이제 인덱싱하지 않은 데이터에 대해 풀텍스트 검색을 실행합니다. 계산된 열, 매핑되지 않은 필드, 즉석에서 조합된 스트링, 심지어 S3에 저장된 페더레이션된 데이터까지 모두 포함됩니다. 새로운 TO_TEXT 함수는 ES|QL에 모든 스트링을 분석 가능한 텍스트로 취급하도록 지시하므로, MATCH가 쿼리 수명 동안만 존재하는 값을 토큰화하고 대소문자를 구분하며 검색어 매칭을 수행할 수 있습니다. 이는 대부분의 쿼리 엔진이 인덱싱되지 않은 스트링에 대해 제공하는 LIKE 및 RLIKE 패턴 매칭을 넘어선 진정한 분석입니다. 현재 Elastic Cloud Serverless에서 이용 가능하며, Elasticsearch 9.5에서 기술 미리보기로 제공됩니다.

MATCH 및 TO_TEXT가 모든 ES|QL 표현식에 대한 풀텍스트 검색을 가능하게 하는 방법

먼저 Elasticsearch 9.4에서 불가능했던 쿼리부터 시작하겠습니다. 이 쿼리는 EVAL 명령을 사용합니다.

FROM cooking_blog
| EVAL summary = TO_TEXT(CONCAT(title, description))
| WHERE MATCH(summary, "pancakes")
| KEEP title, author

이 예시에서 요약에는 매핑이나 분석기 구성이 없습니다. 또한 어떤 역 인덱스와도 연관이 없습니다. 이 쿼리의 수명 동안에만 존재하지만, 이제는 검색할 수 있습니다. 두 가지 추가 요소가 이를 가능하게 합니다.

첫째, MATCH는 이제 첫 번째 인수로 매핑된 필드뿐만 아니라 모든 표현식을 지원합니다. 여기에는 EVAL에서 생성된 열과 인라인에서 사용되는 함수 결과도 포함됩니다. 또한 원본 문서에서 직접 불러온 매핑되지 않은 필드도 포함됩니다. 뿐만 아니라, MATCH에서 일반적으로 허용되는 모든 데이터 타입이 이 새로운 사용 사례에서 지원됩니다.

두 번째 부분은 새로운 TO_TEXT 함수로, 텍스트 형식의 출력을 생성하는 최초의 ES|QL 변환 함수입니다. 지금까지 텍스트 열은 인덱스에 매핑된 필드에서만 나올 수 있었으며, ES|QL 표현식에서 생성된 모든 스트링은 텍스트가 아닌 키워드 값이었습니다. MATCH는 두 값을 다르게 처리하기 때문에 이 구분이 중요합니다. 텍스트 값은 분석되며, 키워드 값은 정확히 비교되는데, 이는 인덱싱된 키워드 필드에 대한 MATCH 쿼리가 검색어 쿼리로 다시 작성되는 방식을 반영합니다. TO_TEXT(x)는 ES|QL에 이 스트링을 풀텍스트로 처리하도록 지시하는 방법입니다.

이 기능은 Elasticsearch 9.5의 기술 미리보기 버전으로 제공되므로 몇 가지 제한 사항이 있습니다.

  • 현재는 필터링만 지원합니다. 표현식에 대한 MATCH는 아직 관련성 점수에 반영되지 않으며, 점수에는 인덱싱된 필드에서의 매치만 영향을 미칩니다.

  • 표현식을 매칭할 때 퍼지와 같은 쿼리 옵션은 아직 지원되지 않습니다.

  • 런타임 텍스트는 표준 분석기를 사용하여 분석됩니다. 아직 구성할 수 없습니다.

이러한 한계를 해결하기 위한 작업이 진행 중입니다.

ES|QL에서 LIKE 또는 RLIKE 대신 풀텍스트 검색을 사용하는 이유

ES|QL에는 이미 인덱스 없이 스트링을 검색하는 두 가지 방법이 있습니다. LIKE(와일드카드 패턴)와 RLIKE(정규 표현식)입니다. 두 함수 모두 모든 스트링 표현식에서 작동하므로 MATCH 함수가 추가로 제공하는 기능에 당연히 궁금증을 갖게 됩니다. 정답은 분석입니다. 분석은 어간 추출이나 동의어 검색과 같은 기법을 사용하는 고급 검색 방식입니다. 또한 불용어 처리 기능도 사용합니다.

LIKE는 스트링을 구성하는 단어에 대한 이해 없이 단순히 부분 스트링을 매칭하는 연산입니다. 예를 들어, 여우(fox)에 관한 로그 메시지를 찾고 있다고 가정합시다.

FROM app_logs
| WHERE message LIKE "*fox*"

대문자 표기로 인해 "Fox spotted near the henhouse"는 놓치게 되고, "Outfoxed by the competition"은 여우와 관련이 없습니다. 대문자 입력에 거짓 음성이 발생하고, 다른 단어들 안에 숨겨진 부분 스트링에 거짓 양성이 나타나 실패합니다.

정규 표현식은 대소문자 문제를 해결할 수 있으나, 단어 경계 문제는 빠르게 복잡해집니다. 예를 들면 다음과 같습니다.

FROM app_logs
| WHERE message RLIKE "(.* )?[Ff][Oo][Xx]([ ,.:;].*)?"

하지만 이조차 아직 완벽하지 않습니다. 문장 끝에 있는 fox 다음에 ! 또는 ?가 뒤따르는 경우 놓치게 되고 탭, 따옴표 또는 괄호에 대해서는 언급이 없습니다. 수정할 때마다 패턴이 길어지고, 다음에 쿼리를 읽는 사람은 실제로 무슨 작업을 하는지 역으로 파악해야 합니다.

MATCH 문을 사용하면 쿼리와 값 모두를 분석기를 통해 처리하여 텍스트를 소문자로 토큰화한 다음 각 검색어를 서로 비교하기 때문에 문제가 해결됩니다.

FROM app_logs
| WHERE MATCH(TO_TEXT(message), "fox")

이 쿼리는 "The quick brown fox"와 "FOX spotted near the henhouse" 같은 값과 일치하지만, "Outfoxed by the competition"이나 "FOXTROT protocol enabled"와는 단어 주변의 구두점과 상관없이 일치하지 않습니다. 물론 이 모든 것은 MATCH(TO_TEXT(message), "brown fox")와 같은 다중 검색어 쿼리에서도 예상대로 작동합니다.

36개의 전용 언어 분석기를 활용할 수 있도록 하는 작업이 진행 중이며, 이 분석기들은 이전에 인덱싱되거나 매핑된 적이 없는 데이터에 대한 자연어 지원을 제공합니다.

인덱싱되지 않고 매핑되지 않은 데이터에 대한 풀텍스트 검색 사용 사례

위의 예시들은 매핑된 필드에서 계산된 값을 검색한 것입니다. 표현식에 ES|QL MATCH를 사용하는 더욱 흥미로운 사용 사례는 이전에는 전혀 검색할 수 없었던 데이터와 관련된 경우입니다. 몇 가지 예를 살펴보겠습니다.

ES|QL에서 매핑을 추가하지 않고 매핑되지 않은 필드를 검색하는 방법

때로는 상세한 스택 추적이나 원시 요청 페이로드와 같은 필드를 매핑에서 의도적으로 제외하는 경우가 있습니다. 디버그 블롭을 생략할 수도 있습니다. 이러한 필드 중 하나를 인덱싱하면 모든 문서에 디스크 및 힙 공간이 소모되므로 분기별로 한 번 정도만 조회할 필드에 대해서는 그럴 만한 가치가 없습니다.

매핑되지 않은 필드는 쿼리에서 전혀 보이지 않았기 때문에 이 결정은 항상 최종적이었습니다. Elasticsearch 9.5에서는 SET unmapped_fields="load"를 사용하여 ES|QL이 원본 문서에서 매핑되지 않은 필드를 키워드로 직접 불러오도록 할 수 있습니다. 그런 다음 TO_TEXT로 감싸면 이제 풀텍스트 검색을 실행할 수 있습니다.

SET unmapped_fields="load";
FROM app_logs
| WHERE MATCH(TO_TEXT(stack_trace), "java.lang.NullPointerException")
| KEEP @timestamp, service.name, message

이 예제에서는 stack_trace가 매핑되지 않았습니다. 모든 값은 원본 문서에서 가져와 실시간으로 분석됩니다. 각 행별로 짝이 맞춰져 있습니다. 실제 작업이며, 역 인덱스 조회만큼 빠르지는 않을 것입니다. 하지만 이제 인덱싱하지 않은 필드는 더 이상 검색 불가능하지 않습니다. 일상적인 경우에는 매핑 규모를 작게 유지하면서도, 중요한 시점에는 분기별로 필요한 질문에 답할 수 있습니다.

재인덱싱 없이 키워드 필드에서 풀텍스트 검색 가능

키워드 필드는 많은 기능을 수행할 수 있습니다. 정확한 매칭, 빠른 집계 및 정렬 기능을 제공하므로 많은 필드가 이 방식으로 매핑됩니다. 하지만 매핑은 데이터가 도착할 때 결정되기 때문에, 원래 의도했던 것과 다르게 데이터를 처리하고 싶은 상황이 발생하기 쉽습니다. 대시보드가 product_name을 기준으로 집계하므로 이를 키워드로 매핑되었는데, 1년 치 제품 데이터를 받은 후 누군가가 product_name 값 내에서 검색할 수 있기를 원할 수도 있습니다.

기존 해결책은 매핑을 텍스트로 변경하거나(또는 다중 필드 추가) 모든 것을 다시 인덱싱하는 것입니다. 이는 시간과 비용이 많이 소요될 뿐만 아니라, 많은 경우 사용자가 번거롭게 이 과정을 거치고 싶어 하지 않습니다. 새로운 해결책은 하나의 함수 호출입니다.

FROM products
| WHERE MATCH(TO_TEXT(product_name), "wireless noise cancelling headphones")
| KEEP product_name, brand, price

TO_TEXT는 키워드 값을 즉석에서 텍스트로 변환합니다. 따라서 MATCH는 키워드 값을 정확히 비교하지 않고 분석합니다. 이렇게 하면 매핑을 만들거나 소스 문서를 다시 인덱싱하지 않고도 키워드 필드를 쿼리할 수 있습니다. 검색이 일상적인 쿼리가 된다면 장기적으로는 필드를 텍스트로 인덱싱하는 것이 여전히 올바른 방법이지만, TO_TEXT를 사용하면 추가 작업 없이 지금 바로 답을 얻을 수 있습니다.

서로 다른 매핑을 가진 인덱스 전반에서 동일한 필드 검색

ES|QL은 여러 인덱스에 걸쳐 사용될 수 있으며, 동일한 필드가 모든 인덱스에서 항상 동일한 형태를 가질 필요는 없습니다. 같은 필드가 서로 다른 인덱스에서 서로 다른 타입을 가질 때, ES|QL은 이를 유니언 타입으로 처리하며, 변환 함수가 충돌을 해결합니다. 올해 인덱싱 템플릿에서는 메시지 필드의 타입이 텍스트이지만 작년에는 키워드였던 경우를 예로 들어 보겠습니다.

FROM logs-2025, logs-2026
| EVAL msg = TO_TEXT(message)
| WHERE MATCH(msg, "connection reset")

쿼리 시점에 텍스트 인덱스든 키워드 인덱스든 모든 값이 분석됩니다. 이전 인덱스의 키워드 값은 다른 모든 것과 마찬가지로 토큰화되고 소문자로 변환되어 있어 “connection reset”은 어떤 인덱스에 있든 “Connection RESET by peer”를 찾습니다.

또 다른 흥미로운 경우는 필드가 한 인덱스에만 매핑되어 있지만 다른 인덱스에도 존재하는(또는 다른 인덱스에는 매핑되지 않은) 경우입니다.

SET unmapped_fields="load";
FROM logs-2025, logs-2026
| WHERE MATCH(TO_TEXT(error_details), "timeout")

여기서 주목할 만한 미묘한 차이가 있습니다. error_details가 logs-2026에 매핑되어 있고 logs-2025에는 매핑되지 않았다면, Elasticsearch는 이 쿼리를 Lucene으로 푸시할 수 없습니다. 필드가 매핑되지 않은 인덱스에서는 오류나 경고 없이 일치하는 항목이 없다고 표시되기 때문입니다. 대신, 플래너는 해당 필드가 매핑되지 않았을 가능성이 있음을 감지하고 행의 출처와 관계없이 전체 MATCH 행을 하나씩 평가합니다. 어떤 인덱스에 필드가 매핑되었는지 알 필요가 없습니다. 쿼리가 그 질문에 대한 답을 제공합니다.

ES|QL이 쿼리 시점에 역 인덱스 없이 텍스트를 분석하는 방법

ES|QL은 표현식에 대해 MATCH를 계획할 때 쿼리 스트링을 먼저 검색어 집합으로 한 번 분석합니다. 각 행이 어떻게 평가되는지는 표현식의 유형에 따라 다릅니다.

표현식 유형

처리

매칭 동작

텍스트(TO_TEXT 사용)

분석기가 값을 소문자 검색어로 토큰화합니다.

토큰 간 비교. 토큰 중 하나라도 쿼리 검색어와 일치하면 해당 행이 일치하는 것으로 간주합니다(OR 의미론).

키워드, ip, 날짜, 숫자

분석 없이, 쿼리 상수를 네이티브 타입으로 한 번 변환합니다.

행별로 정확하게 비교합니다.

두 경로 모두 Lucene을 완전히 우회하고 값을 행 단위로 평가합니다. 비텍스트 경로는 해당 필드 타입에 대해 Lucene으로 매치 쿼리가 전달될 때 수행하는 작업을 정확히 반영하므로, 쿼리가 인덱스에 도달하든 상관없이 의미가 일관됩니다.

역 인덱스 조회는 인제스트 시점에 작업을 수행하며, 쿼리 시점에는 일치하지 않는 문서에 접근하지 않습니다. 런타임 MATCH는 쿼리 시점에 도달하는 모든 행에 대해 해당 분석을 수행합니다. 전자는 이미 작업이 이루어졌기 때문에 빠르며, 후자는 데이터가 전혀 인덱싱될 필요가 없어서 유연합니다.

ES|QL 풀텍스트 검색의 다음 단계

이 게시물의 모든 내용은 ES|QL에서의 검색이 미리 인덱싱한 항목뿐만 아니라 모든 항목에서 작동하도록 하려는 대규모 노력의 첫 번째 단계를 나타냅니다. 앞서 언급된 제한 사항들은 현재 적극적으로 개선 작업이 진행 중이며, 향후 로드맵은 더욱 발전할 것입니다.

  • 점수. 런타임 매치는 _score에 기여하므로, 데이터가 인덱싱되지 않았더라도 관련성에 따라 정렬할 수 있습니다.

  • MATCH_PHRASE 표현식 사용. 이미 Elastic Cloud Serverless에서 이용할 수 있으며, Elastic Stack 9.6에 도입됩니다.

  • 구성 가능한 분석기. 표현식에 대한 MATCH 및 MATCH_PHRASE 사용 시 분석기 지원을 통해 쿼리 시점의 언어 분석기, 어간 추출, 동의어 기능을 가능하게 합니다.

  • 매치 옵션. 런타임 매치를 위한 퍼지와 연산자 옵션이 있습니다.

  • 벡터 검색. 행별 임베딩을 생성하고 런타임 dense_vector 표현식에 대해 k-최근접 이웃(kNN)을 실행하여 인덱싱되지 않은 데이터에도 의미 검색 기능을 제공합니다.

지금 표현식에 대한 ES|QL 풀텍스트 검색을 사용해 보세요

지금 바로 런타임 검색을 사용해 보세요. 현재 새로운 ES|QL 기능이 먼저 도입되는 Elastic Cloud Serverless에서 이용 가능하며, Elasticsearch 9.5에서 기술 미리보기로 출시됩니다. 검색 함수 참조부터 시작하고, 현재 한계에 대한 ES|QL 제한 사항 페이지를 확인하시기 바랍니다. 이번 버전은 기술 미리보기 버전이므로 여러분의 피드백을 기다리고 있습니다. 이전에 인덱싱되지 않은 내용을 검색하여 예상치 못한 결과가 나타난다면(긍정적이든 부정적이든) 언제든지 알려주시기 바랍니다.

관련 콘텐츠

1분 만에 프롬프트로 대시보드 완성, 비용은 5분의 1: Kibana의 AI 대시보드와 맞춤형 Vega-Lite 차트

Marta Bondyra

LINQ to Elasticsearch ES|QL: C# 작성, Elasticsearch 쿼리

Florian Bernd

스위스식 해시 테이블을 사용한 더 빠른 ES|QL 통계 처리

Chris Hegarty

Google MCP Toolbox for Databases, 새롭게 추가된 Elasticsearch 지원

Enrico Zimuel

ES|QL 9.2: 스마트한 LOOKUP JOIN 및 시계열 지원

Tyler Perkins