본문으로 건너뛰기
최종 수정: 에

Lance 하이브리드 검색 성능 최적화 모범 사례

Lance에서 벡터 검색이나 전문 검색에 스칼라 조건을 함께 사용하는 경우 이 가이드를 참고하세요. 필터를 배치하는 방법, 인덱스 세그먼트를 구성하는 방법, 반복되는 필터링 작업을 식별하는 방법을 설명합니다. 여기서 하이브리드 검색은 스칼라 필터링을 결합한 검색을 의미합니다. 벡터 TVF와 전문 검색 TVF를 별도로 호출한다고 해서 두 검색의 순위가 자동으로 융합되지는 않습니다.

노트

Lance Catalog는 실험적 기능이며 Apache Doris 4.2부터 지원됩니다. 먼저 Lance Catalog에서 접근을 구성하고 리더/라이터 호환성 요구 사항을 확인하세요. Doris는 기존 Lance 인덱스를 읽습니다. 인덱스는 호환되는 Lance 도구로 구축하고 유지 관리해야 합니다. 최신 Lance SDK나 업스트림 제안이 있다고 해서 사용 중인 Doris 빌드에 포함된 리더가 해당 기능을 지원하는 것은 아닙니다.

자동 Lance 검색이 포함된 Doris 4.2 빌드에서는 조건에 맞는 일반 SQL 쿼리가 명시적 TVF와 동일한 네이티브 검색 경로를 사용할 수 있습니다. 이 기능은 TVF를 직접 호출하는 것과 별개이므로, vector_search()가 작동한다는 이유만으로 이전 빌드도 자동 검색을 지원한다고 가정하지 마세요. 자동 검색을 위한 별도의 세션 스위치는 필요하지 않습니다.

함수 매핑​

SQL 표현식필수 정렬 방향Lance 메트릭 및 SQL 결과
l2_distance_approximateASCl2; 네이티브 제곱 L2 거리 대신 유클리드 거리인 CAST(sqrt(_distance) AS FLOAT)를 반환합니다.
inner_product_approximateDESCdot; 내적 유사도인 1.0f - _distance를 반환합니다.
cosine_distance_approximateASCcosine; 네이티브 코사인 거리를 반환합니다.
cosine_similarity_approximateDESCcosine; 1.0f - _distance를 반환합니다.
hamming_distance_approximateASChamming; 서로 다른 비트의 개수를 반환합니다.
MATCH_ANY, MATCH_ALL 또는 MATCH_PHRASE와 함께 사용하는 score()DESCLance BM25; 각각 Match OR, Match AND 또는 Phrase입니다.

cosine_distance(), cosine_similarity(), hamming_distance를 비롯한 정확한 거리 및 유사도 함수는 계속 스칼라 함수로 실행됩니다. l1_distance()도 스칼라 함수로 실행됩니다. 번들로 제공되는 Lance 벡터 검색 API는 L1 메트릭을 제공하지 않습니다.

벡터 및 BM25 예제​

Catalog를 구성한 후 lance_catalog.default.items에 id, category, content, 네 차원의 Float32 고정 크기 리스트인 embedding, 세 바이트의 UInt8 고정 크기 리스트인 binary_embedding이 있다고 가정합니다. 인덱스를 통한 검색 가속이 필요하다면 Lance 도구로 호환되는 벡터 인덱스와 content에 커밋된 FTS 인덱스를 구축하세요.

SELECT id, cosine_distance_approximate(embedding, [0.1,0.2,0.3,0.4]) AS distance
FROM lance_catalog.default.items
WHERE category = 'book'
ORDER BY distance ASC LIMIT 10 OFFSET 5;

SELECT id, cosine_similarity_approximate(embedding, [0.1,0.2,0.3,0.4]) AS similarity
FROM lance_catalog.default.items
ORDER BY similarity DESC LIMIT 10;

SELECT id, hamming_distance_approximate(binary_embedding, [0,255,170]) AS distance
FROM lance_catalog.default.items
ORDER BY distance ASC LIMIT 10;

SELECT id, score() AS relevance
FROM lance_catalog.default.items
WHERE content MATCH_ALL 'storage engine' AND category = 'book'
ORDER BY relevance DESC LIMIT 10;

첫 번째 쿼리는 후보를 가지치기하기 전에 category = 'book'에 해당하는 범위에서 검색합니다. 각 분할은 오프셋 없이 후보 15개를 요청하며, Doris가 후보를 병합한 뒤 최종 OFFSET 5 LIMIT 10을 적용합니다. 다른 쿼리들은 각각의 정렬 순서에 따라 이웃 또는 BM25 일치 항목을 최대 10개 반환합니다. 인덱스를 사용하는 검색은 근사 방식일 수 있으며 요청한 행 수보다 적게 반환할 수 있습니다. 기존의 호환 인덱스는 재사용하고, 인덱스가 적용되지 않은 데이터에는 기존 Flat Search 경로를 사용합니다. BM25에는 기존 FTS 인덱스가 필요합니다.

