벡터 검색

복합 벡터 인덱스를 사용한 필터링된 ANN 검색(1부)

이 포스팅은 Couchbase의 복합 벡터 인덱싱에 대한 여러 파트로 구성된 시리즈의 시작을 알립니다. 직관 구축부터 시작하여 내부, 실행 최적화 및 성능에 대해 점진적으로 살펴볼 것입니다.

이 시리즈에서 다룰 내용입니다:

  1. 개념, 용어, 개발자의 동기 부여 등 복합 벡터 인덱스가 중요한 이유에 대해 알아보세요. 스마트 식료품 추천 시스템을 실행 예제로 사용합니다.
  2. 카우치베이스 인덱싱 서비스 내에서 복합 벡터 인덱스가 구현되는 방식입니다.
  3. 복합 벡터 쿼리에서 ORDER BY 푸시다운이 작동하는 방식입니다.
  4. 실제 성능 동작 및 벤치마킹 결과.

필터링된 ANN이 적용된 스마트 식료품 추천 시스템

식료품 추천 앱을 구축한다고 가정해 보겠습니다.

사용자가 일요일 아침에 이 앱을 열고 입력합니다:

“다크 초콜릿 스프레드를 좋아하지만 설탕을 줄이고 단백질을 더 추가하려고 합니다. 또 어떤 제품을 사야 하나요?”

이때 시스템은 사용자의 의도를 이해하고, 식품을 의미론적으로 비교하며, 엄격한 영양 필터를 적용해야 합니다.

바로 이 점이 필터링된 근사 이웃(필터링된 ANN)이 필요한 이유입니다:

  • ANN 레이어는 먼저 맛 프로필, 질감 또는 카테고리에서 다크 초콜릿 스프레드와 “같은 느낌'의 의미론적으로 유사한 항목/식품을 찾습니다.  
  • 그런 다음 필터링 계층이 개입하여 당분이 많은 항목을 제거하고, 특정 단백질 기준치 이상의 항목을 유지하며, 식단 선호도(비건, 케토, 견과류 없음)를 적용할 수도 있습니다.

결과는? 고객의 취향을 파악하고 고객의 목표를 고려하는 스마트한 매장 직원처럼 의미와 제약 조건을 모두 이해하는 추천 엔진입니다.

FANN에 들어가기 전에 직관력을 키우자

NN(가장 가까운 이웃): 찾기 가장 유사한 것 에 대한 질문입니다. “내 목록에서 이 초콜릿 스프레드와 가장 비슷한 맛이 나는 음식은 무엇인가요?”라고 묻는 것과 같습니다.”

ANN(대략적인 가장 가까운 이웃): 찾기 매우 유사한 것, 와 비슷하지만 더 빠릅니다. 마치 “나는 더 이상 완벽한 일치하는 것, 그냥 충분히 가깝게 빨리.”

FANN(필터링된 근사 이웃): 찾기 충분히 가까운 것 하지만 특정 규칙을 충족하는 항목 중에서만. “초콜릿 스프레드와 비슷한 식품을 보여주되 설탕 함량이 낮고 단백질 함량이 높은 것만 보여줘”라고 말하는 것과 같습니다.”

ANN 알고리즘은 효과 (정확도)를 훨씬 더 효율성 (속도 및 메모리).

A 복합 지수 는 다음을 기반으로 구축된 인덱스입니다. 여러 필드(열) 하나가 아닌 함께. 전자예를 들어 스프레드시트를 카테고리별로 먼저 정렬한 다음 설탕, 단백질 순으로 정렬하는 것과 같습니다. 이 주문 방법은 모든 초콜릿 스프레드를 먼저 그룹화합니다. 해당 그룹 내에서 모든 제품을 스캔하지 않고도 저당, 고단백 제품을 빠르게 찾을 수 있습니다.

기존 인덱스가 실패하는 이유

메모리에 세계 식량 현황 데이터 집합의 작은 하위 집합이 다음과 같이 로드되어 있다고 가정합니다:

설탕 함량이 낮고 단백질 함량이 높은 다크 초콜릿 스프레드와 같은 식품을 찾으려면 아래와 같은 검색어를 사용할 수 있습니다:

쿼리 속도를 높이려면 아래와 같은 복합 보조 인덱스를 사용할 수 있습니다: 

