본 시리즈의 1부에서 5부까지는 상품 카탈로그를 쿼리하기 전 단계에서 검색 의도를 분류하고 제약 조건을 적용하며, 정책 충돌을 해결하고 적절한 검색 전략으로 라우팅하는 거버넌스 제어 평면에 대해 다룹니다. 지금까지 설명한 모든 메커니즘은 모든 쇼핑객을 동일하게 취급합니다. "chocolate"을 검색하면 구매자가 비건이든, 자녀의 생일 선물을 사는 부모이든, 혹은 할랄을 준수하는 소비자이든 관계없이 동일하게 통제된 결과 집단을 생성합니다.
본 게시물에서는 기존 아키텍처를 변경하지 않고도 거버넌스 제어 평면을 확장할 수 있는 두 가지 개인화 메커니즘을 소개합니다. 두 메커니즘 모두 1부에서 5부까지의 거버넌스 계층과 곱연산 방식으로 중첩됩니다. 즉, 정책은 여전히 실행되고 제약 조건은 적용되며, 충돌은 해결되는 동시에 개인화 신호가 동일한 거버넌스 쿼리에 구성되어 Elasticsearch가 반환하는 결과가 이미 개인화되도록 보장합니다.
첫 번째 메커니즘은 개별 쇼핑객이 이전에 구매한 적이 있는 제품을 부스트하는 것입니다. 두 번째는 쇼핑객의 프로필을 기반으로 특정 코호트별 정책을 활성화하는 것입니다. 이 두 가지는 개인화가 검색 시스템 옆에 별도로 덧붙여지거나 검색 후처리로 적용되는 독립적인 시스템이 아니라 정책 기반 제어 평면의 자연스러운 확장임을 보여줍니다.
본 게시물에 사용된 개인화 기법의 수학적 원리를 자세히 살펴보려면 ML 후처리 없는 Elasticsearch 검색 개인화 및 Elasticsearch의 코호트 인식 랭킹을 참조하세요.
재방문 고객을 위해 구매 이력을 사용하여 검색 결과를 부스팅하는 방법에 대한 라이브 데모를 보려면 설명 가능한 개인화: 구매 이력을 통한 검색 부스팅 영상을 시청해 보세요.
개인 구매 이력 부스팅
개인화의 가장 단순한 형태는 가장 효과적인 방법 중 하나이기도 합니다. 즉, 쇼핑객이 이전에 제품을 구매한 적이 있다면 관련 내용을 검색할 때 해당 제품을 부스트하는 것입니다. 특정 브랜드의 초콜릿 칩 쿠키를 정기적으로 구매하는 쇼핑객이 "cookies"를 검색하면 해당 쿠키가 더 높은 순위에 노출되어야 합니다. 이는 모델이 선호도를 예측했기 때문이 아니라 직접적인 행동 증거가 존재하기 때문입니다.
참여 방법
세션이 활성화된 사용자의 경우처럼 검색 요청에 사용자 식별자가 포함되면 제어 평면은 스레드 풀을 사용하여 두 개의 Elasticsearch 쿼리를 병렬로 실행합니다.
- 정책 인덱스에 대한 퍼콜레이터 쿼리(3부와 4부에서 설명한 거버넌스 조회와 동일)
user_purchases인덱스에 대한 구매 이력 쿼리이며term(user_id)을 통해 특정 사용자로 필터링한 후 현재 검색 문자열을 해당 사용자의 제품 제목과 매칭합니다.
이 쿼리들은 동시에 실행되므로(어느 쪽도 상대방을 기다리지 않음) 개인화 조회 작업이 거버넌스 파이프라인에 유의미한 지연 시간을 추가하지 않습니다.
구매 이력 쿼리는 현재 검색 문자열을 저장된 제품 제목과 매칭할 때 Elasticsearch의 텍스트 분석(어간 추출, 토큰화)을 사용합니다. 이는 "cookies"를 검색했을 때 표준 텍스트 분석을 통해 과거에 구매한 "brownie cookies"와 매칭됨을 의미하며 정확한 문자열 일치를 요구하지 않습니다.
부스트 가중치 산출
모든 과거 구매 이력이 동일한 부스트 가중치를 가져야 하는 것은 아닙니다. 가중치는 두 가지 직관적인 요소, 즉 쇼핑객이 해당 제품을 얼마나 자주 구매했는지와 얼마나 최근에 구매했는지를 고려합니다. 지난주에 15번 구매한 제품은 6개월 전에 한 번 구매한 제품보다 훨씬 더 강력한 신호입니다. 가중치 산정 시 빈도에는 로그 스케일링을 적용하여 특정 품목의 대량 구매가 전체 결과를 압도하지 않도록 하며, 최신성에는 지수 감쇠를 적용하여 오래된 구매 기록이 시간이 지남에 따라 자연스럽게 희미해지도록 합니다.
부스트 공식에 대한 자세한 수학적 내용은 ML 후처리 없는 Elasticsearch 검색 개인화를 참조하세요.
쿼리 구성 방식
구매 이력 부스팅은 3부와 4부에서 다룬 거버넌스 정책 필터 및 부스트 그리고 마진이나 인기도(7부에서 살펴볼 예정) 같은 비즈니스 신호 부스트를 감싸는 최외곽 스코어링 계층으로 쿼리에 구성됩니다. 이는 거버넌스 정책에 의해 제외된 제품이 구매 이력 부스트로 인해 다시 나타나지 않음을 의미합니다. 거버넌스가 결과 세트를 제어한다면 개인화는 그 안에서 순위를 조정합니다. 구매 이력이 없는 제품에 불이익이 주어지지는 않습니다. 다른 모든 조건이 동일할 때 관련 구매 이력이 있는 제품이 더 높은 순위를 차지할 뿐이며 구매 이력이 없는 제품의 거버넌스 랭킹은 그대로 유지됩니다.