적용 조건과 대체 실행 경로​

  • 쿼리에는 Lance Catalog 테이블 직접 스캔, 선택적 필터, 프로젝션, 하나의 검색 정렬 키와 양수 LIMIT을 사용하는 Top-N이 있어야 합니다. JOIN/EXISTS, 보조 정렬 키, DISTINCT, 테이블 샘플링, 이동할 수 없는 프로젝션 표현식이 있으면 이 재작성이 적용되지 않습니다.
  • 부동 소수점 메트릭에는 물리적 Float32 고정 크기 리스트 벡터가, Hamming에는 물리적 UInt8 고정 크기 리스트 벡터가 필요합니다. 쿼리 벡터는 차원이 일치하는 상수 배열이어야 합니다. Float16/Float64, 다중 벡터 컬럼, 원소 값을 임의로 바꾸는 캐스트는 자동으로 재작성되지 않습니다. UInt8은 Doris SMALLINT로 노출되며, 이를 Hamming 함수의 BIGINT 인수로 확장하는 것은 안전합니다.
  • 지원되지 않는 잔여 조건, 반대 방향의 검색 정렬, 누락된 네이티브 메타데이터가 있거나 네이티브 벡터 또는 스키마 검증에 실패한 벡터 쿼리는 스칼라 평가를 유지합니다. 재작성하려면 LIMIT + OFFSET이 네이티브 UINT32 후보 예산에 들어가야 합니다. 이 제한은 일반 스칼라 SQL에는 적용되지 않습니다.
  • BM25에는 직접 참조하는 문자열 컬럼에 대한 지원되는 최상위 MATCH 조건 하나, 리터럴 쿼리, 인덱스에 저장된 분석기, 내림차순 score()가 필요합니다. 나머지 스칼라 조건은 모두 푸시다운할 수 있어야 합니다. score() > literal과 score() >= literal은 잔여 하한 조건으로 지원되지만, 상한 조건과 그 밖의 점수 조건은 거부됩니다. 지원되지 않는 점수 쿼리 형태는 score()를 조용히 스칼라 함수로 평가하는 대신 오류를 보고합니다.
  • 점수는 Lance에 저장된 분석기와 인덱스, 스냅샷 전체의 통계에서 나옵니다. 별도로 인덱싱한 Doris 내부 테이블의 점수와 수치적으로 일치하지 않을 수 있습니다. 이 재작성에서는 퍼지 검색, 분석기 재정의, 여러 컬럼을 사용하는 SEARCH() 표현식을 사용할 수 없습니다. 지원되는 다중 벡터 쿼리와 nprobes, refine_factor, ef 등의 튜닝에는 명시적 TVF를 사용하세요.

네이티브 근사 검색의 의미​

_approximate 접미사를 사용하면 적용 조건에 맞는 Top-N 쿼리에 네이티브 Lance 후보 검색 방식이 적용됩니다. 재작성되지 않으면 함수가 스칼라 커널로 실행됩니다. 모든 저장 행을 대상으로 빠짐없이 순위를 매기거나 스칼라 검증을 해야 한다면 근사 함수를 사용하지 마세요.

입력스칼라 평가적용 조건에 맞는 네이티브 검색
저장된 NULL 벡터오류후보에서 제외됩니다.
NaN 거리를 생성하는 저장 벡터NaN을 반환할 수 있습니다.제외되므로 쿼리가 더 적은 행을 반환할 수 있습니다.
NULL 벡터 원소오류지원되는 네이티브 입력 범위를 벗어납니다. 원소에 NULL이 없도록 하세요.
저장된 노름이 영인 코사인 벡터거리 2, 유사도 0정의되지 않은 거리 또는 NaN 네이티브 거리가 생성되면 제외됩니다.
Float32 제곱 노름의 오버플로 또는 언더플로를 포함하여, 코사인 쿼리 노름이 영이거나 유한하지 않은 경우스칼라 동작자동 재작성을 건너뜁니다.
Hamming 입력각 원소가 [0,255] 범위에 있는 정수 배열물리적 UInt8 벡터와 유효한 바이트 쿼리.

네이티브 코사인 연산과 인덱스 추정치는 스칼라 결과와 다를 수 있습니다. Hamming은 서로 다른 바이트의 개수가 아니라 서로 다른 비트의 개수를 셉니다. hamming_distance([0,255,170], [255,0,85])는 24를 반환합니다. Hamming은 암시적 캐스트 전에 부동 소수점, decimal, LARGEINT 배열을 거부하고, 바이트 변환 전에 범위를 벗어난 값도 거부합니다. 정확한 함수는 계속 스칼라 실행에 사용할 수 있습니다.