복합 보조 인덱스는 특정 값을 더 빠르게 조회하거나 낮은 값에서 높은 값의 범위를 반복(즉, 범위 스캔)할 수 있는 연결된 키의 정렬된 목록으로 볼 수 있습니다. 이러한 조회 값과 높은 값과 낮은 값은 쿼리 술어를 사용하여 쿼리 시점에 구성됩니다.

복합 인덱스는 구조화된 조회에 매우 효과적입니다.

하지만 카테고리 필터는 절대 찾을 수 없습니다:

  • 초콜릿 맛 견과류 버터
  • 초콜릿 단백질 스프레드
  • 헤이즐넛 코코아 블렌드
  • 초콜릿 단백질 바

... 인간은 초콜릿 스프레드의 친척이라는 것을 즉시 알 수 있습니다.

기존 인덱스는 구조만 일치하고 의미는 일치하지 않습니다. 이것이 바로 카테고리 기반 범위 스캔이 실패하는 이유입니다.

필터링된 ANN의 작동 방식

쿼리와 데이터를 벡터로 변환할 수 있습니다.

사용자의 문장은 임베딩 모델(예: OpenAI, Cohere 또는 도메인별 모델)에 입력됩니다.  

그 결과 다음과 같은 개념을 포착하는 조밀한 벡터가 생성됩니다:

  • 초콜릿 같은 맛  
  • 확산 가능한 텍스처  
  • 디저트/스낵 카테고리  

이 벡터는 문자 그대로의 단어가 아닌 사용자가 원하는 것을 나타냅니다.

다음으로 가장 가까운 이웃을 찾을 수 있습니다(의미적 유사성).

후보에는 다음이 포함될 수 있습니다:

  • 헤이즐넛 코코아 스프레드  
  • 초콜릿 아몬드 버터  
  • 코코아 단백질 스프레드  
  • 초콜릿 타히니  

하지만 모두가 건강에 좋은 옵션은 아니며, 사용자는 특별히 저당분과 고단백을 요구했습니다.

엄격한 필터를 적용할 수 있습니다. 은 필터링된 ANN의 “필터링된” 부분입니다.

항목을 필터링할 수 있습니다:

  • 설탕 > 기준치(예: 1회 제공량당 5g 초과)  
  • 단백질 기준치 미만(예: 1회 제공량당 8g 미만)  

시스템에서 메타데이터 필터를 결합할 수도 있습니다:

  • 비건 전용  
  • 팜유 없음  
  • 견과류 없음  
  • $10 미만  

남은 것은 의미와 제약 조건이 모두 일치하는 항목 집합입니다.

필터만 사용하는 것이 효과가 없는 이유

필터만 사용하면 다음과 같은 결과를 얻을 수 있습니다:

  • 고단백, 저당분 제품  
  • 초콜릿과 관련이 없는 품목(두부, 그릭 요거트, 닭가슴살 등)도 가능합니다.

하지만 사용자는 “초콜릿 스프레드와 비슷한” 것을 원합니다.”

필터링된 ANN = 개인화 + 제약 조건. 실제 매장 직원이 요청에 응답하는 방식을 모방합니다: “초콜릿 스프레드와 비슷하지만 더 건강한 것을 원한다면 이걸 드셔보세요...”

그러나 그 이면에는 미묘하지만 심각한 문제가 있습니다. 최신 벡터 데이터베이스는 “하이브리드 검색'을 할 수 있다고 말하지만, 보통 설탕이나 단백질과 같은 스칼라 필드는 일반 메타데이터로 따로 보관합니다. ANN 인덱스는 이를 사용하는 방법을 모릅니다.

그렇다면 어떻게 될까요?

시스템은 먼저 벡터와 유사한 후보군을 대량으로 가져온 다음, 당류_100g 10과 같은 영양 규칙을 검사하기 시작합니다.

마치 매장 직원이 뒷방에서 초콜릿 관련 제품을 모두 꺼내 카운터 위에 올려놓고 이렇게 말하는 것과 같습니다:

“아, 잠깐만요, 저당을 원하셨어요? 고단백? 이거 대부분 버릴게요.”

일부 벡터 시스템은 그래프 탐색 중에 더 일찍 필터링을 시도하지만, 여전히 실제 범위 필터링이나 접두사 가지치기를 수행할 수 없습니다. 이러한 시스템은 모든 후보를 가져와 디코딩한 후 버릴지 여부를 결정해야 합니다.