매 검색마다 Elasticsearch에 쿼리를 수행하는 이유는 무엇인가요?
구매 이력은 애플리케이션 계층에 캐싱되는 대신 매 검색 시 Elasticsearch에서 조회됩니다. 이는 의도적인 설계 결정입니다. 쿼리가 Elasticsearch의 텍스트 분석 파이프라인을 사용하여 현재 검색 문자열을 제품 제목과 매칭하기 때문에 시스템은 제품 검색 자체를 구동하는 것과 동일한 어간 추출, 토큰화 및 언어 처리 기능의 이점을 누릴 수 있습니다. 인메모리 캐시 조회를 사용한다면 이러한 분석 기능을 다시 구현하거나 정밀도가 떨어지는 매칭 방식을 수용해야만 합니다.
이러한 순서가 왜 중요한지 확인하기 위해 이전에 오렌지 주스를 구매했던 쇼핑객이 현재 "oranges"를 검색하는 상황을 가정해 보겠습니다. 구매 이력 쿼리는 텍스트 분석을 통해 검색어 "oranges"와 과거 구매 이력인 "orange juice"를 매칭하고 해당 제품에 대한 부스트 가중치를 계산합니다. 하지만 거버넌스 계층은 이미 "oranges" 검색을 농산물 카테고리로 제한하여 오렌지 주스를 결과에서 완전히 필터링한 상태입니다. 쿼리 내에 오렌지 주스에 대한 구매 이력 부스트가 존재하더라도 거버넌스 제어를 거친 결과 세트 내에 적용 대상이 되는 문서가 없기 때문에 아무런 영향을 주지 못합니다. 쇼핑객은 관련성과 개인화에 따라 정렬된 신선한 오렌지를 보게 되며 이로써 거버넌스 가드레일은 유지됩니다.
성능 비용은 최소화됩니다. 구매 이력 인덱스는 규모가 작고(한 사용자의 구매 이력은 보통 수백만 건이 아니라 수십에서 수백 건의 문서에 불과함) 쿼리가 퍼콜레이터 조회와 병렬로 실행되므로 임계 경로를 연장하지 않기 때문입니다.
사용자 이력이 없는 경우의 "spring water" 쿼리 예시
로그인하지 않은 사용자나 "spring water"를 구매한 적이 없는 사용자가 검색할 경우 다음과 유사한 결과를 보게 될 수 있습니다.