WHERE 사전 필터와 JOIN 경계​

Lance 테이블에서 완전히 푸시다운할 수 있는 조건은 검색 전에 동일한 고정 스냅샷을 대상으로 평가됩니다. Doris는 먼저 행 ID만 읽는 스캔을 시도하고, 완성된 허용 목록을 해당 쿼리의 검색 리더들이 공유합니다. 목록이 비어 있으면 행을 선택하지 않습니다. ID가 65,536개를 초과하면 Doris는 부분 목록을 버리고 완전한 네이티브 조건 사전 필터를 유지합니다. 임시 목록과 캐시된 목록의 용량은 모두 쿼리 메모리 제한에 포함되며, 중단되거나 실패한 수집 결과는 캐시하지 않습니다. 이는 일반 스캔에서 JOIN Runtime Filter를 푸시다운하는 것과 별개입니다.

이 ACL 예제에서는 internal.auth.allowed_items에 item_id와 principal_id가 있다고 가정합니다.

SELECT d.id,
l2_distance_approximate(d.embedding, [0.1,0.2,0.3,0.4]) AS distance
FROM lance_catalog.default.items d
WHERE EXISTS (
SELECT 1 FROM internal.auth.allowed_items a
WHERE a.item_id = d.id AND a.principal_id = 42
)
ORDER BY distance ASC
LIMIT 10;

이 경우 조인, 스칼라 거리 평가, 전역 Top-N이 유지됩니다. 재작성은 ACL 세미 조인을 가로지르거나 비즈니스 ID를 네이티브 Lance _rowid에 매핑하지 않습니다. ACL 필터링 전에 필터 없는 Top-K를 선택하면 권한이 있는 행 중 가장 가까운 행을 놓칠 수 있습니다. 보조 정렬 키를 추가해도 자동 재작성이 적용되지 않습니다.

해당 쿼리에 EXPLAIN을 사용하여 externalSearchType=VECTOR와 lanceMetric=l2|dot|cosine|hamming, 또는 externalSearchType=FULL_TEXT를 확인하세요. LanceRowIdPrefilterTime, LanceRowIdPrefilterRows, LanceRowIdPrefilterCacheHits, LanceRowIdPrefilterFallbacks는 Doris 허용 목록 처리 작업을 보고하며, 아래의 네이티브 행 ID 로더 메트릭과는 다릅니다. 이 규칙의 적용 범위를 벗어나지만 지원되는 쿼리 형태나 검색 튜닝에는 명시적 TVF를 사용하세요. 재작성된 검색도 자체 적용 조건을 충족하면 기존의 두 단계 출력 읽기를 사용할 수 있습니다.

1. 후보 필터를 검색 내부에 배치하기​

정수형 id, 문자열 category, 문자열 content, 네 차원의 벡터 embedding을 포함하는 테이블이 구성되어 있다면, 후보 생성에 영향을 주어야 하는 조건은 TVF의 filter 매개변수에 지정하세요. 벡터 인덱스는 차원과 거리 측정 방식이 일치해야 하며, 텍스트 열에는 커밋된 FTS 인덱스가 있어야 합니다.

SELECT id, _distance
FROM vector_search(
"table" = "lance_catalog.default.items",
"column" = "embedding",
"query_vector" = "[0.1,0.2,0.3,0.4]",
"metric" = "l2",
"top_k" = "20",
"filter" = "category = 'book'",
"use_index" = "true"
)
ORDER BY _distance ASC, id;

이 쿼리는 조건에 맞는 행 중에서 최대 20개의 이웃을 검색합니다. 근사 검색이므로 20개 행을 반환했다고 해서 재현율이 입증되는 것은 아닙니다. 필터링된 전문 검색은 다음과 같이 실행합니다.

SELECT id, _score
FROM full_text_search(
"table" = "lance_catalog.default.items",
"column" = "content",
"query" = "storage engine",
"query_type" = "match",
"operator" = "and",
"top_k" = "20",
"filter" = "category = 'book'",
"coverage_mode" = "strict"
)
ORDER BY _score DESC, id;

이 쿼리는 일치하는 문서를 BM25 순위에 따라 최대 20개 반환합니다. 외부의 WHERE category = 'book'은 의미가 다릅니다. Doris는 Lance가 이미 생성한 후보를 전역 TopN 전에 필터링합니다. 제거된 후보는 자동으로 보충되지 않습니다. EXPLAIN에 해당 조건이 Doris Scan 연산자 내부에 표시되더라도, 검색 후 적용하려는 조건만 TVF 외부에 두세요.

2. 병렬 작업의 단위 이해하기​