이것이 앱에 어떤 의미가 있을까요?

  • 디스크 읽기 횟수 증가
  • 더 많은 거리 계산
  • 지연 시간 증가

...그리고 사용자가 결코 볼 수 없는 결과를 위해 낭비되는 많은 작업도 있습니다.

이것이 바로 벡터 유사도와 스칼라 가지치기를 동일한 인덱스로 병합하는 복합 벡터 인덱스가 판도를 바꾸는 이유입니다.

복합 벡터 인덱스 - 개요

1단계: 임베딩 레이어 - 벡터 임베딩 만들기

각 제품의 텍스트 설명(태그, 제품명, 카테고리, 성분)은 언어 모델을 사용하여 고차원 벡터로 변환됩니다. 비슷한 의미를 가진 제품은 비슷한 벡터를 갖게 됩니다.

예를 들어 제품 이름 임베딩을 예로 들 수 있습니다:

  •  “다크 초콜릿 스프레드” → [0.23, -0.15, 0.87, ...] (384개 치수)
  •  “초콜릿 헤이즐넛 버터” → [0.25, -0.12, 0.85, ...](유사한 벡터)
  •  “초콜릿 단백질 바” → [0.18, -0.08, 0.79, ...] (다소 유사)

2단계: FANN 인덱스: 복합 벡터 인덱스 구축

임베딩 공간에서 가장 가까운 이웃을 빠르게 찾을 수 있는 벡터 인덱스(예: 카우치베이스 벡터 인덱스, FAISS)를 생성합니다.

복합 벡터 인덱스에서 벡터는 다른 데이터 유형과 어떻게 다른가요?

  • 벡터에는 자연스러운 전체 순서가 없으므로 인덱스 구성 시 벡터 필드의 정렬 순서를 인덱스 시점에 결정할 수 없습니다.
    • 벡터 필드는 WHERE 절에서 기존 비교 술어(예: 같음 또는 범위 필터)를 지원하지 않습니다.
    • 그러나 벡터 필드는 벡터 거리 함수와 함께 ORDER BY에 사용되며 이러한 표현식을 통해 쿼리 계획에 참여할 수 있습니다.
  • 정렬은 쿼리 벡터와의 유사도를 사용하여 스캔 시점에 이루어집니다. 유사도 함수는 데이터와 애플리케이션에 따라 필요에 따라 사용자가 선택합니다.
    • APPROX_VECTOR_DISTANCE는 ORDER BY 절에서 사용할 수 있으며 호환되는 벡터 인덱스가 존재할 때 효율적으로 지원되며, 그렇지 않으면 전체 스캔을 수행합니다.
  • 벡터의 각 차원은 독립적인 의미가 없으므로 “두 벡터가 얼마나 비슷한가”와 같은 질문만 할 수 있습니다. 따라서 가장 가까운 이웃 또는 유사한 요소만 찾을 수 있습니다.
    • 마찬가지로 함수 및 쿼리도 쿼리 시 입력으로 제공해야 합니다.
  • 최접근 이웃 검색은 벡터 차원이 증가할수록 계산 집약적인 문제입니다. 따라서 대략적인 결과를 얻으려면 시간과 공간 효율적인 솔루션이 필요합니다.
    • 정량화 방법은 DDL의 설명에 나와 있습니다.
  • 쿼리 시 비교 횟수를 줄이면 쿼리 속도를 높일 수 있습니다.
    • 중심체 수와 n프로브 값은 검색 공간을 줄이는 데 도움이 됩니다.

복합 벡터 인덱스는 키 중 하나 이상에 벡터 속성이 있고 차원, 유사성, 설명 등과 같은 다른 속성이 주어져 벡터를 한정하는 인덱스입니다.

이 정의에서 VECTOR 키워드는 text_vector를 벡터 어트리뷰트로 명시적으로 표시합니다. 이는 JSON 수준에서 벡터 임베딩이 단순한 부동 소수점 숫자 배열로 저장되기 때문에 필요합니다. 벡터 어노테이션이 없으면 GSI는 필드를 일반 배열로 취급하고 표준 인덱싱 시맨틱을 적용합니다.

