이 블로그 게시물은 Couchbase에서 복합 벡터 인덱싱을 살펴보는 여러 부분으로 구성된 시리즈의 두 번째 블로그 게시물이며, 첫 번째 게시물을 확인하세요. 여기.
이 시리즈에서 다룰 내용입니다:
- 개념, 용어, 개발자의 동기 부여 등 복합 벡터 인덱스가 중요한 이유에 대해 알아보세요. 스마트 식료품 추천 시스템을 실행 예제로 사용합니다.
- 카우치베이스 인덱싱 서비스 내에서 복합 벡터 인덱스가 구현되는 방식입니다.
- 복합 벡터 쿼리에서 ORDER BY 푸시다운이 작동하는 방식입니다.
- 실제 성능 동작 및 벤치마킹 결과.
복합 벡터 인덱스 구현
GSI는 내부적으로 FAISS 인덱스 팩토리를 사용합니다. 아래 표시된 것과 같은 인덱스 문자열은 DDL에 지정된 설명 필드에서 구성됩니다.
|
1 |
IVF{nlist}_HNSW,{PQ{M}x{N}|SQ{n} |
예시: 예: “IVF10000,PQ32x8”과 같은 설명 문자열을 사용하면 “IVF10000_HNSW,PQ32x8”과 같은 FAISS 인덱스 팩토리 문자열이 구성됩니다.
그러나 이 팩토리 문자열은 인덱스 학습 및 구성 중에 빌딩 블록으로만 사용됩니다. GSI는 전체 데이터 세트에 대한 모놀리식 인메모리 인덱스로서 FAISS를 사용하지 않습니다. 대신, FAISS는 샘플 데이터에 선택적으로 적용되어 중심과 양자화 코드북을 학습하고, 전체 인덱스 레이아웃, 저장, 변이 처리, 쿼리 실행은 Couchbase에서 관리합니다. 이를 통해 Couchbase는 벡터 검색을 효율적으로 확장하고, 스칼라 필터링과 쿼리 시맨틱을 통합하며, 지속적인 업데이트를 지원할 수 있습니다. - 독립형 FAISS 인덱스를 호출하는 것 이상으로 확장된 기능을 제공합니다.
임베딩
임베딩 레이어는 관련 텍스트 필드를 가져와 트랜스포머 모델을 통해 전송하고 각 제품을 나타내는 시맨틱 벡터를 생성합니다. 이 벡터는 ANN 검색을 구동합니다. 애플리케이션은 이러한 임베딩을 제품 데이터와 함께 저장해야 벡터 인덱스가 이를 사용할 수 있으며, 저장된 임베딩이 Couchbase에서 예상하는 벡터 인덱스 정의(예: 고정 차원 및 숫자 유형)와 일치하는지 확인해야 합니다.
|
1 2 3 4 5 6 7 8 |
{ "product_name": "almond butter", "sugar_100g": 15, "proteins_100g": 20, "description": "almond butter with chocolate chips", "text_vector": [0.12, -0.04, 0.33, 0.25, ...] ... } |
인덱스 생성 및 빌드

|
1 2 3 4 5 6 7 8 9 10 11 12 |
CREATE INDEX idx_vec_food ON food ( text_vector VECTOR, sugars_100g, proteins_100g, product_name ) WITH { "dimension": 384, "similarity": "L2", "description": "IVF,SQ8" }; |
흐름 생성 및 구축
- 사용자가 충분한 수의 벡터를 저장한 경우, GSI는 무작위로 데이터를 샘플링하여 대표 데이터 세트를 생성합니다.
- 이 대표 데이터 세트는 FAISS 지수를 학습하는 데 사용됩니다. 훈련 후 GSI는 IVF 반전 목록, PQ 코드북을 포함하는 코드북을 유지합니다:
- IVF는 벡터 공간을 n리스트 파티션으로 분할하고 각 파티션은 중심점으로 표시됩니다.
- 벡터를 압축하기 위해 파티션당 PQ 또는 SQ 양자화가 사용됩니다.
- 구심점의 HNSW 그래프가 생성되며, 구심점 수가 매우 많을 때 유용합니다.
- 데이터는 DCP 프로토콜을 사용하여 데이터 서비스에서 수신된 각 문서에 대해 스트리밍됩니다.
- 문서는 벡터에 가장 가까운 중심이 있는 파티션에 할당됩니다(즉, 중심이 C1인 경우).
- 인덱싱하는 동안 각 문서의 벡터는 가장 가까운 중심점에 할당되고, 빠른 거리 계산에 최적화된 압축 인코딩 형식으로 저장되며, 효율적으로 추적되어 향후 업데이트에서 벡터가 실제로 변경되는 경우에만 재처리됩니다.
참고
기본 벡터 분포가 크게 변경되면 인덱스를 재구축하여 인덱스를 다시 학습시켜야 합니다. 이는 다음과 같은 이유로 발생할 수 있습니다:
- 임베딩 모델 변경(예: 모델 또는 치수 전환)
- 데이터 배포의 주요 변화(새로운 제품 카테고리, 언어 변경 또는 도메인 이동)
- 업데이트된 임베딩으로 기존 문서 다시 임베딩하기
벡터 인덱스를 학습하려면 얼마나 많은 문서가 필요하나요?
카우치베이스는 벡터 인덱스를 구축할 때 자동으로 벡터 데이터를 샘플링하여 효율적이고 정확한 ANN 검색을 위한 인덱스를 학습시킵니다.
일반적으로 각 중심에는 학습 데이터가 필요하므로 컬렉션에는 인덱스에 구성된 중심(nlist)의 수만큼의 문서가 포함되어야 합니다.
제품 양자화(PQ)를 사용하는 경우, 이 최소값은 최대(nlist, 2^n비트)가 되어 양자화 학습에 충분한 데이터를 확보할 수 있으며, 여기서 n비트는 PQ{M}x{N}에서 N비트입니다.
카우치베이스는 다음과 같이 교육을 처리합니다:
- 소규모 데이터 세트(최대 ~10,000개 문서):
- 모든 벡터는 트레이닝에 사용되며 샘플링이 필요하지 않습니다.
- 더 큰 데이터 세트:
- 기본적으로 카우치베이스는 다음 중 더 큰 것을 취하여 훈련 집합을 선택하고 최종 샘플 크기는 100만 개의 벡터로 제한합니다:
- 10%의 데이터 세트와
- 10배의 중심체 수(nlist)
- 이 접근 방식은 교육 품질과 인덱스 구축 시간의 균형을 맞춥니다.
- 기본적으로 카우치베이스는 다음 중 더 큰 것을 취하여 훈련 집합을 선택하고 최종 샘플 크기는 100만 개의 벡터로 제한합니다:
모범 사례: 안정적이고 고품질의 학습을 위해서는 인덱스가 기본 벡터 분포를 효과적으로 학습할 수 있도록 중심점당 최소 10개의 벡터를 목표로 하세요.
고급 제어: 필요한 경우 인덱스를 생성할 때 train_list 매개변수를 지정하여 학습 벡터의 수를 명시적으로 제어할 수 있습니다.
카우치베이스는 샘플링과 훈련 프로세스를 자동으로 관리합니다. 사용자로서 핵심 요구 사항은 색인을 구축하기 전에 충분한 수의 임베디드 문서가 존재하는지 확인하는 것입니다.
색인 스캔
|
1 2 3 4 5 |
SELECT product_name FROM food WHERE sugars_100g < 20 AND proteins_100g > 10 ORDER BY APPROX_VECTOR_DISTANCE(text_vector, [query_embedding], 'L2') LIMIT 10; |
스캔 흐름
벡터 검색이 포함된 쿼리가 도착하면 Couchbase는 잘 정의된 순서에 따라 올바른 구성 요소를 통해 쿼리를 라우팅하고 스캔을 준비합니다.

색인 가능한 벡터 인덱스가 생성되어 활성화되어 있으면 쿼리 서비스에서 이를 사용하여 필터링된 ANN 쿼리를 더 빠르게 제공할 수 있습니다.
인덱서 프로세스의 스캔 코디네이터는 인덱스 이름 및 일관성 매개변수와 함께 아래에 언급된 입력을 받습니다:
- 인덱싱된 필드의 스칼라 필터
- 같음 또는 범위 술어(예: sugars_100g < 20
- 쿼리 벡터
- 사용자의 검색 의도 임베딩
- 결과 크기 k
- 사용자가 반환을 원하는 유사한 항목의 수
- 이것은 LIMIT
- 가장 가까운 구심점의 수 n
- 검색하려는 벡터 공간의 크기
- n프로브 값 n은 약_벡터_거리
위의 정보를 받은 후 스캔 요청이 생성되며, 다음 단계를 거칩니다:
- 인덱스 스냅샷은 주어진 일관성 매개변수에 따라 가져옵니다.
- 사용자가 세션 일관성이 필요한 경우, 인덱서는 데이터 서비스에서 최신 타임스탬프를 가져와서 인덱서가 따라잡고 쿼리를 제공할 수 있는 일관된 스냅샷을 생성할 때까지 기다립니다.
- 사용자가 일관성에 문제가 없는 경우 인덱서는 대기하지 않고 캐시한 스냅샷을 사용합니다.
- 인덱서는 쿼리를 처리하는 데 필요한 스토리지 리더를 기다립니다.
- 인덱서의 리소스가 제한되어 있으므로 실행할 수 있는 최대 리더 수로 구성되므로 시스템이 사용 중일 경우 리더가 여유가 생길 때까지 스캔을 기다려야 합니다.
- 인덱서는 주어진 쿼리에 대해 스캔 중인 파티션의 코드북을 가져옵니다.
- 코드북(IVF 반전 목록 및 PQ 코드북)은 초기 교육 단계 이후에는 변경되지 않으므로 코드북에 대한 스냅샷이 없습니다.
- 위에서 가져온 코드북을 사용하여 가장 가까운 nprobe 주어진 쿼리 벡터에 대해 필요한 모든 파티션에서 쿼리 벡터의 중심을 가져옵니다.
- 주어진 인덱스 파티션에 대해 스칼라 필터와 가장 가까운 구심점이 결합되어 겹치지 않는 스캔 범위를 형성하여 인덱스에서 데이터를 읽습니다.
- 이제 인덱서는 전용 스캔 파이프라인을 설정합니다. 이 파이프라인은 해당 쿼리를 위해 특별히 구축된 작고 수명이 짧은 조립 라인이라고 생각하면 됩니다. 파이프라인 단계(읽기, 누적, 쓰기)는 쿼리 프로세스를 가속화하기 위해 병렬로 실행됩니다.
스캔 파이프라인
스캔 파이프라인 자체는 읽기, 집계, 쓰기의 세 단계로 구성되어 있습니다. 다른 파이프라인과 마찬가지로 각 단계는 병렬로 실행되며 출력을 다음 단계로 스트리밍합니다.

- 읽기 단계 => 병렬 스캔 + 스칼라 필터링
- 읽기 단계는 여러 스캔 작업자에 걸쳐 분산됩니다.
- 각 작업자:
- 할당된 인덱스 범위를 스캔하고 읽습니다.
- 스칼라 필터를 조기에 적용하여 관련 없는 항목을 제거합니다.
- 적격 문서만 다음 단계로 전달합니다.
- 이 초기 가지치기는 매우 중요하며, 나중에 불필요한 벡터 거리 계산을 방지합니다.
- 집계 단계 => 순서 지정 + 벡터 거리 계산
- 집계 단계에서는 모든 작업자의 결과를 수집하고 “무거운 작업”을 수행합니다.”
- 필요한 경우 항목을 병합하고 주문합니다.
- 이 기능은 중심점 기반의 대략적인 거리를 실제 계산된 거리로 대체합니다.
- 지금까지 가장 좋은 후보를 추적하기 위해 상위 K 힙을 유지합니다.
- 이 단계는 ANN 로직과 스칼라 필터링이 만나는 곳입니다.
- 쓰기 단계 => 쿼리 서비스에서 GSI 클라이언트로 결과 스트리밍하기
- 마지막으로 쓰기 단계에서는 집계된 결과를 쿼리 서비스로 스트리밍합니다.
- 인덱스가 여러 인덱서 노드에 분산되어 있는 경우 각 노드의 결과가 병합됩니다.
- 여기에서 쿼리 프로세서는 다음과 같은 추가 작업을 수행할 수 있습니다:
- 세분화된 필터링
- 조인
- 투영 또는 최종 정렬
사용자는 궁극적으로 모든 스칼라 조건을 충족하는 가장 가까운 이웃의 깔끔하고 정렬된 목록을 받게 됩니다.
복합 벡터 인덱스 스캔의 고급 쿼리 동작
벡터 스캔 파이프라인의 작동 방식을 이해했다면, 쿼리 실행 효율성에 영향을 미치는 몇 가지 심층적인 개념이 있습니다. 이러한 동작은 인덱스가 정의되는 방식과 스칼라 술어가 벡터 파티션과 상호 작용하는 방식과 관련이 있습니다.
스캔 병렬 처리
각 쿼리에는 활용할 수 있는 병렬 처리의 양에 대한 자연스러운 제한이 있습니다. 이 고유한 병렬성은 스칼라 술어와 n프로브에서 얼마나 많은 비중첩 인덱스 범위를 생성할 수 있는지에 따라 달라집니다. 겹치지 않는 범위가 많을수록 병렬로 스캔할 수 있는 기회가 더 많아집니다.
하지만 스칼라 제약이 유일한 요소는 아닙니다. 시스템에서 실제로 사용하는 작업자 수는 최소한의 수입니다:
- 쿼리가 노출할 수 있는 고유 병렬 처리의 양
- 쿼리에서 검색해야 하는 중심체 수
- 인덱스에 포함된 중심체 수
- 파티션당 스캔당 구성된 최대 작업자 수
이렇게 하면 추가 병렬 작업이 없는데도 시스템이 CPU를 과도하게 사용하거나 불필요한 스레드를 생성하는 것을 방지할 수 있습니다.
주요 핵심 술어 - 병렬 처리가 시작되는 곳
선행 인덱스 키(복합 인덱스 정의에 나열된 첫 번째 필드)의 술어는 쿼리가 활용할 수 있는 병렬 처리의 양을 결정합니다.
선행 스칼라 키에 여러 개의 동일성 술어가 있는 경우, 각각은 독립적인 스캔 범위가 되어 시스템이 이를 분산하여 병렬로 실행할 수 있습니다.
선행 키가 범위 술어를 사용하는 경우 전체 쿼리가 하나의 큰 스캔 범위로 축소되어 병렬성이 감소합니다.
요컨대:
- 선행 키의 평등성 → 최대 병렬 처리
- 선행 키의 범위 → 한 번의 큰 순차 스캔
인덱스 정의에서 올바른 선행 키를 선택하면 지연 시간에 직접적인 영향을 미칩니다.
벡터 술어는 중심 ID에 대한 동일성 필터처럼 효과적으로 작동하여 스캔할 고정된 클러스터 집합을 선택합니다.
스칼라 선택성 - 하지 않아도 되는 작업의 양
스칼라 선택성은 스칼라 필터로 제거할 수 있는 데이터 포인트의 수를 측정합니다.
고도의 선택적 필터는 벡터 거리 계산이 시작되기도 전에 데이터 집합의 상당 부분을 제거합니다.
예를 들어
- sugars_100g < 20은 70%의 항목을 제거할 수 있습니다.
- 단백질_100g > 10은 90%의 항목을 제거할 수 있습니다.
스칼라가 선택적일수록 인덱스가 비교해야 하는 벡터의 수가 줄어듭니다.
복합 벡터 인덱스 스캔 처리량은 스칼라 선택성에 비례하여 증가하는데, 파이프라인이 문서를 읽고 거리를 계산하는 데 소요되는 시간이 줄어들기 때문입니다.
페이지 매김 - 정렬된 벡터 결과
복합 벡터 인덱스 스캔은 결과 세트에 대한 전역 순서를 생성합니다.
이는 인덱스가 중심점 ID를 실제 벡터 거리로 대체하여 각 후보에 정확한 거리 메트릭을 제공하기 때문에 발생합니다.
선택한 n개의 구심점 내에 적격 항목이 충분히 존재하는 한 LIMIT 및 OFFSET을 사용하여 결과를 페이지 매김할 수 있습니다.
따라서 복합 벡터 인덱스는 무한 스크롤 또는 페이지별 제품 탐색이 필요한 UI에 적합합니다.
인덱스 정의의 유연성 - 워크로드에 맞게 인덱스 조정
복합 벡터 인덱스 DDL을 사용하면 인덱스 키의 순서를 완벽하게 제어할 수 있습니다.
이 순서는 필터링이 수행되는 방식에 직접적인 영향을 미치며, 쿼리 성능에 영향을 미칩니다.
이러한 유연성 덕분에 다양한 가지치기 전략이 가능합니다:
- 벡터 중심 가지치기
- 벡터 키로 인덱스를 시작한 다음 스칼라로 인덱스를 시작합니다:
- 음식(text_vector, sugars_100g, protein_100g)에 인덱스 idx를 생성합니다.
- 주로 벡터 유사성에 의해 구동되는 워크로드에 적합합니다.
- 벡터 키로 인덱스를 시작한 다음 스칼라로 인덱스를 시작합니다:
- 스칼라 중심 가지치기
- 선택성이 높은 스칼라 필드를 먼저 배치한 다음 벡터 키를 배치합니다:
- 음식(당류_100g, 단백질_100g, 텍스트_벡터)에 대한 인덱스 idx를 생성합니다.
- 선택적 스칼라는 벡터 평가가 시작되기 전에 공격적으로 가지치기를 하기 때문에 가장 일반적이고 가장 효율적인 패턴입니다.
- 선택성이 높은 스칼라 필드를 먼저 배치한 다음 벡터 키를 배치합니다:
- 벡터 전용 가지치기
- 벡터 키에만 인덱스를 생성합니다:
- 음식(text_vector)에 인덱스 idx를 생성합니다.
- 스칼라 필드의 선택성이 좋지 않아 검색 공간을 정리하는 데 도움이 되지 않을 때 유용합니다.
- 벡터 키에만 인덱스를 생성합니다:
- 스칼라 전용 가지치기
- 벡터 유사성을 사용하지 않는 쿼리에 대해 기존의 비벡터 인덱스를 생성합니다.
스칼라 전용 쿼리 지원 및 스캔 커버링
쿼리의 스칼라 술어가 복합 벡터 인덱스의 스칼라 키와 비교 가능한 경우, 인덱스를 사용하여 순수 스칼라 쿼리를 제공할 수 있습니다. - 벡터 유사성 조건이 없는 경우에도 마찬가지입니다. 이러한 경우, 복합 벡터 인덱스는 기존의 보조 인덱스처럼 작동하며 커버링 인덱스 역할을 할 수 있으므로 데이터 서비스에서 문서를 가져오지 않고도 인덱스만으로 쿼리를 충족할 수 있습니다.
스칼라 전용 쿼리입니다:
|
1 2 3 |
SELECT product_name FROM food WHERE sugars_100g < 20 AND proteins_100g > 10; |
를 사용할 수 있습니다:
|
1 2 |
CREATE INDEX idx_vec_food ON food(sugars_100g, proteins_100g, text_vector VECTOR, product_name); |
그리고
|
1 2 |
CREATE INDEX idx_bad ON food(text_vector VECTOR, sugars_100g, proteins_100g); |
그 이유는 다음과 같습니다:
- 선행 키는 벡터입니다.
- GSI는 탐색 또는 범위 스캔이 불가능합니다.
- 인덱스는 스칼라 술어에 효율적으로 사용할 수 없습니다.
지금까지 복합 벡터 인덱스가 스칼라 프루닝과 벡터 유사성을 효율적으로 결합하는 방법을 살펴보았습니다. 하지만 실제 애플리케이션에서는 유사도만으로는 충분하지 않습니다. 대규모 결과 세트를 쿼리 계층으로 다시 가져가지 않고도 영양, 가격 또는 신선도 같은 애플리케이션별 신호에 따라 정렬된, 의미적으로 쿼리에 근접한 결과를 원한다면 어떻게 될까요?
이 시리즈의 다음 파트에서는 다음과 같은 내용을 살펴보겠습니다. 복합 벡터 인덱스를 사용한 필터링된 ANN 검색 그리고 Couchbase가 어떻게 복잡한 ORDER BY 및 LIMIT 시맨틱을 인덱서에 직접 푸시하여 거리 기반 유사성과 스칼라 순서를 단일의 효율적인 실행 경로에서 함께 평가할 수 있는지를 보여줍니다.