Query snapshot
-> FE plans physical search-index segment splits
-> Each split: scalar Prefilter -> vector or full-text candidates
-> Doris residual filtering -> local TopN -> global TopN
-> Optional deferred fetch of output columns
쿼리 경로분할 기준영향
인덱스를 사용하는 벡터 검색선택된 물리 벡터 인덱스 세그먼트. 인덱스로 커버되지 않는 프래그먼트에는 Flat Search 분할이 할당됨스칼라 인덱스 세그먼트가 벡터 쿼리의 분할 수를 독립적으로 결정하지는 않습니다. 인덱스가 없는 데이터가 지연 시간의 대부분을 차지할 수 있습니다.
전문 검색선택된 물리 FTS 인덱스 세그먼트기본 strict 커버리지 모드는 인덱스로 커버되지 않는 프래그먼트가 있으면 쿼리를 거부합니다. index_only는 해당 프래그먼트를 제외합니다. 전문 검색에는 Flat Search 폴백이 없습니다.
일반 스칼라 스캔자체 스캔 계획. 스칼라 인덱스 세그먼트나 프래그먼트 그룹을 사용할 수 있음스칼라 전용 쿼리 계획으로 필터링된 벡터 쿼리의 병렬성을 추정하지 마세요.

lance_fragments_per_split은 일반 프래그먼트 스캔의 그룹 구성을 제어하며, 물리 벡터 인덱스 세그먼트 수를 제어하지는 않습니다. 파이프라인 인스턴스나 스캐너 동시성을 늘려도 플래너가 생성한 수보다 많은 벡터 세그먼트 분할을 만들 수는 없습니다. Lance 스캐너는 내부적으로도 작업을 병렬 실행할 수 있으므로, Doris 스캐너 하나가 CPU 스레드 하나와 같지는 않습니다.

검색 분할이 S개이면 각 분할은 최대 top_k + offset개의 후보를 반환할 수 있습니다. 따라서 잔여 필터링과 전역 TopN을 적용하기 전, 병합 전 후보 수의 상한은 S * (top_k + offset)입니다. 세그먼트를 늘리면 작업 분산이 개선될 수 있지만, 세그먼트별 초기 설정, 필터링, 후보 병합, 동시 메모리 수요도 증가합니다.

3. 세그먼트 개수가 아닌 프래그먼트 커버리지 맞추기​

프래그먼트는 데이터 단위입니다. 인덱스 세그먼트는 프래그먼트 집합을 커버하는 독립적인 인덱스입니다. 논리 인덱스는 하나의 인덱스 이름 아래 물리 세그먼트를 묶습니다. IVF 파티션, BTree 페이지, FTS 포스팅 블록은 인덱스 내부 단위이며, 이들 중 어느 것도 Doris 검색 분할을 정의하지 않습니다.

다음 예제는 데이터 프래그먼트 여덟 개를 사용합니다.

레이아웃벡터 커버리지스칼라 커버리지프래그먼트 범위 스칼라 리더에서의 필터링 영향
정렬됨V0={0,1,2,3}, V1={4,5,6,7}B0={0,1,2,3}, B1={4,5,6,7}각 벡터 분할에는 대응하는 스칼라 세그먼트만 필요합니다.
스칼라 세그먼트가 더 세밀함위와 동일프래그먼트마다 하나씩, 총 여덟 개의 세그먼트각 분할은 스칼라 세그먼트 네 개를 선택하며, 어느 세그먼트도 벡터 경계를 가로지르지 않습니다.
스칼라 세그먼트가 더 큼위와 동일B0={0,1,2,3,4,5,6,7}두 벡터 분할이 동일한 스칼라 세그먼트를 조회할 수 있습니다.
개수는 같지만 경계를 가로지름위와 동일B0={0,1,4,5}, B1={2,3,6,7}두 스칼라 세그먼트 모두 두 벡터 분할과 겹칩니다. 개수가 같다고 해서 정렬되는 것은 아닙니다.

프래그먼트 범위 프루닝은 겹치는 부분이 없는 스칼라 세그먼트를 건너뜁니다. 일부만 겹치더라도 세그먼트는 유지됩니다. 조건식을 평가하기 전에 더 작은 BTree를 자동으로 다시 구축하거나 모든 포스팅 리스트를 잘라내지는 않습니다. 반복 쿼리는 캐시된 인덱스 내용을 재사용할 수 있지만, 검색과 행 ID 집합 구성은 여전히 반복할 수 있습니다. BTree 조회가 유지된 세그먼트의 모든 페이지를 반드시 스캔하는 것은 아닙니다. 비용은 조건식과 일치 항목에 따라 달라집니다.

important

이 이점은 실제 리더 경로에 따라 달라집니다. 프래그먼트 범위 스칼라 로딩을 사용하는 Lance의 V2 필터링 읽기 경로에서는 검색 전에 관련 없는 스칼라 세그먼트를 제외할 수 있습니다. 레거시 V1 데이터 파일 경로는 논리 스칼라 인덱스를 더 넓은 범위에서 평가한 뒤 행 ID를 제한할 수 있습니다. 여기서 V1/V2는 Doris 릴리스나 벡터 인덱스 유형이 아니라 데이터 파일 형식을 가리킵니다. 모든 리더 버전이나 FTS 경로에서 동일한 프루닝이 적용된다고 가정하지 마세요. 각 쿼리 유형의 실행 계획과 프로파일을 확인하세요.