필드를 벡터로 선언함으로써 사용자는 GSI 서비스와 명시적인 계약을 맺게 됩니다:

  • 인덱스에는 단일 벡터 키가 포함되며, 이 키는 이 인덱스에서 벡터 유사성 검색에 사용되는 임베딩을 나타냅니다.
  • 애플리케이션은 벡터 임베딩을 생성하고(예: 외부 임베딩 모델 사용) 지정된 문서 필드에 임베딩을 유지합니다.
  • GSI 서비스는 필드를 의미론적으로 벡터 임베딩으로 해석하고 기존의 스칼라 또는 배열 인덱싱 로직을 사용하는 대신 ANN(근사 최인접 이웃) 검색에 최적화된 벡터 인식 인덱스 구조를 구축해야 합니다.

벡터 인덱스 DDL에서 사용자는 다음과 같은 몇 가지 추가 매개 변수를 지정해야 합니다:

  • 치수: 생성된 벡터 임베딩의 길이
  • 유사도: ANN 검색에 사용되는 메트릭
  • 설명: 정확도 대 속도 트레이드 오프를 지정하는 설명과 같은 FAISS 인덱스입니다.

위의 예시에서는

  • 문장 변환기/all-MiniLM-L6-v2 모델을 사용하여 태그, 제품 이름, 카테고리 및 성분 필드에 대한 384개의 차원 임베딩을 생성하고 이를 문서의 text_vector 필드에 저장했습니다. 
  • 기본 중심 수와 SQ8 양자화 기능을 갖춘 IVF 굵은 양자화기를 사용했습니다.

3단계: 필터링된 ANN 쿼리

정확한 카테고리를 기준으로 필터링하지 않습니다:

  • “다크 초콜릿 스프레드” 쿼리에 대한 임베딩을 생성합니다.”
    • 쿼리 텍스트 = “다크 초콜릿 스프레드”
    • 쿼리 임베딩 = [0.23, -0.15, 0.87, 0.42, ..., -0.31] # 384차원 벡터
  • ANN 검색을 사용하여 기준(설탕_100g 10)을 충족하는 가장 유사한 제품 상위 10개(예: 상위 10개)를 찾습니다.
  • 상위 일치 항목을 반환합니다.

SQL++ 예제(카우치베이스):

주요 이점

이 접근 방식은 다음과 같은 제품을 찾습니다:

  • “다크 초콜릿 스프레드'와 의미적으로 유사합니다(벡터 검색 사용).
  • 영양 필터(저당, 고단백)를 만나보세요.
  • “초콜릿 단백질 바”, “견과류 버터 스프레드” 또는 “초콜릿 맛 스낵'과 같이 의미는 비슷하지만 ”초콜릿 스프레드“ 카테고리 필터와 일치하지 않는 다른 카테고리의 제품이 포함될 수 있습니다.

이 시리즈의 다음 편에서는 복합 벡터 인덱스에 대해 자세히 알아보면서 다음과 같은 실용적인 질문에 답해 보겠습니다:

  • 인덱스 레이어 내에서 벡터 임베딩을 효율적으로 저장하고 구성하는 방법은 무엇인가요?
  • 복합 벡터 인덱스가 전체 문서를 읽지 않고 스칼라 전용 쿼리에 응답할 수 있나요?
  • 인덱스 정의에서 스칼라 필드와 벡터 필드의 순서가 중요한가요?

복합 벡터 인덱싱의 메커니즘에 대해 자세히 알아보세요. 두 번째 게시물 이 시리즈에서는 Couchbase 내에서 구현하는 방법을 살펴봅니다.

이 문서 공유하기
받은 편지함에서 카우치베이스 블로그 업데이트 받기
이 필드는 필수 입력 사항입니다.

작성자

게시자 사이 코마라주

댓글 남기기

카우치베이스 카펠라를 시작할 준비가 되셨나요?

구축 시작

개발자 포털에서 NoSQL을 살펴보고, 리소스를 찾아보고, 튜토리얼을 시작하세요.

카펠라 무료 사용

클릭 몇 번으로 Couchbase를 직접 체험해 보세요. Capella DBaaS는 가장 쉽고 빠르게 시작할 수 있는 방법입니다.

연락하기

카우치베이스 제품에 대해 자세히 알고 싶으신가요? 저희가 도와드리겠습니다.