이 게시물은 3부에서 설명된 제어 플레인 아키텍처를 Elasticsearch 퍼콜레이터를 사용해 구현하는 방법을 다루는 기술 심화 분석 글입니다. 운영 환경에서 결정적이고 거버넌스가 적용된 정책 엔진을 구현하기 위해 사용된 패턴들을 설명합니다.
아키텍처에서 구현까지
3부에서는 제어 플레인 아키텍처에 대해 설명했습니다. 여기에는 조회 기본 메커니즘으로서의 역방향 매칭, 매칭과 동작을 분리하는 정책 문서, 그리고 여러 정책을 하나의 실행 계획으로 조합하는 연쇄적 변환이 포함됩니다. 이 게시물에서는 정책 조회를 가능하게 하는 Elasticsearch 기능인 퍼콜레이터 쿼리를 실제 구현 관점에서 자세히 다룹니다.
퍼콜레이터는 제어 플레인에 필요한 방식 그대로 검색 방향을 반대로 뒤집기 때문에, 거버넌스 용도에 매우 잘 어울립니다. 이 게시물에서는 먼저 퍼콜레이터가 무엇을 하는지, 왜 중요한지를 명확히 설명한 뒤, 인덱스 설계, 정책 저장, 쿼리 시점 평가, 여러 정책의 조합까지 구현 과정을 단계별로 살펴봅니다.
일반 검색이 작동하는 방식
전자 상거래 시스템에서는 title, category, price와 같은 필드를 포함하는 수십만 또는 수백만 개의 제품 문서가 존재할 수 있습니다. 사용자가 일치하는 문서를 검색할 때는, Elasticsearch가 사용자의 검색 문자열을 이러한 제품 문서에 저장된 하나 이상의 필드와 비교하도록 요청하는 것입니다. Elasticsearch의 기본 분석기인 표준 분석기는 텍스트를 소문자로 변환하고 토큰으로 분리합니다. “oranges” 검색이 “Oranges”와 일치하는 것은 소문자 변환 처리 때문입니다. 어간 추출 기능이 포함된 언어 인식 분석기를 사용하면 “orange”와도 일치하게 되는데, 이는 두 형태가 동일한 어간으로 축약되기 때문입니다. 예를 들어, 다음 매치 쿼리는 “title” 필드에 “orange” 또는 “oranges”가 포함된 문서를 반환합니다.
따라서 위의 쿼리에 대해 Elasticsearch는 title 필드가 "oranges"와 일치하는 제품 문서들을 반환합니다. 여기에는 "Orange Fruit Spread", "Orange Juice", "Juicy oranges", "Orange Marmalade" 등의 결과가 포함될 수 있습니다. 기억해야 할 핵심 사항은 Elasticsearch가 일반적으로 검색 문자열을 문서와 비교하고, 그 검색 문자열과 일치하는 문서를 반환하는 데 사용된다는 점입니다.

거버넌스 문제: 제품 검색 전에 관련 정책 찾기
1~3부에서 설명했듯이, 거버넌스 기반 검색 시스템은 사용자의 검색 문자열을 제품 카탈로그에 직접 전달하지 않습니다. 먼저 해당 검색 문자열에 적용되는 정책이 있는지 확인합니다.
한 판매자가 사용자가 정확히 "oranges"를 검색할 때, 검색 결과를 오렌지 카테고리로만 제한하고 오렌지 주스, 오렌지 마멀레이드, 오렌지 탄산음료는 제외하기로 결정했습니다. 이 비즈니스 결정은 정책 형태로 저장됩니다. 사용자가 "oranges"를 입력하면, 제어 플레인은 해당 정책을 찾아 그 지침을 읽고, 그에 맞게 상품 카탈로그에 대한 검색을 수정해야 합니다. 이를 수행하려면, 제어 플레인은 어떤 저장된 정책들이 해당 검색 문자열과 관련 있는지 판별해야 합니다.
기업 환경에는 이러한 정책이 수백 개에서 수천 개에 이를 수 있습니다. 이를 if/else 논리로 하나씩 확인하는 것은 2부에서 설명한 애플리케이션 계층 안티패턴입니다. 필요한 것은 모든 정책을 인덱스에 저장하고 주어진 검색 문자열과 일치하는 정책을 즉시 찾을 수 있는 방법입니다. 바로 이 지점에서 퍼콜레이터가 사용됩니다.
방향 뒤집기: 퍼콜레이터
이전에 언급했듯이, 일반적인 검색에서 Elasticsearch는 검색 문자열을 문서들과 비교하고, 해당 검색 문자열을 포함하는 문서들을 반환하는 데 주로 사용됩니다.
퍼콜레이터는 이 과정을 반대로 뒤집습니다. 퍼콜레이터에서는 각 문서가 쿼리 패턴을 저장하는 인덱스를 만들고, 이후 들어온 검색 문자열을 이 저장된 쿼리들과 비교하여 어떤 저장된 쿼리 패턴이 트리거되었는지를 판별합니다.