자주 함께 사용하는 열은 벡터, 스칼라, FTS 인덱스를 구축할 때 동일한 프래그먼트 그룹을 사용하세요. 각 스칼라 세그먼트가 자신이 지원하는 검색 세그먼트 안에 포함된다면 더 세밀하게 나누어도 됩니다. 벡터 세그먼트와 FTS 세그먼트에 서로 다른 크기가 필요하다면, 공통 기본 그룹을 더 큰 그룹으로 묶는 방식을 고려하고 스칼라 세그먼트가 양쪽 경계 안에 모두 포함되도록 하세요. 이는 잠재적인 중첩을 줄이기 위한 레이아웃 정책이며, 모든 실행 경로가 이를 활용한다는 보장은 아닙니다.

초기 세분화 수준 선택하기​

세그먼트당 최적의 행 수나 바이트 크기는 모든 경우에 공통으로 적용되지 않습니다. 워크로드를 처리할 수 있는 BE를 기준으로 시작한 다음, 재현율과 목표 동시성을 고정한 상태에서 BE당 균형 잡힌 검색 세그먼트 하나를 두는 구성과 BE당 몇 개의 세그먼트를 두는 구성을 비교하세요. 이는 실험의 출발점이며, 반드시 따라야 하는 비율은 아닙니다.

워크로드구축 시 우선순위측정할 주요 절충점
지연 시간에 민감한 필터링 벡터 검색균형 잡힌 벡터 그룹과 그 안에 포함되는 스칼라 커버리지가장 느린 분할의 지연 시간과 반복 필터링 및 후보 확산 간의 관계
동시성이 높은 검색쿼리별 분할 확산이 과도해지지 않도록 하고 재사용 가능한 파티션 유지QPS, P95 지연 시간, CPU 큐, 최대 메모리를 함께 측정
범위가 넓은 스칼라 필터 또는 흔한 FTS 검색어그룹의 균형을 맞출 때 일치 항목 수, 텍스트 길이, 포스팅 크기 고려행 수가 같아도 필터링/검색 비용은 다를 수 있음
빈번한 데이터 추가동일한 새 프래그먼트 그룹에 대해 관련 인덱스를 모두 구축새 데이터 커버리지와 작은 세그먼트 누적 간의 관계
개별 프래그먼트가 매우 큼인덱스 구축 전에 데이터 파일 크기 계획프래그먼트 기반 커버리지로는 하나의 프래그먼트를 서로 겹치지 않는 여러 커버리지 그룹으로 나눌 수 없음

행 수뿐 아니라 인코딩된 벡터/그래프 크기, 스칼라 값 분포, 텍스트/포스팅 크기도 측정하세요. 각 벡터 세그먼트에 해당 IVF/PQ 구성에 필요한 학습 데이터를 충분히 확보하세요. nlist(IVF 파티션 수)와 물리 세그먼트 수는 별도로 선택하는 항목입니다. 하나를 변경해도 다른 하나가 자동으로 조정되지는 않습니다.

4. 하나의 그룹 구성 계획으로 관련 인덱스 구축하기​

Doris에는 열 간 인덱스를 자동으로 정렬하는 매개변수가 없습니다. 호환되는 SDK에서 지원하는 경우 Lance 분산 구축 API를 사용하세요. 다음은 오케스트레이션 패턴이며, 바로 실행할 수 있는 데이터셋 생성기나 벤치마크 구성이 아닙니다.

# ds is an existing LanceDataset at the chosen build snapshot.
# groups contains non-empty, disjoint lists of actual fragment IDs.
# vector_params and fts_params are validated for the schema and SDK version.
# Keep writes and compaction paused for this simple coordinator example.
visible = {fragment.fragment_id for fragment in ds.get_fragments()}
assigned = [fragment_id for group in groups for fragment_id in group]
assert groups and all(groups)
assert len(assigned) == len(set(assigned))
assert set(assigned) == visible

specs = [
("embedding_idx", "embedding", "IVF_PQ", vector_params),
("category_idx", "category", "BTREE", {}),
("content_idx", "content", "INVERTED", fts_params),
]
segments = {name: [] for name, _, _, _ in specs}
for fragment_ids in groups:
for name, column, index_type, params in specs:
segment = ds.create_index_uncommitted(
column,
index_type,
name=name,
fragment_ids=fragment_ids,
**params,
)
segments[name].append(segment)

# These are three separate commits, not an atomic multi-index publication.
for name, column, _, _ in specs:
ds.commit_existing_index_segments(name, column, segments[name])