사용자 구매 이력 예시
반면 Carol이라는 사용자는 다음과 같은 제품들이 포함된 쇼핑 이력을 보유하고 있습니다.

상기 구매 이력이 있는 경우의 "spring water" 검색 예시
Carol이 "spring water"를 검색하면 과거에 구매했던 내역이 반영된 개인화된 결과를 보게 됩니다. 위의 구매 이력을 보면 Carol은 "Carbonated Spring Water"(초록색 병)를 약 40회 구매했으며 가장 최근 구매일은 이틀 전입니다. Carol이 "spring water"를 검색하면 그녀가 해당 제품을 선호한다는 점을 시스템이 알고 있으므로 그 제품을 부스트합니다. 개인화되지 않은 결과에서는 루비콘(Rubicon) 생수가 대신 첫 번째 검색 결과로 노출된 점에 주목해 주세요.

코호트 인지형 정책 활성화
개별 구매 이력은 행동 패턴이 확립된 재방문 고객에게 효과적입니다. 그러나 많은 쇼핑객은 신규 방문자이거나 익명 혹은 평소 패턴을 벗어나 검색하는 방문자입니다. 이러한 쇼핑객들에게 코호트 멤버십은 과거의 행동이 아닌 쇼핑객이 누구인가에 기반한 다른 차원의 개인화를 제공합니다.
비건 쇼핑객이 "chocolate"을 검색하면 비건 초콜릿이 더 높은 순위에 노출되어야 합니다. 할랄을 준수하는 쇼핑객이 "snacks"를 검색하면 할랄 인증 제품이 눈에 띄게 표시되어야 합니다. 건강에 관심이 많은 쇼핑객이 "yogurt"를 검색하면 프로바이오틱스 제품이 부스트되어야 합니다.
제품 태그가 아닌 정책으로서의 코호트
제품에는 이미 dietary_restrictions: ["vegan"] 또는 dietary_restrictions: ["halal"]과 같은 필드를 포함하여 일반적인 속성들이 부여되어 있습니다. 문제는 쇼핑객의 코호트와 이러한 제품 속성을 연결하는 로직이 어디에 위치하느냐 하는 것입니다.
단순한 접근 방식은 애플리케이션 계층이나 검색 템플릿에 해당 매핑을 하드코딩하는 것입니다. 예를 들어 사용자가 비건이라면 dietary_restrictions: "vegan"에 부스트를 추가하는 식입니다. 하지만 이는 1부에서 설명한 동일한 애플리케이션 계층 스파게티 코드이며 새로운 코호트를 추가하거나 코호트의 의미를 변경할 때마다 코드 수정이 필요하다는 동일한 운영상의 마찰을 야기합니다.
대신 거버넌스 제어 평면은 코호트 로직을 정책 엔진 내에 유지합니다. 코호트 정책은 쇼핑객의 코호트 멤버십(예: "vegan")과 제품 속성(예: dietary_restrictions: “vegan”)이라는 두 가지 요소를 연결하는 다리 역할을 합니다. 이 정책은 비건 코호트에 속한 쇼핑객이 검색할 때 dietary_restrictions 항목에 "vegan"이 포함된 제품을 부스트하도록 그 연결 고리를 정의합니다.

코호트 로직이 애플리케이션 코드가 아닌 정책 엔진 내에 존재한다는 것은 다음을 의미합니다.
- 새로운 정책을 만드는 것만으로도 신규 코호트를 추가할 수 있으며 제품을 다시 인덱싱할 필요가 없습니다.
- 코호트 정책은 규칙 엔진의 모든 기능을 활용합니다. 즉, 필터 추가, 소프트 부스트 적용, 유의어 확장, 검색 전략 변경 등 정책이 수행할 수 있는 모든 조치를 취할 수 있습니다.
- 코호트의 동작은 다른 모든 정책과 동일한 관리자 UI를 통해 관리됩니다. 머천다이저는 2부에서 설명한 Author → Test → Promote 워크플로우를 통해 코호트 정책을 생성하고 테스트한 뒤 배포할 수 있습니다.
비건 코호트 정책 예시
머천다이저는 다음과 같은 특성을 가진 코호트 정책을 생성합니다.
- 코호트:
["vegan"] - 일치 조건: 모든 쿼리(또는 특정 제품 카테고리)에 일치
작업: dietary_restrictions: "vegan" 항목에 부스트 가중치 2를 적용하는 소프트 부스팅 수행

