Python과 Snowflake Cortex로 실무용 의미 검색 구축
Source: Dev.to
최근에 카탈로그에 AI 기반 의미 검색을 구현하는 작업을 맡게 되었고, 이미 Snowflake를 사용하고 있기 때문에 Cortex Search Service를 이용해 이 기능을 구현하기로 했습니다.
Cortex Search가 무엇인지 모른다면, 간단히 개요를 확인할 수 있는 링크를 참고하세요. 기본적으로 Cortex Search는 Snowflake에 이미 저장된 데이터를 기반으로 저지연 의미 검색 및 전체 텍스트 검색을 직접 구축할 수 있게 해줍니다.
이 글에서는 Python 백엔드 애플리케이션에 Cortex Search Service를 통합하면서 겪은 경험과 주의해야 할 점들을 공유합니다.
SEARCH_TEXT 컬럼
정확히 말하면 Cortex Search가 인덱싱하고 검색에 활용하는 “search column”이 바로 SEARCH_TEXT 컬럼입니다.
처음에 제가 저지른 실수는 거의 모든 필드를 SEARCH_TEXT 컬럼에 넣으려 했던 것입니다.
처음에는 논리적으로 타당해 보였습니다. Cortex Search가 볼 수 있는 필드가 많을수록 더 많은 컨텍스트를 가질 수 있다고 생각했죠. 하지만 실제로는 검색 텍스트가 잡음(noise)으로 가득 차게 됩니다.
예를 들어, ID, 상태 플래그, 테넌트/회사 ID, 통화 ID, 내부 카테고리 ID와 같은 필드는 의미 검색에 거의 도움이 되지 않습니다.
'id:' || COALESCE(id::STRING, ''),
'warehouse_id:' || COALESCE(warehouse_id::STRING, ''),
'project_id:' || COALESCE(project_id::STRING, ''),
'is_active:' || COALESCE(is_active::STRING, ''),
'status:' || COALESCE(status, ''),
'currency_id:' || COALESCE(currency_id::STRING, '')
이러한 필드는 사용자가 검색하고자 하는 내용에 포함되지 않는 경우가 대부분입니다. 대신 ATTRIBUTES 로 노출해 필터링에 사용해야 합니다. 다음 섹션에서 자세히 다루겠습니다.
SEARCH_TEXT 컬럼은 실제 아이템의 의미를 설명하는 필드에 집중해야 합니다.
CONCAT_WS(
' ',
title,
brand,
origin,
category_name,
subcategory_name,
short_description
) AS SEARCH_TEXT
즉, 최종 사용자가 검색할 때 유용한 필드만 포함해야 합니다.
ATTRIBUTES
Cortex Search Service 설정에서 중요한 작은 디테일 중 하나가 ATTRIBUTES 속성입니다.
ON 컬럼은 검색 가능한 텍스트 컬럼이며, Cortex Search가 사용자 질의와 매칭하는 데 사용됩니다. 하지만 조직, 상태, 카테고리, 브랜드, 지역, 가용성 등 메타데이터로 결과를 필터링해야 한다면, 해당 컬럼들을 서비스 생성 시 ATTRIBUTES 에 추가해야 합니다.
Attributes는 메인 검색 텍스트가 아니라, 검색 결과와 함께 반환되고 필터링이나 표시용으로 활용되는 컬럼입니다. Snowflake 문서에 따르면, Cortex Search 필터링은 CREATE CORTEX SEARCH SERVICE 명령에 지정된 ATTRIBUTES 컬럼을 기준으로 동작합니다.
CREATE OR REPLACE CORTEX SEARCH SERVICE DEMO_DB.SEARCH.PRODUCT_SEARCH_SERVICE ON SEARCH_TEXT
ATTRIBUTES (
CITY,
COUNTRY,
CURRENCY,
IS_ACTIVE
)
WAREHOUSE = WH_SEARCH_DEMO
TARGET_LAG = '1 hour'
AS
SELECT
ID,
CITY,
COUNTRY,
CURRENCY,
IS_ACTIVE
FROM DEMO_DB.SEARCH.PRODUCT_SEARCH_SOURCE;
핵심 포인트: 나중에 필터링하고 싶은 모든 컬럼은 ATTRIBUTES 로 선언돼 있어야 합니다. 또한, ATTRIBUTES 에 포함된 컬럼은 서비스 생성에 사용된 소스 쿼리에도 반드시 포함돼야 합니다.
Python 코드에서 필터를 적용하는 예시입니다.
from typing import Any, Dict, List
COUNTRY_ATTRIBUTE = "COUNTRY"
CITY_ATTRIBUTE = "CITY"
IS_ACTIVE_ATTRIBUTE = "IS_ACTIVE"
filters: List[Dict[str, Any]] = [
{
"@or": [
{
"@eq": {
COUNTRY_ATTRIBUTE: "UK",
}
},
{
"@eq": {
CITY_ATTRIBUTE: "London",
}
},
],
},
{
"@eq": {
IS_ACTIVE_ATTRIBUTE: True,
}
},
]
response = search_service.search(
query=query,
columns=[
"ID",
"COUNTRY",
"CITY",
"IS_ACTIVE",
],
filter={
"@and": filters,
},
limit=20,
)
필터는 가능한 한 선택적이고 이해하기 쉬운 형태로 유지하세요. 좋은 필터는 Cortex Search가 결과를 순위 매기고 반환하기 전에 검색 공간을 크게 줄여줍니다. 필터 페이로드가 지나치게 크거나 중첩되면, 검색 소스 테이블 자체를 조정하는 것이 좋습니다.
TARGET_LAG
CREATE OR REPLACE CORTEX SEARCH SERVICE 명령에서 또 하나 중요한 인자는 TARGET_LAG 입니다.
간단히 말해, TARGET_LAG 는 검색 인덱스가 소스 테이블에 비해 얼마나 최신 상태를 유지할지를 제어합니다.
예시:
TARGET_LAG = '10 minutes'
이 설정이 “새로운 행이 즉시 검색 가능해진다”는 뜻은 아닙니다. Cortex Search는 내부 인덱스를 새로 고쳐야 하므로, 소스 테이블에 행을 삽입·수정한 후 다음 인덱스 새로 고침이 이루어질 때까지는 검색 결과에 반영되지 않습니다.
특히 관리형 임베딩을 사용하는 경우, 업데이트된 소스 데이터를 처리하고 임베딩을 생성·갱신한 뒤 인덱스를 새로 고쳐야 사용자가 의미 검색을 통해 해당 레코드를 찾을 수 있습니다.
따라서 TARGET_LAG 가 너무 길면 검색 결과가 오래된 상태가 될 수 있습니다. 새로 게시된 아이템이 이미 테이블에 존재하더라도, 사용자는 일정 시간 동안 검색에서 찾지 못합니다.
반대로 TARGET_LAG 를 너무 짧게 설정하면 항상 최선은 아닙니다. 인덱스를 자주 새로 고치면 Snowflake 백그라운드 작업이 늘어나고, 그에 따라 크레딧 사용량도 증가합니다. Snowflake는 너무 낮은 TARGET_LAG 가 불필요하게 인덱스를 자주 새로 고칠 수 있다고 경고합니다.
따라서 적절한 값은 검색 결과가 얼마나 신선해야 하는지에 따라 결정됩니다.
CREATE OR REPLACE CORTEX SEARCH SERVICE DEMO_DB.SEARCH.PRODUCT_SEARCH_SERVICE
ON SEARCH_TEXT
ATTRIBUTES
(
ITEM_ID,
STATUS,
IS_ACTIVE,
ACCOUNT_ID
)
WAREHOUSE = WH_SEARCH_DEMO
TARGET_LAG = '30 minutes'
AS
SELECT
ITEM_ID,
SEARCH_TEXT,
STATUS,
IS_ACTIVE,
ACCOUNT_ID
FROM DEMO_DB.SEARCH.PRODUCT_SEARCH_SOURCE;
- 사용자-facing 카탈로그 검색(새로 나온 아이템이 빠르게 보여져야 하는 경우) : 15~20분 정도가 적당할 수 있습니다.
- 내부 지식 베이스, 문서 검색, 변동이 적은 데이터 : 1시간 이상도 충분합니다.
EMBEDDING_MODEL
Cortex Search는 벡터 검색 단계에서 임베딩 모델을 사용합니다. 이 모델은 검색 컬럼과 사용자 질의를 벡터로 변환해, 단순 키워드 매칭이 아니라 의미적으로 유사한 레코드를 찾을 수 있게 해줍니다.
Cortex Search Service를 만들 때 EMBEDDING_MODEL 파라미터로 모델을 지정할 수 있습니다.
CREATE OR REPLACE CORTEX SEARCH SERVICE DEMO_DB.SEARCH.PRODUCT_SEARCH_SERVICE
ON SEARCH_TEXT
ATTRIBUTES
(
ITEM_ID,
STATUS,
IS_ACTIVE,
ACCOUNT_ID
)
WAREHOUSE = WH_SEARCH_DEMO
TARGET_LAG = '30 minutes'
EMBEDDING_MODEL = 'snowflake-arctic-embed-m-v1.5' -- custom EMBEDDING_MODEL
AS
SELECT
ITEM_ID,
SEARCH_TEXT,
STATUS,
IS_ACTIVE,
ACCOUNT_ID
FROM DEMO_DB.SEARCH.PRODUCT_SEARCH_SOURCE;
Snowflake는 snowflake-arctic-embed-m-v1.5 를 기본 Cortex Search 임베딩 모델로 제공하고 있습니다. 이 모델은 768 차원의 출력, 512 토큰 컨텍스트 윈도우, 영어 전용 지원을 특징으로 하며, 현재 제공되는 모델 중 가장 빠른 인덱싱 속도를 자랑합니다. 따라서 영어 전용 카탈로그나 인덱싱 속도가 중요한 내부 검색에 좋은 출발점이 됩니다.
카탈로그가 다국어를 지원해야 한다면, 기본 영어 모델만으로는 부족