다음과 같이 입력을 준비하고 검증하세요.

  1. 구축 계획을 확정합니다. 하나의 스냅샷에서 실제 프래그먼트 ID를 나열합니다. ID가 연속적일 필요는 없습니다. 행 수와 예상 인덱스 바이트 크기를 기준으로 그룹의 균형을 맞춥니다. 모든 관련 열에 정확히 같은 그룹을 재사용합니다. 모든 분산 워커는 동일한 구축 버전을 열어야 합니다.
  2. 호환되는 구성을 선택합니다. IVF_PQ에는 벡터 차원과 샘플 크기에 적합한 거리 측정 방식, num_partitions, num_sub_vectors를 지정합니다. 하나의 논리 인덱스 안에서는 인덱스 구성을 일관되게 유지합니다. FTS에서는 분석기/토크나이저 설정을 일관되게 유지하고, 구문 검색이 필요하면 with_position을 활성화합니다.
  3. 벡터 모델 전략을 선택합니다. 지원되는 다중 세그먼트 구축 방식은 세그먼트별로 독립적인 학습을 수행할 수 있습니다. 이후 물리 병합에 공유 모델이 필요하다면 워커에 동일한 IVF 중심점과 PQ 코드북을 제공합니다. 독립적으로 학습된 세그먼트를 무작정 병합하거나 HNSW 그래프가 병합 후에도 유지된다고 가정하지 마세요. SDK가 지원하는 워크플로를 확인하세요.
  4. 먼저 구축한 뒤 게시합니다. 각 구축 작업은 고유 UUID를 가진 물리 세그먼트를 반환합니다. 성공한 결과를 수집하고, 각 논리 인덱스를 커밋하기 전에 커버리지와 호환성을 검증합니다. 서로 다른 열이나 워커 출력에 동일한 물리 UUID를 공유해서 할당하지 마세요. 이 예제는 새로운 논리 인덱스 이름을 사용한다고 가정합니다. 교체, 재시도, 실패 시 정리에는 별도의 코디네이터 정책이 필요합니다.
  5. 생성된 매니페스트를 검증합니다. 각 인덱스에서 실제 세그먼트별 프래그먼트 커버리지를 계획한 그룹과 비교하고, 누락되거나 겹치는 커버리지가 있는지 확인합니다. 세그먼트 수가 일치하는 것만으로는 충분하지 않습니다. Lance 인덱스 메타데이터를 사용하세요. Filesystem Catalog 테이블에 대한 Doris의 SHOW INDEX는 논리 인덱스 이름, 유형, 필드를 확인하는 데 도움이 되지만, 이 커버리지 검증을 대체하지는 않습니다.

공유 그룹 구성 계획을 사용한다고 해서 모든 워커 출력을 하나의 세그먼트로 병합해야 하는 것은 아닙니다. 그렇게 병합하면 유지하려던 경계가 사라집니다. 구축/커밋 API와 지원되는 병합 워크플로는 Lance 분산 인덱싱을 참고하세요.

유지 관리 중 정렬 유지하기​

  • 동일한 새 프래그먼트 그룹을 사용해 관련 벡터, 스칼라, FTS 세그먼트를 추가하세요. 게시할 때마다 쿼리 스냅샷의 커버리지를 확인하세요. FTS의 strict 모드에서는 데이터 추가 후 인덱스 게시가 완료되기 전까지 쿼리가 거부될 수 있습니다.
  • 컴팩션, 재작성, 인덱스 최적화를 레이아웃 변경으로 취급하세요. 작업과 리더 버전에 따라 인덱스가 다시 매핑되거나 재구축될 수 있습니다. 기존 경계가 유지된다고 가정하지 말고 결과 커버리지를 확인하세요.
  • 하나의 레이아웃 계획에 따라 작은 그룹을 합치세요. 혼합 쿼리 성능이 정렬에 좌우된다면 각 인덱스를 독립적으로 최적화해 서로 무관한 그룹으로 만들지 마세요.
  • 온라인 유지 관리에는 스냅샷/충돌 처리와 복구 기능을 갖춘 코디네이터를 사용하세요. 인덱스별 개별 커밋으로는 모든 열을 원자적으로 전환할 수 없습니다. TVF는 최신 main 스냅샷을 읽으며, table 인수로 과거 버전이나 태그를 선택할 수는 없습니다.

논리 인덱스와 업스트림 계획​

Lance의 인덱스 형식은 논리 인덱스와 물리 세그먼트를 정의합니다. 분산 인덱스 추적 이슈는 인덱스 유형 간 공유 세그먼트 수명 주기를 추적합니다. 이 모델은 서로 다른 열에 동일한 프래그먼트 경계를 강제하지 않습니다. 열 간 그룹 구성과 유지 관리는 여전히 오케스트레이션의 책임입니다. 업스트림 로드맵을 Doris의 자동 정렬 기능으로 간주하지 마세요.