코호트 활성화 방식
각 정책 문서에는 cohorts 필드가 있습니다. 코호트에 관계없이 모든 쇼핑객에게 적용되는 유니버설 정책은 이 필드를 비워둘 수 있으며 이 경우 거버넌스 제어 평면에 의해 내부적으로 "_all"이라는 값이 할당됩니다. 코호트별 정책에는 ["vegan", "kosher", “sweet_tooth”]와 같이 대상이 되는 코호트 이름이 저장됩니다.
검색 요청에 사용자 프로필이 포함될 경우 제어 평면은 퍼콜레이터 쿼리를 위한 단순한 terms 필터를 구성합니다.
이 단일 필터에는 모든 유니버설 정책과 사용자의 코호트별 정책이 함께 포함됩니다. _all 센티널 덕분에 깔끔한 포함 필터 구성이 가능해지며 정책에 코호트 제한이 없는 경우를 처리하기 위해 별도의 must_not이나 exists 쿼리를 사용할 필요가 없습니다.
그런 다음 퍼콜레이터는 평소와 같이 정책 일치 여부를 평가합니다. 유일한 차이점은 후보 정책 세트가 해당 쇼핑객의 코호트와 관련된 것들로 한정되었다는 점뿐입니다. 이후 단계의 모든 과정(연쇄적인 변환, 필드별 충돌 해결, 소비된 구문 추적 등)은 3부와 4부에서 설명한 비개인화 흐름과 동일하게 작동합니다.
"chocolate" 검색 시 비건이 아닌(일반) 사용자의 결과
비건이 아닌 사용자가 초콜릿을 검색할 경우 해당 결과에 비건 코호트 부스트가 적용되지 않습니다. 이들 사용자는 다음과 같이 상위 검색 결과에서 비건이 아닌 초콜릿을 자주 보게 됩니다.

"chocolate" 검색 시 비건 코호트 정책이 적용된 결과
비건 코호트 쇼핑객이 "chocolate"을 검색하면 이 정책이 퍼콜레이터 후보 세트에 포함됩니다. 정책이 일치하면 거버넌스 제어 평면은 비건 인증 초콜릿에 소프트 부스트를 적용합니다. 이 부스트는 곱연산 방식으로 적용되어 비건 초콜릿이 더 높은 순위에 오르지만 위 필터는 이 시리즈의 3부에서 자세히 설명한 소프트 부스팅으로 정의되었기에 비건이 아닌 초콜릿이 결과에서 완전히 제외되지는 않습니다.

하지만 쇼핑객이 명시적으로 "Hershey milk chocolate"을 검색한다면 비건 부스트가 여전히 적용되기는 하지만 Hershey 밀크 초콜릿 제품이 가진 더 강력한 텍스트 관련성에 의해 그 효과가 상쇄될 수 있습니다.

비건 코호트에 속하지 않은 쇼핑객이 동일한 쿼리를 검색할 때는 "vegan cohort" 정책을 전혀 보지 못하며 해당 정책은 그들의 후보 세트에도 포함되지 않습니다. 거버넌스 계층은 동일하며 오직 활성화된 정책 세트만 다를 뿐입니다.
구매 이력과 코호트의 결합
방대한 구매 이력을 가진 비건 쇼핑객은 구매 이력 기반의 부스트뿐만 아니라 비건 코호트 전용 정책 활성화 혜택을 동시에 받게 됩니다. 신규 방문자나 익명 쇼핑객의 경우 어떠한 행동 데이터가 없더라도 추정된 코호트 멤버십만으로 의미 있는 개인화를 제공할 수 있습니다. 예를 들어 익명 사용자가 비건 제품만 검색했다면 해당 사용자를 비건 코호트 멤버로 분류할 수 있습니다. 계정 생성 시 스스로를 할랄 준수자로 식별한 쇼핑객은 첫 번째 검색부터 즉시 할랄에 최적화된 결과를 받게 됩니다.