거버넌스 측면에서 "저장된 쿼리 패턴"은 곧 정책을 의미합니다. 각 정책에는 자신이 어떤 종류의 검색 문자열과 일치해야 하는지를 설명하는 패턴이 포함되어 있습니다. 예를 들어, 검색 문자열이 "oranges"와 정확히 일치하는지, 혹은 검색 문자열에 “olive oil”이 포함되어 있는지를 판별하는 식입니다. 들어오는 문자열은 사용자의 검색 텍스트이며, 이는 쿼리 시점에 전달되어 저장된 모든 정책 패턴과 대조되어야 합니다. 이에 대해서는 관련 PRISM 영상의 4분 9초 부분에서 다루고 있습니다.
단계별 설명: "oranges" 검색으로 관련 정책을 찾는 방식
정책
한 판매자는 사용자가 다른 단어 없이 정확히 "oranges"만 검색했을 때 일치하도록 정책을 작성했습니다. 퍼콜레이터에서 일치가 발생하면, 문서의 나머지 내용에는 제어 플레인이 제품 쿼리를 구성할 때 사용할 규칙들이 포함됩니다. 이 예시에서는 그 규칙 중 하나로 검색 결과를 과일 카테고리로 제한(필터링)하는 설정이 있습니다.
percolator 필드에는 이 정책이 언제 실행되어야 하는지를 정의하는 패턴이 포함되어 있습니다. 이 경우에는 "START oranges END" 문구와 일치합니다. rule_type, rule_args 필드는 정책이 실행되었을 때 어떤 동작을 수행해야 하는지를 정의합니다. START, END 토큰은 경계 표시자이며, 이에 대해서는 곧 설명하겠습니다.
정책이 어떻게 작성되는지는 관련 PRISM 동영상의 2:52에 나오는 PRISM Studio UI에서 확인할 수 있습니다.
사용자 검색
한 쇼핑객이 검색창에 "oranges"라고 입력합니다.
제어 플레인이 일치하는 정책을 확인
제품 카탈로그를 검색하기 전에 제어 플레인은 사용자의 검색 문자열을 가로채 경계 표시로 감싼 뒤 퍼콜레이터로 전송합니다.
"START oranges END" 문자열은 저장된 모든 정책 패턴과 대조됩니다. 내부적으로는 Elasticsearch가 저장된 정책 패턴들을 이 문자열에 대해 실행한 뒤, 일치하는 항목들을 반환합니다. 이것이 바로 퍼콜레이터입니다. 사용자의 검색 문자열은 저장된 모든 정책 패턴과 비교되었고, 일치하는 패턴이 반환되었습니다. if/else 체인은 없습니다. 순차적 평가도 없습니다. 인덱스가 매칭 처리를 담당합니다.
제어 플레인이 정책 적용
제어 플레인은 일치하는 정책들의 동작 규칙을 읽어들입니다. 위의 정책은 제어 플레인이 결과를 과일 카테고리로 제한하도록 지시합니다. 이에 따라 제어 플레인은 제품 카탈로그를 대상으로 다음과 같은 최종 Elasticsearch 쿼리를 구성합니다.
사용자가 "oranges"를 검색했습니다. 제품 카탈로그는 과일 카테고리로 제한된 "oranges"에 대한 쿼리를 수신합니다. 이 제한 조건 때문에 오렌지 주스, 오렌지 마멀레이드, 오렌지 탄산음료는 제외됩니다.
"orange marmalade"가 오렌지 정책을 트리거하지 않는 이유
다른 사용자가 “orange marmalade”를 검색한다고 가정해 보겠습니다. 제어 플레인이 해당 문자열을 감싼 뒤 퍼콜레이션을 수행합니다. "START orange marmalade END". 오렌지 정책의 패턴은 match_phrase: "START oranges END"입니다. 오렌지 정책이 일치하지 않아 해당 정책은 적용되지 않으며, 결과도 과일 카테고리로 제한되지 않습니다.
이것이 START 및 END 경계 표시자의 목적입니다. 이 경계 표시자가 없다면, "oranges"라는 단어에 일치하도록 만든 정책이 "orange marmalade" 같은 쿼리에서도 의도치 않게 실행될 수 있습니다. 사용자의 검색 문자열을 START 와 END 로 감싸고 정책 패턴에 해당 표시자를 포함함으로써 "oranges"가 다른 단어 없이 완전한 검색 문자열일 때만 정책이 실행되도록 보장할 수 있습니다. 이는 쇼핑객과 판매자 양측의 의도 모두에 부합합니다.
두 번째 정책: 어간 추출 필드에서의 "olive oil"
모든 정책에 정확한 문자열 일치가 반드시 필요한 것은 아닙니다. “olive oil” 정책은 어간 추출 처리된 필드에서 일치 여부를 판단하므로, 사소한 단어 형태 변화와 관계없이 실행됩니다.
이 정책의 패턴은 query 대신 query.stemmed에 대해 매칭됩니다. 사용자의 검색 문자열이 들어오면, 이는 query 필드(정확한 원문 텍스트)와 query.stemmed 필드(어간 추출 분석기로 분석하여 단어를 어간으로 축약해, "olives"와 "olive", "oils"와 "oil"이 각각 같은 어간으로 처리됨) 둘 다에 저장됩니다. 정책의 패턴은 어간이 추출된 문자열과 비교하여 검사되므로, 단어 형태의 사소한 변형과 관계없이 실행됩니다.
START 및 END 경계 표시자는 어간 처리된 필드에서도 동일하게 작동하며, 이를 통해 "olive oil"이 더 긴 문장의 일부로 포함된 경우가 아니라 전체 검색 문자열 자체일 때만 이 정책이 실행되도록 보장합니다.
이 게시물의 나머지 부분에서는 이를 실제 운영 환경에서 사용할 수 있도록 만드는 구현 세부 사항들을 다룹니다. 여기에는 두 가지 매칭 방식을 모두 지원하는 인덱스 매핑, 하이라이트를 이용해 구문 제거 및 소비된 구문 추적을 수행하는 방법, 그리고 서로 충돌하는 여러 정책을 하나의 실행 계획으로 조합하는 방식이 포함됩니다.
정책 인덱스 매핑
정책 인덱스에는 저장된 쿼리 패턴을 담기 위한 퍼콜레이터 필드와, 퍼콜레이터가 매칭 대상으로 사용할 들어오는 검색 문자열의 구조를 반영하는 텍스트 필드가 필요합니다. 아래 매핑은 설명을 단순화하기 위해 간략화한 버전입니다. 실제 운영 환경의 배포 구성은 훨씬 더 복잡하며, 경계 표시 처리, 가변 패턴 매칭(예: “under $4”에 통화 값이 포함되어 있음을 인식하는 기능), 그리고 기타 다양한 분석 작업을 처리하기 위해 커스텀 분석기를 사용합니다.
이 인덱스의 이름이 policies인 이유는 각 문서가 2부에 정의된 대로 완전한 거버넌스 정책 하나를 나타내기 때문입니다. 여기에는 매칭 기준, 작업, 우선순위, 메타데이터가 포함됩니다. rule_type 및 rule_args 필드에는 정책의 작업 구성 요소가 포함되어 있으며, 여기에는 제어 플레인이 제품 카탈로그에 대해 실행할 쿼리를 구성할 때 사용할 지침이 담겨 있습니다.
query 필드는 퍼콜레이터가 매칭 대상으로 사용하는 문자열입니다. 이 필드에는 정확한 원문 버전과 어간 처리된 버전, 총 두 가지 형태가 있습니다. 사용자의 검색 문자열이 들어오면, 해당 문자열은 임시 인메모리 인덱스의 이 필드에 저장됩니다. query에 대해 매칭하는 정책은 정확한 원문 문자열을 기준으로 검사하며, query.stemmed에 대해 매칭하는 정책은 어간 처리된 버전을 기준으로 검사합니다.
하이라이트, 필터링, 정렬을 통한 퍼콜레이션
위의 간단한 예시는 최소한의 퍼콜레이터 요청만 보여주었습니다. 실제 환경에서는 제어 플레인이 하이라이트 기능을 추가하고, 비활성화된 정책을 필터링하며, 우선순위 기준으로 정렬을 수행합니다.
하이라이트 설정은 "query"를 필드 키로 사용하고 matched_fields 안에는 "query.stemmed"가 포함됩니다. 이 기능은 Elasticsearch의 통합 하이라이터에 대해, 부모 query 필드에 대한 하이라이트를 반환하되 어떤 토큰을 하이라이트할지 결정할 때는 query.stemmed 서브필드의 매칭 결과도 함께 고려하도록 지시하는 것입니다. 이 덕분에 어간 처리된 필드에서 매칭된 정책이라도 원본 텍스트에서 정확한 하이라이트 범위를 생성할 수 있으며, 이는 제어 플레인이 구문 제거 및 이미 적용된 구문 추적을 수행하는 데 필요합니다.
enabled: true 필터는 비활성화된 정책이 제외되도록 보장합니다. 우선순위에 적용된 sort는 우선순위가 높은 정책이 먼저 반환되도록 하며, 이를 통해 제어 플레인이 연쇄 변환을 올바른 순서로 처리할 수 있게 합니다. 가장 중요한 추가 요소는 highlight 필드로, 이는 사용자의 검색 문자열에서 어떤 단어들이 각 매칭을 유발했는지를 정확히 알려줍니다.
"olive oil" 검색에 대한 응답은 다음과 같은 형태일 수 있습니다.
하이라이트가 중요한 이유
응답의 하이라이트는 다음과 같습니다. "<em>START olive oil END</em>". Elasticsearch는 사용자의 검색 문자열에서 어떤 단어들이 정책 매칭을 유발했는지를 정확히 알려줍니다. 이는 단순한 시각적 표시용 기능이 아닙니다. 이 하이라이트 메타데이터는 이후 단계의 다음과 같은 두 가지 핵심 동작을 구동합니다.
구문 제거. 일부 정책은 제품 카탈로그 쿼리를 구성하기 전에 검색 문자열에서 매칭된 텍스트를 제거해야 합니다. 예를 들어 "cheap"에 매칭되는 정책은 해당 단어를 제거한 뒤, 대신 이를 가격 필터로 변환합니다. 하이라이트는 정책이 검색 문자열의 어느 부분과 정확히 매칭되었는지를 식별하므로, 시스템은 어떤 부분을 제거해야 하는지 알 수 있습니다.
처리된 구문 추적. 3부에서 설명했듯이, 여러 정책이 동일한 검색 문자열에 매칭될 경우 우선순위가 더 높은 정책이, 더 낮은 우선순위 정책이 매칭한 단어를 제거할 수 있습니다. 각 정책의 하이라이트를 현재의(계속 변화하는) 검색 문자열과 비교함으로써, 시스템은 특정 구문이 이미 처리되었음을 감지하고 우선순위가 더 낮은 정책을 건너뛸 수 있습니다. 이를 통해 중복 처리를 방지하고 결정론적인 동작을 보장할 수 있습니다.
하이라이트 기능의 동작 방식에 대해서는 이 문서에서 더 자세히 알아볼 수 있습니다.
퍼콜레이션에서 실행 계획까지
퍼콜레이터는 매칭된 정책들의 집합을 반환합니다. 그러나 3부에서 설명했듯이, 조회는 전체 과정의 절반에 불과합니다. 나머지 절반은 이러한 매칭 결과들을 일관된 실행 계획으로 조합하는 과정입니다. 실제 쿼리를 예로 들면 다음과 같습니다.
실제 사례: 크리스마스 캠페인 기간 중 "Cheap chocolate" 검색
시스템에 두 가지 활성 정책이 있다고 가정해 봅시다. 하나는 "Cheap chocolate" 정책(우선순위 210)이고, 다른 하나는 "Christmas chocolates" 정책(우선순위 300)이며, 둘 다 3부에 자세히 설명되어 있습니다.
1단계: 퍼콜레이션. 사용자가 "cheap chocolate"을 검색합니다. 제어 플레인은 검색 문자열을 "START cheap chocolate END" 형태로 감싼 뒤 퍼콜레이터로 전달합니다. 두 가지 정책이 매칭됩니다. "Cheap chocolate" 정책의 패턴은 "cheap chocolate" 구문에 매칭되고, "Christmas chocolates" 정책의 패턴은 어간 처리된 필드를 통해 "chocolate"에 매칭됩니다.
2단계: 우선순위 기준 정렬. 퍼콜레이터는 두 정책을 모두 반환하며, 우선순위가 높은 순서대로 정렬합니다. “Christmas chocolates” 정책(300)이 먼저 처리되고, 그다음 “Cheap chocolate” 정책(210)이 처리됩니다.
3단계: 연쇄 변환 적용. 이는 3부의 initial state → [Policy A] → state' → [Policy B] → state'' → execution plan 모델입니다.
"Christmas chocolates" 정책(우선순위 300)이 먼저 적용됩니다.
- "Christmas foods and drinks", "Christmas sweets" 카테고리에 대한 하드 필터를 추가합니다.
- 7달러 미만 가격 필터를 추가합니다.
- "Advent calendars" 카테고리에 3배 소프트 부스트를 적용합니다.
“Cheap chocolate” 정책(우선순위 210)이 수정된 상태를 기준으로 다음으로 적용됩니다.
- "Chocolates", "Milk chocolates"라는 카테고리 하드 필터를 추가하려고 시도하지만, 크리스마스 정책이 이미 이 필드를
on_conflict: override로 설정했기 때문에 Cheap chocolate 카테고리는 제외됩니다. - 가격 필터 $2를 추가하려고 시도합니다. 크리스마스 초콜릿 정책이 이미 가격에 대해
on_conflict: restrict를 설정했지만, $2가 $7보다 더 제한적이므로 $2가 적용됩니다. - 검색 문자열에서 "cheap"을 제거합니다.
4단계: Elasticsearch 쿼리 구성. 제어 플레인은 실행 계획을 조합하여 제품 카탈로그에 대해 실행할 단일 Elasticsearch 쿼리를 구성합니다.
원래 검색 문자열은 “cheap chocolate”이었습니다. 제품 카탈로그에 도달하는 쿼리는 거버넌스가 적용된, 의도 인식 기반의 검색 실행 계획입니다. "cheap"이라는 단어는 이미 처리되어 가격 제약 조건으로 변환되었으며, 결과는 크리스마스 시즌 카테고리로 제한되고, 어드벤트 캘린더 제품은 순위 부스트가 적용됩니다. 또한 가격 상한은 더 낮은 우선순위 정책에서 정의된, 더 엄격한 값을 따르게 됩니다. 모든 변환 과정은 결정적이며, 추적 가능하고, 설명 가능합니다.
이러한 배수값들이 기본 BM25 점수와 어떻게 상호작용하는지에 대한 간단한 개요는 관련 PRISM 동영상의 8:45에서 확인할 수 있으며, 여기서 곱셈 방식의 부스트에 대해 간단히 설명합니다.
이것이 확장 가능한 이유
퍼콜레이터는 비대칭성 덕분에 이러한 사용 사례에서 효율적으로 동작합니다. 기업용 전자 상거래 시스템에는 수백만 개의 제품이 있을 수 있지만, 거버넌스 정책은 보통 수백 개에서 수천 개 수준에 불과하기 때문입니다. 퍼콜레이터는 전체 제품 카탈로그를 스캔하는 것이 아니라, 들어온 하나의 검색 문자열을 저장된 정책 패턴 집합과 비교하는 방식으로 동작합니다. 비용은 정책 수에 비례하며, Elasticsearch는 내부 최적화(저장된 쿼리 패턴의 용어 인덱싱, 불 논리 단락 평가 등)를 적용하여 빠른 매칭 속도를 유지합니다.
새 정책을 추가하는 것은 단순히 새 문서를 인덱싱하는 작업에 불과합니다. 정책을 비활성화하는 것도 필드 하나를 업데이트하면 됩니다. 코드 변경도, 배포도, 재시작도 필요 없습니다.
조회에서 거버넌스 기반의 검색으로
퍼콜레이터는 3부에서 설명한 제어 플레인 아키텍처를 대규모 환경에서도 실용적으로 만들어 주는 고속 역매칭 기능을 제공합니다. 정책은 저장되고 인덱싱되는 데이터이며, 들어오는 검색 문자열에 대해 효율적으로 매칭됩니다. 제어 플레인은 3부에서 설명한 연쇄 변환과 필드별 충돌 해결 과정을 통해, 매칭된 정책들을 거버넌스된 실행 계획으로 조합합니다. 그리고 검색 엔진은 제품 카탈로그에 대해 이 거버넌스된 실행 계획을 수행합니다.
그 결과, 판매자가 애플리케이션 코드를 건드리지 않고도 새 정책을 작성하고, 대표 검색 쿼리를 대상으로 테스트한 뒤, 이를 운영 환경에 반영하고, 그 효과를 즉시 확인할 수 있는 시스템이 만들어집니다. 퍼콜레이터는 정책 조회를 빠르게 만들고, 제어 플레인은 정책 조합을 결정적으로 수행하며, 거버넌스 기반 워크플로는 전체 과정을 안전하게 만듭니다.
이 시리즈의 다음 내용
이 시리즈의 다음 게시물에서는 거버넌스 기반의 제어 플레인을 새로운 영역으로 확장합니다. 여기서는 다중 계층 검색 아키텍처를 소개하며, 안정적인 페이지 지정과 패싯을 유지하면서 엄격 검색, 완화 검색, 시맨틱 검색을 어떻게 오케스트레이션할 수 있는지를 설명합니다.
거버넌스 기반 전자 상거래 검색 실제로 적용해 보기
이 게시물에 설명된 퍼콜레이터 기반 제어 플레인은 인덱스 매핑과 경계 표시부터 하이라이트 기반 구문 추적 및 연쇄적 정책 조합에 이르기까지, Elastic Services Engineering이 반복 적용 가능한 전자 상거래 검색 가속기의 일부로 구축한 것입니다. 여기에 제시된 모든 쿼리 예시와 정책 구조는 엔터프라이즈 규모의 제품 카탈로그를 대상으로 검증된 실제 시스템에서 가져온 것입니다.
Elasticsearch 기반의 거버넌스가 적용된 정책 중심 제어 플레인을 구현하고자 한다면, Elastic Services를 통해 이를 더 빠르게 구축할 수 있습니다. Elastic 전문 서비스에 문의하세요.
논의에 참여하기
검색 거버넌스, 검색 전략 또는 전자 상거래 검색 아키텍처에 대해 궁금한 점이 있으신가요? Elastic 커뮤니티 대화에 참여하세요.