5. 필터와 메모리를 함께 튜닝하기​

관찰된 현상다음 실험정확성 또는 리소스 제약
필터의 선택도가 매우 높고 벡터 결과가 너무 적거나 재현율이 낮음필터 위치와 인덱스 커버리지를 확인하고, 크기를 제한한 데이터셋에서 탐색 설정과 정확 검색 기준선을 비교지연 시간을 줄이려고 탐색량을 줄이면 재현율이 낮아질 수 있습니다. 탐색량을 늘려도 필터를 통과하지 못하는 행을 만들어낼 수는 없습니다.
범위가 넓은 필터가 큰 Prefilter 집합을 생성함정렬된 레이아웃과 스칼라 조건식의 실제 선택도를 비교인덱스를 사용해도 일치하는 많은 행 ID를 구체화하는 비용은 사라지지 않습니다.
벡터 분할 수가 늘면서 스칼라 작업이 반복됨세그먼트 커버리지의 교집합과 리더 경로를 비교캐시가 웜 상태여도 분할 수가 늘면 필터링 작업이 증가할 수 있습니다.
콜드 쿼리가 느림대표적인 벡터와 검색어로 콜드 프로파일과 웜 프로파일을 비교쿼리 하나에 대한 워밍업으로는 운영 환경의 작업 집합 전체를 커버할 수 없습니다.
웜 상태의 혼합 트래픽이 느려짐목표 동시성에서 공유 캐시 미스와 CPU 큐를 측정스칼라, 벡터, FTS 인덱스 내용은 BE에서 동일한 세션 인덱스 캐시 예산을 놓고 경쟁할 수 있습니다.
큰 top_k, 깊은 offset 또는 폭이 넓은 출력쿼리 의미가 허용하는 범위에서 요청 후보 수를 줄이고, 지연 가져오기를 점검후보 힙, 행 ID 마스크, 정밀화, 가져오기 버퍼는 추가 쿼리 메모리를 사용합니다.

재사용 가능한 인덱스 작업 집합의 전체 크기를 산정하고, 동시 쿼리 메모리를 위한 여유를 남겨두세요. 캐시 용량은 프로세스 메모리 한도가 아니며, 메타데이터 캐시를 늘려도 인덱스 캐시가 커지지는 않습니다. 인덱스별 페이로드 계산식, HNSW 그래프 비용, 캐시 범위, Doris BE 구성 예제는 Lance 쿼리 모범 사례를 참고하세요.

6. 실행 계획과 프로파일로 검증하기​

동일한 쿼리 집합, 데이터셋 스냅샷, 인덱스 유형/구성, top_k, 재현율 목표를 기준으로 비교하세요. 한 번에 하나의 레이아웃이나 매개변수만 변경하세요. 선택도가 높은 필터와 범위가 넓은 필터, 다양한 벡터/검색어, 콜드 실행과 웜 실행, 목표 동시성을 모두 포함하세요. P50/P95, 처리량, 결과 수, 재현율, BE별 최대 메모리를 기록하세요.