개인화 계층의 구성 방식
function_score 계층의 중첩 순서는 매우 중요합니다. 최내곽에서 최외곽 순으로 나열하면 다음과 같습니다.
- 기본 쿼리: 명명된 쿼리(
fulltext_match,title_phrase_match)를 사용한 키워드 또는 시맨틱 매치 - 거버넌스 정책 계층:
bool.filter절로 구현된 하드 필터와function_score함수로 구현된 소프트 부스트(3부 및 4부 참고) - 비즈니스 신호 부스트: 마진 및 인기도 부스팅(7부에서 자세히 다룰 예정)
- 구매 이력 부스트: 최외곽의
function_score계층
이러한 순서는 거버넌스가 결과 세트를 제어(무엇이 노출될지)하고 비즈니스 신호가 해당 세트 내의 순위를 조정(소매업체 관점에서 무엇을 먼저 보여줄지)하며, 구매 이력이 개인의 행동에 기반해 순위를 추가로 조정(쇼핑객 관점에서 무엇을 먼저 보여줄지)하도록 보장합니다. 각 계층은 이전 계층을 곱연산 방식으로 감싸기 때문에 효과들이 서로 충돌하지 않고 결합되어 작용하게 됩니다.
운영 측면에서의 의미
거버넌스 제어 평면을 통한 개인화는 1부와 2부에서 설명한 모든 운영적 특성을 그대로 유지합니다.
- 배포가 필요 없는 변경: 코호트 정책은 관리자 UI를 통해 생성, 테스트 및 배포됩니다. 새로운 식단 관련 코호트를 추가하거나 부스트 가중치를 조정할 때 코드 수정이나 엔지니어의 개입이 전혀 필요하지 않습니다.
- 감사 가능성: 모든 코호트 정책은 버전이 관리되는 개별 문서입니다. 머천다이저가 "왜 이 사용자에게 비건 제품이 더 높게 노출되나요?"라고 묻는다면 해당 쿼리에 실행된 다른 모든 정책과 함께 디버그 패널에 표시되는 특정 우선순위의 특정 정책이 그 답이 됩니다.
- 충돌 해결: 코호트 정책은 3부에서 설명한 것과 동일한 필드별 충돌 해결 프로세스에 참여합니다. 만약 코호트 정책의 카테고리 부스팅이 캠페인 정책의 카테고리 오버라이드와 충돌하더라도 별도의 처리 없이 동일한 우선순위 및 전략 프레임워크에 의해 결정론적으로 해결됩니다.
- 측정 가능성: 코호트 정책은 개별적으로 분리되어 있고 각각 활성화 여부를 전환할 수 있으므로 시스템 내 다른 정책들과 마찬가지로 전환율, 클릭률, 장바구니 담기 비율에 미치는 영향을 독립적으로 측정할 수 있습니다.
이 시리즈의 다음 내용
다음 게시물에서는 거버넌스 제어 평면의 또 다른 측면을 살펴봅니다. 바로 정책을 통해 마진과 인기도 부스팅을 쿼리마다 세밀하게 조정하여 경제적 최적화를 단순한 정적 설정을 넘어 거버넌스 차원의 의사결정으로 전환하는 방법입니다.
7부 참조: 쿼리 기반의 거버넌스 경제적 최적화: 쿼리별 마진 및 인기도 부스팅
거버넌스 기반 전자 상거래 검색 실제로 적용해 보기
이 게시물에서 설명한 개인화 패턴(개별 구매 이력 부스팅 및 코호트 인지 정책 활성화)은 Elastic 서비스 엔지니어링 팀이 반복 가능한 이커머스 검색 액셀러레이터의 일환으로 설계하고 구축했습니다. 두 메커니즘 모두 이 시리즈 전반에서 설명한 거버넌스 제어 평면 아키텍처와 통합됩니다. Elastic 전문 서비스에 문의하세요.
논의에 참여하기
검색 거버넌스, 검색 전략 또는 전자 상거래 검색 아키텍처에 대해 궁금한 점이 있으신가요? Elastic 커뮤니티 대화에 참여하세요.