단계확인할 정보해석
FE 메타데이터 및 계획Execution Summary → Lance Metadata Metrics: MetadataReadTime, IndexMetadataReadTime, SplitPlanningTime 및 해당 CallsCatalog 메타데이터 접근과 분할 계획을 BE 검색과 구분합니다. 전체 메타데이터 시간에는 하위 단계 시간이 포함됩니다.
계획 및 커버리지EXPLAIN: lanceSearchIndexSegments, lanceSearchIndexFragments, lanceSearchUnindexedFragments; LanceSearchPlan: LancePlannedIndexSegmentCount, LancePlannedIndexedFragmentCount, LancePlannedFlatSearchFragmentCount이 값은 완료된 검색이 아니라 계획되거나 할당된 검색 작업을 나타냅니다. Flat Search 수치는 벡터 쿼리에 적용되며 FTS에는 적용되지 않습니다.
리더 설정 및 배치 읽기LanceTiming: LanceDatasetOpenTime, LanceScannerConfigureTime, LanceScannerNextTime, LanceArrowToDorisBlockTime열기/구성, Lance 읽기, Doris 변환을 구분합니다. 배치 읽기 시간에는 내부 검색, I/O, 디코딩, 대기가 포함됩니다.
행 ID PrefilterLanceScan: RowIdPrefilterLoads, RowIdPrefilterInputRows, RowIdPrefilterInputBatches, RowIdPrefilterIds, RowIdPrefilterLoadTime, RowIdPrefilterInputTime, RowIdPrefilterBuildTime입력 대기/읽기와 마스크 구성은 전체 로딩 시간의 일부입니다. 이 값은 행 ID 로더를 나타내며, 모든 스칼라 조건식이나 최종 후보 집합을 나타내지는 않습니다.
스칼라 세그먼트 경로LanceScalarIndex: LanceScalarIndexSegmentsRequested, LanceScalarIndexSegmentsSearched, LanceScalarIndexSegmentFallbacks, LanceScalarIndexCandidateRows, LanceScalarIndexCandidateEvaluationTime, LanceScalarIndexSegmentSearchTime후보 평가 시간에는 세그먼트 검색 시간이 포함됩니다. 후보 수는 프래그먼트 제한/전체 필터링 전의 값입니다. 반복 작업은 후보를 다시 집계할 수 있으며, 카디널리티를 얻을 수 없으면 값이 영으로 남을 수 있습니다.
인덱스 재사용 및 I/OLanceIndex: LanceIndexCacheHits, LanceIndexCacheMisses, LanceIndexObjectLoads, LanceIndexComponentsLoaded, LanceIndexComparisons; LanceIO: LanceIOReadBytes, LanceIOReadOps, LanceIOReadRequests객체/컴포넌트 로딩, 캐시 조회, 검색 작업을 구분합니다. 로딩 횟수는 미스 횟수가 아니며, 실행 I/O는 전체 원격 트래픽 합계가 아닙니다.
벡터 파티션 선택 및 검색LanceIVFPartitionsConsidered, LanceIVFPartitionSelectionTime, LanceIndexPartitionsSearched, LanceVectorIndexDeltasSearched, LanceIndexPartitionLoadTime, LanceIndexSearchTime, LanceIndexDistanceTopKTime파티션 선택과 실제 검색을 구분합니다. 거리/Top-K 시간에는 필터링과 힙 작업이 포함되며, 검색 시간과 겹칩니다. FTS도 검색한 파티션 수를 보고합니다.
대기 및 동시성LanceIndexPrefilterWaitTime, LanceIndexCpuQueueWaitTime, LanceScanTaskWaitTime인덱스 Prefilter 대기, CPU 작업 큐 대기, FilteredRead 작업 대기를 구분합니다. 어느 것도 전체 I/O 대기 시간이나 Doris 스케줄링 시간의 합계는 아닙니다.
출력 가져오기Row ID Fetch/Materialization 정보: LanceRowIdFetchCalls, LanceRowIdFetchRows, LanceRowIdFetchTotalTime, LanceRowIdTakeReadTime, LanceArrowToDorisBlockTime 및 데이터 캐시 바이트 카운터가져오기 합계에는 읽기/변환 하위 단계가 포함되며 별도의 통계 경로를 사용합니다. 내보낸 동일한 값을 독립적인 작업으로 간주해 다시 합산하지 마세요.
종단 간 지연 시간가장 느린 Scanner, 의존성 대기, FE 대기/가져오기/쓰기, 클라이언트 경과 시간서로 겹치는 연산자 시간이나 병렬로 실행되는 모든 Scanner 시간을 합산해 쿼리 지연 시간으로 계산하지 마세요.

메트릭 정의와 범위는 Lance 쿼리 프로파일 참조를, Dataset 데이터 캐시 바이트와 BE 전체의 Session 메트릭은 캐시 효과 확인을 참고하세요. 쿼리 수준의 인덱스 캐시 카운터는 Session 합계의 차이로 계산한 값이 아닙니다.

프로파일의 상세 수준은 실행 중인 FE/BE와 함께 제공되는 Lance 계측 기능에 따라 달라집니다. Scanner 메트릭은 미리 등록되므로, 값이 영이면 네이티브 경로가 해당 메트릭을 보고하지 않았을 수 있습니다. 실행 콜백에도 완료된 스트림이 필요하므로, 실패하거나 취소되거나 조기에 종료된 스캔은 집계가 불완전할 수 있습니다. 값이 누락되었거나 영이라는 사실만으로 인덱스나 필터가 사용되지 않았다고 단정할 수는 없습니다. LanceScannerNextTime은 내부 검색 및 대기 타이머와 겹치므로, 값이 크다고 해서 스토리지 병목이 입증되는 것은 아닙니다. 해당 메트릭으로 읽기 구간을 설명할 수 없다면, 나머지 시간을 스토리지에 귀속하기 전에 CPU/I/O 트레이스를 수집하세요.

필터가 없는 벡터 쿼리에서는 선택된 인덱스 세그먼트 전체가 스캔의 프래그먼트 범위 안에 있을 때, 세그먼트 범위 최적화를 지원하는 리더가 모든 행 ID를 나열하는 작업을 피할 수 있습니다. 실제 스칼라 조건식은 여전히 평가해야 하며, 삭제 및 가시성 처리도 남아 있을 수 있습니다. 정렬은 불필요한 작업을 줄여 주지만, 필터링된 검색에서 Prefilter 비용이 전혀 들지 않는다고 보장하지는 않습니다.