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

Lance Catalog

노트

이 기능은 실험적 기능입니다.

Lance Catalog는 Apache Doris 4.1.4부터 지원됩니다.

Lance는 분석 및 AI 워크로드를 위해 설계된 열 지향 데이터 형식입니다. Doris는 Lance Catalog를 사용해 Lance Namespace의 데이터베이스와 테이블을 검색하고, 로컬 파일 시스템, S3 호환 객체 스토리지 또는 Alibaba Cloud OSS에 저장된 Lance 데이터셋을 직접 쿼리할 수 있습니다.

현재 Doris는 Lance에 대한 읽기 전용 액세스를 제공합니다. Lance 테이블 생성, 쓰기, 업데이트, 삭제는 지원하지 않습니다.

사용 사례​

사용 사례설명
Lance 데이터 직접 쿼리데이터를 먼저 마이그레이션하거나 가져오지 않고 Doris SQL로 기존 Lance 데이터셋을 분석합니다.
AI 벡터 검색Lance에 이미 있는 벡터 인덱스를 재사용하고 vector_search()로 검색한 뒤, 스칼라 필터링 및 Doris SQL 분석과 결합합니다.
전문 검색Lance에 이미 있는 FTS 역색인 인덱스를 재사용하고 full_text_search()로 BM25 검색을 수행한 뒤, 스칼라 필터링 및 Doris SQL 분석과 결합합니다.
데이터 통합추가 처리, 조인 또는 장기 저장을 위해 Lance 데이터를 Doris 내부 테이블로 읽어 들입니다.
Namespace 관리간단한 디렉터리 기반 구조에는 Filesystem Catalog를 사용합니다. Namespace, 테이블 위치 또는 임시 스토리지 자격 증명을 중앙에서 관리해야 한다면 REST Catalog를 사용합니다.

기능 개요​

기능지원 내용
Filesystem Catalog로컬 파일 시스템, file://, s3:// 또는 oss://의 warehouse 지원
REST Catalog인증 없음, Bearer Token, API Key 또는 사용자 지정 HTTP 헤더를 사용하는 Lance REST Namespace 지원
메타데이터 액세스SHOW DATABASES, SHOW TABLES, DESC, SHOW INDEX, lance_index_entries TVF 지원. 마지막 두 기능은 Filesystem Catalog에서만 지원
시스템 테이블(table$...)지원하지 않음. 인덱스 메타데이터에는 SHOW INDEX와 lance_index_entries TVF 사용
데이터 쿼리열 프루닝, 병렬 Lance Fragment 스캔, 현재 버전의 스냅샷 일관 읽기 지원
조건자 푸시다운호환되는 정적 스칼라 조건자와 조인 Runtime Filter를 Lance로 푸시다운
파일 TVFs3() 및 local()을 통한 Lance 데이터셋 직접 쿼리 지원
벡터 검색단일 벡터 및 다중 벡터 열을 지원하고, 물리적 Lance 인덱스 세그먼트를 병렬 split으로 사용하며, 인덱스가 적용되지 않은 Fragment는 Flat Search split으로 검색하고, Doris에서 전역 Top-K 병합 수행
전문 검색full_text_search()를 통해 기존 Lance FTS 역색인 인덱스를 사용하고, Match OR/AND 및 Phrase 쿼리를 지원하며, Doris에서 전역 Top-K 병합 수행
Lance Cache각 BE 내에서 인덱스 및 메타데이터 캐시와 로컬 디스크 데이터 캐시 공유
2단계 TopN 읽기벡터 및 전문 검색에서 적합한 출력 열에 기본적으로 활성화되며 enable_lance_lazy_materialization으로 독립적으로 제어
Lance 쓰기지원하지 않음
Time TravelREST Namespace에서 버전을 관리하는 테이블을 포함해 FOR VERSION AS OF, FOR TIME AS OF, @tag(...), @branch(...) 지원
Hybrid Search지원하지 않음

Lance 버전 및 호환성​

Doris BE 데이터 리더는 lance-c v0.1.9에 Doris 패치를 적용해 빌드합니다. 이 패치는 다중 벡터 검색을 지원하고 내장 Lance Rust 크레이트를 11.0.0(커밋 ab6b5bbe)으로 업그레이드합니다. Doris FE는 org.lance:lance-core:12.0.0으로 Namespace 및 데이터셋 메타데이터를 읽습니다. FE는 메타데이터만 읽고 데이터셋에는 쓰지 않으므로, FE의 Lance 릴리스가 더 최신이어도 BE가 읽을 수 있는 범위는 달라지지 않습니다. 데이터셋은 FE의 메타데이터 로드와 BE의 데이터 읽기 양쪽 모두와 호환되어야 합니다. 이러한 구현 버전은 데이터셋에 기록된 Lance data_storage_version과 다릅니다.

다음 표는 이 리더의 파일 형식 호환성을 설명합니다.

data_storage_version읽기 지원비고
0.1 / legacy지원최초 Lance 파일 형식.
2.0(쓰기 옵션 별칭 0.3)지원Lance v2 파일 형식의 초기 버전.
2.1 / stable지원, 기본 안정 형식내장 Lance 버전에서 stable 쓰기 옵션과 새 데이터셋의 기본 형식은 모두 2.1로 결정됩니다.
2.2지원내장 Lance 버전에서는 안정 형식으로 취급하지만 기본 쓰기 형식은 아닙니다.
2.3 / next실험적, 보장되지 않음내장 Lance 버전은 2.3을 불안정 형식으로 표시하며 next 쓰기 옵션은 2.3으로 결정됩니다.
이후 버전 또는 알 수 없는 버전지원하지 않음데이터셋을 열거나 스캔할 때 지원되지 않는 스토리지 버전 오류가 발생할 수 있습니다.

Lance SDK 릴리스 번호와 파일 형식 버전은 서로 독립적입니다. 이전 또는 이후 Lance SDK로 작성한 데이터셋이라도, 스토리지 형식, 필수 테이블 기능 플래그, 인덱스 형식, Arrow/Lance 데이터 유형을 Doris 내장 버전이 모두 이해할 때만 읽을 수 있습니다. 따라서:

  • Doris는 아래에 설명된 유형 제한을 전제로 0.1, 2.0, 2.1, 2.2 스토리지 형식으로 작성된 데이터셋을 읽을 수 있을 것으로 예상됩니다.
  • 상위 버전과의 호환성은 보장되지 않습니다. 이후 Lance 릴리스로 작성하거나 수정한 데이터셋이 새로운 스토리지 형식, 알 수 없는 필수 manifest 기능, 새로운 인덱스 형식 또는 지원되지 않는 확장 유형을 사용하면 읽지 못할 수 있습니다.
  • 이 Doris 릴리스에서 계속 읽어야 하는 데이터셋에는 현재 기본 안정 형식인 2.1을 사용하고 next는 사용하지 마세요. 새로운 writer나 선택적 Lance 기능을 도입한다면 운영에 사용하기 전에 대상 Doris 릴리스로 생성된 데이터셋을 검증하세요.

Catalog 설정​

구문​

CREATE CATALOG [IF NOT EXISTS] catalog_name PROPERTIES (
"type" = "lance",
"lance.catalog.type" = "<filesystem|rest>",
{CatalogProperties},
{StorageProperties},
{CommonProperties}
);

공통 속성​

속성필수기본값설명
type예-lance여야 합니다.
lance.catalog.type아니요filesystemCatalog 유형. 유효한 값은 filesystem과 rest입니다.
lance.namespace.parent아니요비어 있음지정한 Lance Namespace와 하위 Namespace로 액세스를 제한합니다. 예를 들어 기본 구분자를 사용하면 production$analytics는 두 수준의 Namespace를 나타냅니다.
lance.namespace.delimiter아니요$lance.namespace.parent를 파싱하는 데 사용하는 구분자입니다. REST Namespace 클라이언트에도 전달됩니다. 이 속성은 Doris에서 다단계 Namespace를 표시하는 방식은 변경하지 않습니다.
lance.namespace.root_database아니요default루트 Lance Namespace에 매핑할 Doris 데이터베이스 이름입니다.
lance.table_access_cache_ttl_seconds아니요60음수가 아닌 정수입니다. FE 테이블 액세스 캐시의 최대 수명(초)이며, 0은 캐시를 비활성화합니다. Filesystem 및 REST Catalog에 적용됩니다. Namespace 관리 버전이 있는 테이블과 제공된 스토리지 옵션 또는 자격 증명이 포함된 URI를 가진 결과는 캐시하지 않습니다. FE 테이블 액세스 캐시를 참조하세요.

Filesystem Catalog​

Filesystem Catalog는 warehouse 디렉터리에서 Lance Namespace와 테이블을 직접 검색합니다.

속성필수설명
warehouse예Lance warehouse의 루트 경로입니다. 로컬 절대 경로, file:// URI, s3:// URI, oss:// URI를 지원합니다.

다음 두 속성은 용도가 다릅니다.

  • fs.s3.support=true는 S3 설정 파싱을 명시적으로 선택합니다. AWS S3는 보통 속성으로 감지할 수 있습니다. MinIO, 사용자 지정 endpoint 또는 S3 호환 API를 통해 액세스하는 OSS에는 이 값을 명시적으로 설정하여 감지 실패나 도메인에 따른 다른 스토리지 유형 선택을 방지하세요. 아래의 모든 S3 예제에는 바로 사용할 수 있도록 이 속성을 포함했습니다.
  • use_path_style은 요청 URL에서 버킷이 들어가는 위치를 제어하며 기본값은 false입니다. true로 설정하면 endpoint/bucket/object-path를 사용하고, false로 설정하면 가상 호스트 방식 액세스를 사용합니다. 현재 Lance의 기본 S3 클라이언트에서 명시적으로 설정한 가상 호스트 방식 endpoint에는 버킷이 포함되어야 합니다. 서비스가 지원하는 액세스 방식에 맞춰 이 속성과 endpoint를 함께 설정하세요.
액세스 시나리오스토리지 유형 설정use_path_styleEndpoint 형식
AWS S3권장: fs.s3.support=truefalse, 생략 가능https://my-bucket.s3.us-east-1.amazonaws.com처럼 버킷을 포함합니다.
MinIO(서비스 도메인 또는 IP를 통한 액세스)fs.s3.support=true 설정true를 명시적으로 설정http://minio.example.com:9000처럼 버킷이 없는 서비스 주소입니다.
OSS S3 호환 APIfs.s3.support=true 설정false, 예제에서 명시적으로 설정https://my-bucket.oss-cn-beijing.aliyuncs.com처럼 버킷을 포함합니다.
네이티브 OSS API권장: fs.oss.support=true, fs.s3.support는 설정하지 않음일반 use_path_style을 설정할 필요 없음https://oss-cn-beijing.aliyuncs.com처럼 버킷 없이 oss.endpoint를 사용합니다.

이 S3 endpoint 규칙은 Lance의 기본 S3 액세스 경로에 적용됩니다. 하나의 스토리지 유형을 사용하는 Filesystem Catalog에는 해당 fs.*.support=true만 설정하세요. “호환성 활성화”를 위해 여러 스토리지 유형을 활성화하지 마세요. 이 속성은 설정 파싱을 선택할 뿐 스토리지 서비스에 프로토콜 지원을 추가하지 않습니다.

Warehouse가 있는 스토리지 시스템에 맞는 예제를 선택하고 버킷, 경로, 주소 및 자격 증명을 교체하세요.

CREATE CATALOG lance_fs_s3 PROPERTIES (
"type" = "lance",
"lance.catalog.type" = "filesystem",
"warehouse" = "s3://my-bucket/lance",
"fs.s3.support" = "true",
"use_path_style" = "false",
"s3.endpoint" = "https://my-bucket.s3.us-east-1.amazonaws.com",
"s3.region" = "us-east-1",
"s3.access_key" = "<ak>",
"s3.secret_key" = "<sk>"
);

이 예제는 use_path_style=false와 함께 버킷을 포함한 AWS S3 endpoint를 사용합니다. 현재 Doris의 S3 속성 검증에는 endpoint가 필요합니다. 여기 나온 s3://my-bucket/... URI를 사용할 때 리전만 설정해서는 충분하지 않습니다. 버킷이나 리전을 변경할 때마다 endpoint도 업데이트하세요.

S3 및 OSS에서는 warehouse에 s3://bucket/path 또는 oss://bucket/path처럼 버킷이 포함되어야 합니다.

REST Catalog​

REST Catalog는 Lance REST Namespace를 통해 Namespace, 테이블 위치 및 스토리지 액세스 매개변수를 가져옵니다. REST Catalog에는 warehouse 속성이 필요하지 않으며 설정할 수도 없습니다.

속성필수기본값설명
lance.rest.uri예-REST 서비스 URI입니다. http:// 또는 https://를 사용해야 합니다.
lance.rest.security.type아니요none인증 유형. 유효한 값은 none, bearer, api_key입니다.
lance.rest.bearer-tokenBearer 인증 시 예-Bearer Token입니다.
lance.rest.api-keyAPI Key 인증 시 예-x-api-key 헤더로 전송되는 API Key입니다.
lance.rest.header.<header-name>아니요-REST 서비스에 전송되는 사용자 지정 HTTP 헤더입니다. 인증 헤더에는 위의 전용 인증 속성을 사용하세요.

REST 서비스 인증과 객체 스토리지 인증은 별개입니다. lance.rest.*는 Namespace 서비스 연결에 사용하고, s3.* / oss.*는 FE와 BE가 테이블 데이터를 읽는 데 사용합니다. Namespace가 반환하는 테이블 URI가 액세스 경로를 결정합니다. s3://는 S3를 사용하고 oss://는 네이티브 OSS 액세스를 사용합니다. 반환된 테이블 URI에 따라 기본 스토리지 속성을 선택하세요.

CREATE CATALOG lance_rest_s3 PROPERTIES (
"type" = "lance",
"lance.catalog.type" = "rest",
"lance.rest.uri" = "https://lance.example.com",
"lance.rest.security.type" = "bearer",
"lance.rest.bearer-token" = "<token>",
"fs.s3.support" = "true",
"use_path_style" = "false",
"s3.endpoint" = "https://my-bucket.s3.us-east-1.amazonaws.com",
"s3.region" = "us-east-1",
"s3.access_key" = "<ak>",
"s3.secret_key" = "<sk>"
);

Namespace가 반환한 AWS s3://my-bucket/... 테이블용입니다. 기본 endpoint에는 해당 버킷이 포함되어야 하며 리전이 일치해야 합니다. 다른 버킷의 테이블에는 아래에 설명된 대로 각 버킷에 맞는 endpoint가 필요합니다.

인증하지 않는 REST 액세스에는 lance.rest.security.type과 인증 속성을 생략하세요. API Key 인증에는 이를 "lance.rest.security.type" = "api_key" 및 "lance.rest.api-key" = "<api-key>"로 바꾸세요.

REST Namespace가 각 테이블의 storage_options에서 자격 증명이나 endpoint를 제공하면 Doris는 해당 Catalog 기본값을 옵션별로 덮어쓰고 병합된 설정을 FE와 BE 모두에서 사용합니다. 자격 증명만 제공하면 Catalog의 endpoint와 액세스 방식이 그대로 적용됩니다. Namespace가 완전한 액세스 설정을 제공할 때만 Catalog 스토리지 속성을 모두 생략할 수 있습니다.

S3의 경우 Namespace는 aws_endpoint, aws_region, aws_access_key_id, aws_secret_access_key, aws_session_token, aws_virtual_hosted_style_request를 사용할 수 있습니다. 마지막 옵션은 Doris의 use_path_style과 반대입니다. 가상 호스트 방식 액세스에는 aws_virtual_hosted_style_request=true, 경로 방식 액세스에는 false를 사용합니다. fs.s3.support는 Doris Catalog 속성이며 Lance storage_options 매개변수가 아닙니다.

네이티브 OSS 액세스의 경우 Namespace는 oss_endpoint, oss_access_key_id, oss_secret_access_key, oss_region, oss_security_token 또는 이에 대응하는 네이티브 이름인 endpoint, access_key_id, access_key_secret, region, security_token을 제공할 수 있습니다.

테이블이 서로 다른 버킷에 있다면 예제의 my-bucket으로 고정된 S3 가상 호스트 방식 endpoint를 공유하지 마세요. Namespace가 각 테이블에 맞는 endpoint와 액세스 방식을 제공하도록 하거나 버킷별로 Catalog를 설정하세요. 경로 방식 액세스를 지원하는 서비스라면 버킷 없는 서비스 endpoint에 use_path_style=true를 설정하여 클라이언트가 각 테이블 URI에서 버킷을 읽게 할 수도 있습니다.

REST Namespace가 테이블 버전을 관리한다면(DescribeTable의 managed_versioning = true), Doris는 Namespace의 버전 API를 통해 최신 버전과 Time Travel 선택자를 확인하고 스토리지에서 해당 manifest를 읽습니다. 자세한 내용과 제한 사항은 Time Travel을 참조하세요.

Namespace 매핑​

Lance는 다단계 Namespace를 지원하지만 Doris Catalog에서는 각 Namespace를 데이터베이스 이름으로 표현합니다.

Lance NamespaceDoris 데이터베이스 이름
루트 Namespacedefault, lance.namespace.root_database로 설정 가능
dorisdoris
doris.analyticsdoris.analytics

Doris는 다단계 Namespace의 각 수준을 .으로 연결해 데이터베이스 이름을 만듭니다. .이 포함된 데이터베이스 이름을 참조할 때는 백틱을 사용하세요.

SHOW TABLES FROM lance_catalog.`doris.analytics`;

SELECT *
FROM lance_catalog.`doris.analytics`.user_features;

lance.namespace.parent를 사용하면 Catalog를 Namespace 하위 트리로 제한할 수 있습니다. 예를 들어:

CREATE CATALOG lance_analytics PROPERTIES (
"type" = "lance",
"lance.catalog.type" = "filesystem",
"warehouse" = "s3://my-bucket/lance",
"lance.namespace.parent" = "production$analytics",
"fs.s3.support" = "true",
"s3.endpoint" = "https://my-bucket.s3.us-east-1.amazonaws.com",
"use_path_style" = "false",
"s3.region" = "us-east-1",
"s3.access_key" = "<ak>",
"s3.secret_key" = "<sk>"
);

그러면 Doris는 production.analytics 아래의 테이블과 하위 Namespace만 표시합니다.

Lance 테이블 쿼리​

Catalog를 생성한 뒤에는 다른 외부 테이블과 같은 방식으로 Lance 테이블을 탐색하고 쿼리할 수 있습니다.

SHOW DATABASES FROM lance_catalog;

SHOW TABLES FROM lance_catalog.default;

DESC lance_catalog.default.user_profiles;

SELECT user_id, name, age
FROM lance_catalog.default.user_profiles
WHERE age >= 18
ORDER BY user_id
LIMIT 100;

Lance에서 Doris 내부 테이블로 데이터를 적재할 수도 있습니다.

INSERT INTO internal.demo.user_profiles
SELECT user_id, name, age
FROM lance_catalog.default.user_profiles;

일반 Catalog 쿼리에서는 Doris가 계획 단계에서 Lance 데이터셋 버전을 고정하고 Fragment별 스캔 작업을 생성합니다. 따라서 쿼리는 일관된 스냅샷을 읽고, 여러 Scanner가 각자 데이터셋 전체를 반복 스캔하지 않으면서 서로 다른 Fragment를 병렬로 읽을 수 있습니다.

Time Travel​

Lance 데이터셋에 커밋할 때마다 새 버전이 생성되며 버전 번호는 1부터 시작합니다. 쿼리에서 최신 버전 대신 과거 버전을 읽을 수 있습니다.

-- Read version 2 of the table.
SELECT * FROM lance_catalog.db.tbl FOR VERSION AS OF 2;

-- Read the latest version committed at or before the given time.
SELECT * FROM lance_catalog.db.tbl FOR TIME AS OF '2026-09-19 13:06:10';
  • FOR VERSION AS OF는 양의 정수 버전 또는 Paimon 테이블 및 Iceberg 태그 이름의 경우와 마찬가지로 따옴표로 묶은 태그 이름을 받습니다. FOR VERSION AS OF 'v2'는 @tag(v2)와 같습니다.
  • FOR TIME AS OF는 세션 시간대의 초 또는 밀리초 정밀도 타임스탬프(yyyy-MM-dd HH:mm:ss 또는 yyyy-MM-dd HH:mm:ss.SSS)를 받습니다. Doris는 지정한 타임스탬프 이전 또는 그 시각에 커밋된 버전 중 manifest에 기록된 커밋 시간이 가장 늦은 버전을 선택합니다. 같은 순간에 커밋된 버전 중에서는 더 최신 버전을 선택합니다. Iceberg에서 시간으로 스냅샷을 선택하는 경우처럼 커밋 시간은 기록된 값 그대로 비교하며, 버전 번호가 커질수록 증가한다고 가정하지 않습니다. Lance는 밀리초보다 정밀한 커밋 시간을 기록하며 이를 전체 정밀도로 비교합니다. 요청한 밀리초 범위 안이라도 요청 시각 이후에 커밋된 버전은 선택되지 않습니다. 정리 과정에서 삭제된 버전은 커밋 시간도 함께 사라지므로, Iceberg가 만료된 스냅샷 이전의 스냅샷 로그를 버리는 것처럼 Doris는 가장 최근에 삭제된 버전보다 최신 버전만 고려합니다. 그보다 이른 타임스탬프는 오류입니다. 최초 버전보다 이르거나 정리로 삭제된 기록에 속할 수 있기 때문입니다. 예를 들어 태그로 유지된 버전과 그다음 남아 있는 버전 사이의 시각에는 당시 테이블 상태를 알 수 없습니다.
  • 선택된 버전은 문장 전체에서 고정됩니다. 스키마, Fragment 계획, 조건자 푸시다운, BE 스캔 모두 해당 버전을 사용합니다. 한 문장에서 같은 테이블을 두 번 참조해 서로 다른 버전을 선택할 수도 있습니다. EXPLAIN은 선택한 버전을 lanceVersion으로 표시합니다.
  • Lance의 cleanup_old_versions로 삭제된 버전은 읽을 수 없으며, 존재한 적 없는 버전과 같은 방식으로 보고됩니다. REST Namespace가 버전을 관리하는 테이블은 아래의 REST Namespace 관리 버전 절을 참조하세요.
  • vector_search()와 full_text_search()는 자체 version, timestamp, tag, branch 매개변수로 버전, 시간, 태그 또는 브랜치를 선택합니다. 과거 버전 또는 브랜치 검색을 참조하세요. 두 함수의 table 인수에는 FOR VERSION AS OF, @tag, @branch를 넣을 수 없습니다.
  • 인덱스 조회(SHOW INDEX, lance_index_entries())는 항상 main의 최신 버전을 사용하며 과거 버전이나 브랜치의 인덱스는 표시하지 않습니다.

Lance 태그와 브랜치는 Iceberg 및 Paimon 테이블과 동일한 구문으로 선택합니다.

-- Read the version the tag "v2" points at.
SELECT * FROM lance_catalog.db.tbl@tag(v2);

-- Read the latest version of the branch "dev".
SELECT * FROM lance_catalog.db.tbl@branch(dev);

-- Read version 2 of the branch "dev".
SELECT * FROM lance_catalog.db.tbl@branch(dev) FOR VERSION AS OF 2;
  • 태그는 브랜치의 버전을 가리키며 태그를 읽으면 해당 브랜치의 해당 버전이 선택됩니다. dev에서 만든 태그는 main의 같은 번호 버전이 아니라 dev를 읽습니다. @tag는 FOR VERSION AS OF나 FOR TIME AS OF와 함께 사용할 수 없습니다.
  • @branch(main)은 @branch를 생략한 경우와 같이 main을 읽습니다.
  • 각 브랜치는 생성 당시 기반이 된 버전부터 시작하는 자체 버전 시퀀스를 갖습니다. @branch 뒤의 FOR VERSION AS OF / FOR TIME AS OF는 해당 브랜치의 버전에서만 선택하므로 브랜치 생성 전 타임스탬프는 그 브랜치에서 아무 버전도 선택하지 않습니다. @branch(main)을 포함해 @branch 뒤의 FOR VERSION AS OF에는 태그 이름을 지정할 수 없습니다. 태그가 이미 브랜치를 결정하기 때문입니다.
  • 브랜치는 <table>/tree/<branch>/에 저장되는 테이블의 얕은 복제본이며, manifest에는 테이블 위치가 절대 URI로 기록됩니다. 따라서 브랜치는 생성된 위치에서만 읽을 수 있습니다. 데이터셋을 다른 곳으로 복사하거나 이동한 후에도 main은 읽을 수 있지만 브랜치는 읽을 수 없습니다.
  • 브랜치 메타데이터가 삭제되었거나 생성이 중간에 중단되었더라도 tree/ 아래 디렉터리를 정리하기 전까지는 @branch로 읽을 수 있습니다. Lance SDK가 디렉터리를 기준으로 브랜치를 체크아웃하기 때문입니다. REST Namespace가 버전을 관리하는 테이블에서는 Namespace가 브랜치의 존재 여부를 결정하고, main을 읽지 않고 해당 디렉터리에서 브랜치를 읽습니다.
  • EXPLAIN은 읽을 브랜치를 lanceBranch로 표시합니다. 존재하지 않는 태그나 브랜치는 오류입니다.

REST Namespace 관리 버전​

REST Namespace는 테이블 버전을 직접 관리할 수 있습니다(DescribeTable 응답의 managed_versioning = true). 이런 테이블에서는 Namespace가 존재하는 버전과 최신 버전을 결정합니다. 최신 버전과 FOR VERSION AS OF는 Namespace 기록(ListTableVersions, DescribeTableVersion)을 따릅니다. FOR TIME AS OF는 Namespace가 나열한 버전만 대상으로 manifest에 기록된 커밋 시간을 통해 결정합니다. 태그는 다른 Lance 테이블과 마찬가지로 데이터셋의 _refs/tags/에서 읽으며 Namespace가 기록한 버전을 가리켜야 합니다. 브랜치 버전도 Namespace를 따릅니다. 버전이 선택되면 FE와 BE는 다른 Lance 테이블처럼 테이블 위치와 버전 번호로 스토리지에서 읽습니다.

  • Namespace가 기록하지 않은 버전은 manifest가 스토리지에 남아 있더라도 찾을 수 없는 버전으로 보고됩니다. 스토리지에 더 새로운 manifest가 있더라도 최신 버전은 Namespace가 기록한 최신 버전입니다.
  • Namespace가 더 이상 기록하지 않지만 앞뒤 버전은 기록하는 버전은 정리 과정에서 삭제된 버전과 마찬가지로 FOR TIME AS OF에서 삭제된 이력으로 취급합니다. 해당 버전이 적용될 수 있는 타임스탬프는 이전 버전을 선택하지 않고 오류를 반환합니다.
  • Namespace가 테이블 또는 브랜치에 기록한 버전이 없으면 스토리지에 해당 manifest가 있어도 오류입니다.
  • 이러한 테이블의 EXPLAIN에는 lanceManagedVersioning=true가 표시됩니다.

제한 사항:

  • Doris는 DescribeTable이 반환한 table_uri에서 데이터셋을 읽으며, table_uri가 없으면 location에서 읽습니다. 어느 쪽이든 완전한 스토리지 URI여야 합니다. location이 비어 있거나 함께 반환된 table_uri가 다른 위치를 가리키면(사전 서명된 자격 증명 같은 쿼리 문자열은 제외) 쿼리는 오류로 실패하고 아무것도 읽지 않습니다. s3+ddb URI도 거부됩니다. DynamoDB 커밋 핸들러가 읽을 때 버전을 기록하고 확정하기 때문입니다. Doris는 관리 대상 테이블을 캐시하지 않고 읽을 때마다 describe하며, FE와 BE 모두 해당 응답의 위치와 스토리지 설정으로 읽습니다.
  • Doris는 테이블 디렉터리(브랜치는 tree/<branch>/)의 정규 경로 _versions/<u64::MAX - version>.manifest 또는 이전 Lance 명명 방식의 _versions/<version>.manifest에서 번호로 버전을 읽습니다. Namespace가 선택한 버전에 기록한 manifest는 이 경로 또는 그 옆의 staged manifest(<canonical path>-<id>)여야 합니다. FOR TIME AS OF에서는 커밋 시간이 선택 기준이므로 해당 main이나 브랜치에 기록된 모든 버전의 manifest가 이 조건을 만족해야 합니다. 다른 위치에 기록된 버전은 읽을 수 없으며 쿼리는 기록된 경로와 정규 경로가 표시된 오류로 실패합니다. 쿼리 실행 중 Namespace가 테이블을 이동해도 같은 방식으로 실패할 수 있으며 재시도하면 성공합니다.
  • Namespace가 staged manifest에만 기록했고 정규 manifest가 없는 버전은 읽을 수 없습니다. 커밋이 확정되지 않았거나 정리 과정에서 버전이 삭제된 상태입니다. 쿼리는 두 원인을 명시한 오류로 실패합니다. 해당 버전의 커밋 시간이 알려져 있지 않으므로 그 버전이 적용될 수 있는 타임스탬프에 대한 FOR TIME AS OF는 해당 main 또는 브랜치에서 실패합니다. 최신 버전이라면 writer 또는 Namespace를 사용하는 다른 reader가 확정할 때까지 모든 타임스탬프가 이에 해당합니다. 이 경우 FOR VERSION AS OF 없이 테이블을 읽거나 스키마를 로드하는 작업도 실패합니다. Lance 자체 Namespace 구현은 버전을 기록하기 전에 확정하므로, 확정되지 않은 버전은 커밋이 중단되었거나 Namespace가 staged manifest를 기록한 경우에만 나타납니다. Doris는 읽는 동안 테이블에 쓰지 않습니다.

Lance 인덱스 조회​

Filesystem Catalog의 테이블에서는 SHOW INDEX가 Lance 데이터셋에 기록된 논리적 스칼라 인덱스(FTS 역색인 인덱스 포함)와 벡터 인덱스를 표시합니다. 변형 구문인 SHOW INDEXES, SHOW KEY, SHOW KEYS도 같은 결과를 생성합니다.

SHOW INDEX FROM lance_catalog.default.items;

Doris는 실행 시점의 최신 데이터셋 스냅샷에서 인덱스 메타데이터를 읽고 결과를 인덱스 이름과 열 위치로 정렬합니다. Lance가 내부적으로 유지하는 __lance_frag_reuse, __lance_mem_wal 같은 시스템 인덱스는 표시하지 않습니다. 인덱스가 없는 테이블은 빈 결과를 반환합니다.

SHOW INDEX는 표준 13열 결과 집합을 반환합니다. Lance 테이블에서는 아래 열에만 값이 있고 나머지는 항상 비어 있습니다.

열Lance 테이블의 값
Table테이블 이름.
Key_nameLance 논리 인덱스의 이름.
Seq_in_index인덱스 내 열의 위치이며 1부터 시작합니다. 여러 필드에 대한 인덱스는 필드마다 한 행을 생성합니다.
Column_name인덱싱된 필드. 중첩 필드의 인덱스는 .으로 연결된 필드 경로를 표시하며, 문자·숫자·_ 이외의 문자가 포함된 경로 부분은 백틱으로 감쌉니다. 예: attributes.`child.with.dot`.
Index_typeLance SDK가 보고한 인덱스 유형. 예: BTree, INVERTED, IVF_FLAT, IVF_SQ, IVF_PQ, IVF_HNSW_FLAT, IVF_HNSW_SQ, IVF_HNSW_PQ.
Properties정해진 인덱스 세부 정보 집합을 담은 JSON 객체. 최상위에는 metric_type과 target_partition_size, compression 아래에는 type, num_bits, num_sub_vectors, rotation_type, hnsw 아래에는 construction_ef, max_connections, max_level이 있습니다. 키는 정렬되며, 인덱스 세부 정보에 해당 필드가 없으면 값은 {}입니다.

다음 행은 IVF_PQ 벡터 인덱스와 중첩 필드의 BTree 인덱스를 보여 줍니다.

Table          Key_name              Seq_in_index  Column_name                  Index_type  Properties
vs_ivf_pq_f32 embedding_ivf_pq_f32 1 embedding IVF_PQ {"compression":{"num_bits":4,"num_sub_vectors":4,"type":"pq"},"metric_type":"L2"}
nested_index nested_label_btree 1 attributes.`child.with.dot` BTree {}

SHOW INDEX는 기존 인덱스만 조회하며 생성하지는 않습니다. Doris 외부에서 Lance SDK 또는 다른 Lance writer로 인덱스를 생성하세요. 인덱스가 알 수 없는 필드를 참조하거나 서로 다른 인덱스의 이름이 같은 등 기록된 인덱스 메타데이터가 일관되지 않으면 일부 메타데이터를 반환하는 대신 문장이 실패합니다.

SHOW INDEX에는 테이블에 대한 SHOW 권한이 필요합니다. REST Catalog에서는 SHOW INDEX is not supported for Lance REST catalogs 오류와 함께 거부됩니다.

lance_index_entries로 물리적 인덱스 항목 나열​

노트

lance_index_entries는 Apache Doris 4.1.5부터 지원됩니다.

SHOW INDEX는 고정된 MySQL 방식의 13열 결과 집합으로 Lance 테이블 인덱스의 논리적 정보인 이름, 인덱싱된 열, 유형, 속성을 제공합니다. 인덱스 항목의 물리적 식별자인 UUID와 데이터셋 버전은 이 고정 스키마에 넣을 수 없습니다. 읽기 전용 테이블 값 함수 lance_index_entries는 이러한 물리적 정보를 제공하며 최신 데이터셋 스냅샷에 기록된 물리적 인덱스 항목마다 한 행을 반환합니다.

SELECT *
FROM lance_index_entries("table" = "lance_catalog.default.vs_ivf_pq_f32");

table은 완전한 세 부분의 catalog.database.table 이름이어야 합니다. 식별자는 백틱 인용을 지원하므로 다단계 Namespace가 .이 포함된 데이터베이스 이름에 매핑된다면 데이터베이스 부분을 백틱으로 감싸세요.

TVF는 다음과 같은 고정 열을 반환합니다.

열유형설명
CatalogNameSTRINGCatalog 이름.
DatabaseNameSTRING데이터베이스 이름.
TableNameSTRING테이블 이름.
IndexNameSTRINGLance 논리 인덱스의 이름. 물리적 항목의 UUID가 여러 개이면 하나의 논리 인덱스가 여러 행을 생성합니다.
IndexUuidSTRING물리적 인덱스 항목의 UUID.
DatasetVersionBIGINT물리적 인덱스 항목에 기록된 데이터셋 버전, 즉 항목이 작성된 시점의 데이터셋 버전입니다. 같은 논리 인덱스의 물리적 항목마다 데이터셋 버전이 다를 수 있습니다. 항상 1 이상입니다.

다음 행은 위의 IVF_PQ 인덱스 결과 예시입니다. IndexUuid와 DatasetVersion은 예시 값입니다.

CatalogName    DatabaseName  TableName      IndexName             IndexUuid                             DatasetVersion
lance_catalog default vs_ivf_pq_f32 embedding_ivf_pq_f32 3f9c2a1e-7b4d-4e6f-9a1c-2d5e8f0b3a47 12

lance_index_entries는 Dataset.getIndexes()를 통해 인덱스 메타데이터만 읽습니다. 행 수나 통계는 읽지 않고 데이터셋을 변경하지도 않습니다. 결과에는 WHERE IndexName = ... 같은 일반 조건자를 적용할 수 있습니다. 행은 (IndexName, IndexUuid) 순으로 정렬됩니다. 읽기는 모두 검증하며, 오류가 있으면 결과 전체가 실패합니다. 빈 이름이나 너무 긴 이름, 누락된 UUID, 양수가 아닌 DatasetVersion, 중복 UUID, 또는 16,384개를 초과하는 원시 항목 수가 있으면 잘린 결과를 반환하는 대신 전체 쿼리가 실패합니다. Lance가 내부적으로 유지하는 예약 시스템 인덱스 __lance_frag_reuse와 __lance_mem_wal도 검증하지만 표시하지는 않습니다.

lance_index_entries에는 테이블에 대한 SHOW 권한이 필요합니다. REST Catalog에서는 lance_index_entries is not supported for Lance REST catalogs 오류와 함께 TVF가 거부됩니다.

유형 매핑​

Doris 4.2의 전체 매핑 표, 정밀도 규칙, 지원되지 않는 유형은 Lance에서 Doris로의 데이터 유형 매핑을 참조하세요.

조건자 푸시다운​

Doris는 의미상 호환되는 조건자를 Substrait 표현식으로 변환하고 읽는 동안 Lance가 평가하도록 전달합니다. 조건 전체가 푸시다운되면 Doris BE는 다시 평가하지 않습니다. 안전하게 푸시다운할 수 없는 조건은 Doris에 남습니다.

푸시다운 지원 데이터 유형​

Lance / Arrow 유형푸시다운 지원
bool동등 비교, NULL 검사 및 논리 연산을 지원하며 순서 비교는 지원하지 않음
int8/16/32/64지원
uint8/16/32/64지원
float32/64지원
decimal128정밀도 1~38, Scale은 0부터 Precision까지
utf8, large_utf8지원
date32(day)지원
시간대 정보가 없는 timestamp(s/ms/us)지원
list<utf8>NULL이 아닌 문자열 리터럴을 사용하는 내장 array_contains. 배열 레이블 조건자 참조

읽을 수 있는 다른 유형에 대한 조건자는 현재 Doris에 남습니다. 여기에는 float16, decimal256, Binary, date64, Time, 나노초 Timestamp, 시간대가 있는 Timestamp 및 위의 배열 포함 여부 사례 이외의 복합 유형이 포함됩니다.

푸시다운 지원 연산자​

SQL 조건자푸시다운 조건
=, !=, <>, <, <=, >, >=열과 상수의 직접 비교. 상수가 왼쪽에 있어도 됩니다.
<=>열과 상수의 직접적인 NULL 안전 동등 비교. NOT, AND, OR 안에서도 NULL이 아닌 2값 결과를 유지합니다.
IN, NOT INNULL이 포함되지 않은 비어 있지 않은 상수 목록
IS NULL, IS NOT NULL열 직접 참조
Boolean 열, NOT Boolean 열Boolean 열 직접 참조
LIKE, NOT LIKEutf8 또는 large_utf8 열 직접 참조와 문자열 리터럴 패턴. 백슬래시 이스케이프, 명시적 ESCAPE 절, 포함된 NUL 문자가 없어야 합니다.
starts_with, ends_withutf8 또는 large_utf8 열 직접 참조와 문자열 리터럴을 사용하는 내장 함수
array_contains(labels, 'red')list<utf8> 열 직접 참조와 NULL이 아닌 문자열 리터럴
AND최상위 논리곱 항을 각각 푸시다운할 수 있으며 지원되지 않는 항은 Doris에 남습니다.
OR양쪽 분기가 모두 완전히 변환 가능해야 합니다.
NOT피연산자가 완전히 변환 가능해야 합니다.

다음 형태는 일반적으로 푸시다운하지 않습니다.

  • 지원되는 내장 문자열 함수 및 배열 포함 여부 함수 이외의 함수, 또는 열에 적용된 산술 표현식. 이름이 like, starts_with, ends_with, array_contains인 사용자 정의 함수는 해당 내장 함수로 취급하지 않습니다.
  • 패턴에 NUL 문자가 포함된 문자열 조건자. 백슬래시 이스케이프나 명시적 ESCAPE 절을 사용하는 LIKE, NOT LIKE 패턴도 Doris에 남습니다.
  • 비어 있거나 NULL이 포함된 IN 목록.
  • 일부 표현식만 변환 가능한 OR 또는 NOT 표현식.
  • 손실 없이 Lance로 변환할 수 없는 데이터 유형이나 상수 값.

EXPLAIN의 lancePushdownPredicate를 사용해 실제로 푸시다운된 조건을 확인하세요.

EXPLAIN
SELECT user_id
FROM lance_catalog.default.user_profiles
WHERE active
AND starts_with(name, 'A')
AND country LIKE 'C%';

배열 레이블 조건자​

Apache Doris 4.1.5부터 배열 레이블 푸시다운은 Lance list<utf8> 열을 직접 참조하고 NULL이 아닌 문자열 리터럴을 사용하는 array_contains를 지원합니다. 이전 빌드에서는 이러한 조건이 Doris에 남을 수 있으므로 이 최적화에 의존하기 전에 EXPLAIN을 확인하세요. NULL 배열은 여전히 NULL을 생성하고, 빈 배열은 일치하지 않으며, 중복 레이블이 결과 행을 중복시키지 않습니다.

id 열과 list<utf8> 유형의 labels 열이 있는 Lance 테이블의 예:

EXPLAIN
SELECT id
FROM lance_catalog.default.items
WHERE array_contains(labels, 'red')
AND array_contains(labels, 'blue');

이 조건은 순서나 인접 여부에 관계없이 두 레이블이 모두 포함된 배열과 일치합니다. AND를 OR로 바꾸면 어느 한쪽 레이블이 일치하면 됩니다. NOT (array_contains(labels, 'red'))도 변환 가능하며 SQL의 NULL 의미 체계를 유지합니다. 이 예제들은 일반 테이블 스캔에 적용되며 검색 TVF의 filter 매개변수와 외부 WHERE 조건의 차이를 변경하지 않습니다.

다음은 Doris에서 평가하는 잔여 조건자로 남습니다.

  • array_contains(labels, NULL): Doris는 NULL 요소를 일치시킬 수 있지만 Lance의 포함 여부 함수는 NULL 검색 값에 대해 다른 의미 체계를 사용합니다.
  • array_contains_all(labels, ['red', 'blue']): Doris는 순서가 있고 연속된 부분 시퀀스를 검사합니다. 이를 Lance의 집합 포함 함수나 독립적인 두 포함 여부 조건자로 대체할 수 없습니다.
  • 동적 검색 값, 배열 열을 둘러싼 표현식이나 캐스트, large_list, 고정 크기 목록, list<large_utf8>을 포함한 다른 배열 요소 또는 컨테이너 유형.

조건자 푸시다운과 스칼라 인덱스 사용​

조건자 푸시다운은 Lance가 조건을 평가한다는 뜻이며 인덱스 사용을 보장하지는 않습니다. 일반 스캔에서 Doris는 각 스캔 작업에 물리적 BTree, Bitmap 또는 LabelList 세그먼트를 지정할 수 있습니다. 같은 열의 IN, AND, OR, NOT 조합은 Lance가 전체 표현식을 지원하는 경우 선택된 인덱스를 사용할 수 있습니다. OR는 양쪽 분기에서 안전한 후보가 필요하고, NOT은 불완전하게 평가된 하위 트리를 부정할 수 없습니다. 지원되지 않는 인덱스 표현식은 할당된 Fragment 범위 내 스캔으로 대체됩니다.

각 작업은 하나의 논리적 스칼라 인덱스를 선택합니다. 레이블, 범주, 숫자 조건자를 결합한 쿼리에서 Doris가 세 인덱스를 모두 교집합으로 처리하는 것은 아닙니다. 푸시다운된 전체 조건은 스캔 제한을 적용하기 전에 다시 검사하며 인덱스 범위 밖의 Fragment도 읽습니다. 사용할 수 있는 인덱스가 없어도 호환 조건자를 푸시다운하여 Lance 스캔에서 평가할 수 있습니다.

다음 증거를 함께 확인하세요.

증거의미
EXPLAIN의 lancePushdownPredicateLance로 전송된 조건. 남은 predicates: 조건은 Doris가 평가합니다.
EXPLAIN의 lanceScalarIndexScan=SEGMENT 및 lanceGroupingIndex스칼라 세그먼트 스캔이 계획되었고 선택된 논리 인덱스를 나타냅니다. 이것만으로 실행 시 인덱스가 사용되었음을 증명하지는 못합니다.
Profile의 LanceScalarIndexSegmentsSearched계측된 스칼라 세그먼트 경로에서 실제 수행한 검색.
Profile의 LanceScalarIndexCandidateRows해당 경로에서 보고한 후보 행 수이며 최종 결과 행 수와 같지는 않을 수 있습니다. 일부 NOT 표현식 같은 여집합 마스크는 후보 수를 보고하지 않을 수 있습니다.
Profile의 LanceScalarIndexSegmentFallbacks계획된 스칼라 세그먼트 경로에서 대체 경로로 전환한 작업.

이 카운터를 확인하려면 enable_profile을 활성화하세요. 최적화를 측정할 때는 같은 SQL, 프로젝션, 데이터셋 버전, 캐시 상태를 비교하세요. 이 카운터는 일반 스칼라 세그먼트 스캔을 설명하며 ANN 또는 전문 검색의 사전 필터링 내부에서 스칼라 인덱스를 사용했는지 판단하는 유일한 근거로 삼아서는 안 됩니다.

Runtime Filter 푸시다운​

일반 Lance 테이블이 조인에 참여하면 Doris는 조인 빌드 측에서 생성한 일부 Runtime Filter를 Lance SQL 조건으로 변환하여 Lance가 데이터를 읽는 동안 적용할 수 있습니다. 특히 큰 테이블을 선택성이 높은 빌드 측 테이블과 조인할 때 Doris로 반환되는 데이터 양을 줄일 수 있습니다.

SELECT l.user_id, l.name
FROM lance_catalog.default.user_profiles l
JOIN internal.demo.allowed_users a
ON l.user_id = a.user_id;

현재 다음 Runtime Filter를 푸시다운할 수 있습니다.

Runtime FilterLance로 푸시다운되는 형태
IN Filtercolumn IN (...)
Min/Max Filtercolumn >= min_value, column <= max_value 또는 둘 다

Runtime Filter의 열과 값은 Lance SQL이 지원하는 스칼라 유형으로 안전하게 변환할 수 있어야 합니다. 다른 Runtime Filter 유형, NULL이 포함된 IN Filter, 안전하게 변환할 수 없는 데이터 유형이나 값은 푸시다운하지 않습니다. 푸시다운되지 않은 Runtime Filter도 Doris가 평가하므로 쿼리의 정확성은 유지되고 Lance의 조기 필터링에 따른 성능 이점만 잃습니다.

Lance Scanner를 생성하기 전에 준비된 Runtime Filter만 해당 Scanner의 푸시다운 조건에 포함할 수 있습니다. 나중에 도착한 Runtime Filter도 Doris에서 평가하지만 이미 생성된 Scanner에는 푸시다운되지 않을 수 있습니다.

Runtime Filter 조건은 일반 SQL 조건자에서 생성한 Substrait 조건과 결합되며 서로를 대체하지 않습니다. EXPLAIN의 runtime filters 항목은 계획된 Runtime Filter를 보여 줍니다. Runtime Filter가 하나라도 푸시다운되면 Runtime Profile의 LanceRuntimeFilterPushedIds 및 LanceRuntimeFilterSkippedIds에서 푸시다운되거나 건너뛴 Filter ID를 확인할 수 있습니다.

현재 Runtime Filter 푸시다운은 일반 Lance 테이블 스캔에만 적용됩니다. vector_search() 또는 전문 검색에서 후보를 생성할 때는 사용되지 않습니다.

파일 TVF로 Lance 쿼리​

알려진 경로의 Lance 데이터셋만 읽으면 된다면 Catalog를 생성하지 않고 s3() 또는 local() TVF를 사용할 수 있습니다. uri 또는 file_path는 내부 데이터 파일이 아니라 Lance 데이터셋의 루트 디렉터리를 가리켜야 합니다.

S3 TVF​

s3() TVF는 S3 호환 API를 사용합니다. OSS에는 s3.* 속성과 버킷이 포함된 가상 호스트 방식 endpoint를 사용하세요. 네이티브 OSS 액세스에는 위의 OSS Catalog 예제를 사용하세요. 다음 SQL은 예제 데이터셋의 앞 10행을 읽습니다. 실행 전에 주소, 자격 증명 및 열 이름을 교체하세요.

SELECT user_id, name
FROM s3(
"uri" = "s3://my-bucket/lance/user_profiles.lance",
"format" = "lance",
"fs.s3.support" = "true",
"use_path_style" = "false",
"s3.endpoint" = "https://my-bucket.s3.us-east-1.amazonaws.com",
"s3.region" = "us-east-1",
"s3.access_key" = "<ak>",
"s3.secret_key" = "<sk>"
)
ORDER BY user_id
LIMIT 10;

AWS S3에도 endpoint를 명시적으로 설정하세요. 이 예제는 가상 호스트 방식 액세스를 사용하므로 endpoint의 버킷과 리전은 URI 및 s3.region과 일치해야 합니다.

OSS에서는 동일한 s3.* 속성과 fs.s3.support=true를 유지하면서 예제의 uri를 oss://my-bucket/lance/user_profiles.lance로 작성할 수도 있습니다. TVF는 URI를 s3://로 정규화하며 네이티브 OSS 공급자로 전환하지 않습니다. 네이티브 OSS Catalog의 oss.* 설정을 그대로 복사하지 마세요. 버킷 없는 endpoint와 가상 호스트 방식을 결합하면 데이터셋이 없다는 오류가 발생할 수 있고, 경로 방식으로 전환하면 OSS에서 SecondLevelDomainForbidden 오류로 거부할 수 있습니다.

S3 TVF에서는 FE가 Schema, 현재 버전 및 Fragment 목록을 가져옵니다. Doris는 해당 버전을 고정하고 Fragment를 병렬로 스캔합니다. EXPLAIN에는 VTVF_SCAN_NODE가 표시됩니다. 여러 split이 데이터셋 URI를 공유할 수 있으며 Fragment ID로 구분됩니다. totalFileSize=0이나 length=0은 Lance split의 자리표시자로, 데이터셋이 비어 있거나 각 split이 전체 테이블을 반복 스캔한다는 뜻이 아닙니다.

Local TVF​

SELECT user_id, name
FROM local(
"file_path" = "/data/lance/user_profiles.lance",
"backend_id" = "10001",
"format" = "lance"
);

file_path는 작성된 그대로 대상 BE의 Lance Reader에 전달됩니다. Doris는 user_files_secure_path를 앞에 붙이거나 이 경로를 Glob으로 확장하지 않으므로, 대상 BE가 접근할 수 있는 단일 Lance 데이터셋의 루트 디렉터리를 직접 가리켜야 합니다. 절대 경로를 권장합니다.

Local TVF의 Schema 검색과 실행은 각각 최신 데이터셋 버전을 별도로 엽니다. Schema 검색 시 확인된 버전은 현재 후속 스캔에 고정되지 않습니다. 쿼리 분석과 실행 사이에 데이터셋이 변경되면 검색한 Schema와 스캔하는 스냅샷이 달라질 수 있습니다. Local TVF 쿼리를 분석하고 실행하는 동안에는 데이터셋을 변경하지 마세요. 현재 Local Lance TVF는 Scanner 하나를 사용합니다.

Lance 파일 TVF에는 다음과 같은 추가 제한 사항이 있습니다.

  • s3()와 local()만 지원합니다. HDFS, HTTP 등의 다른 파일 TVF는 현재 지원하지 않습니다.
  • path_partition_keys는 지원하지 않습니다.
  • 하나의 TVF 경로는 하나의 Lance 데이터셋만 나타낼 수 있습니다.
  • DESC FUNCTION은 지원되지 않는 유형이 포함된 Schema를 표시할 수 있지만 SQL로 해당 열을 프로젝션할 수는 없습니다.

Doris 4.2 자동 Lance 검색 기능이 포함된 빌드에서는 조건에 맞는 일반 SQL도 네이티브 벡터 Top-N 및 MATCH/BM25 검색을 사용할 수 있습니다. 근사 함수 5개, 거리 척도 변환, 입력 의미 체계, JOIN/EXISTS 경계는 일반 SQL에서 자동 검색을 참조하세요. 아래 TVF는 명시적 검색 튜닝과 자동 재작성 범위 밖의 지원 형태에 계속 사용합니다.

vector_search()는 Lance 테이블의 벡터 열에서 Top-K 검색을 수행하는 관계형 TVF입니다. 기존 Lance 벡터 인덱스를 사용하거나 Flat Search를 수행할 수 있습니다.

구문 및 예제​

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

vector_search()의 관계 스키마에는 Lance 원본 테이블의 모든 열과 최근접 이웃 쿼리를 위해 Lance Scanner가 생성한 _distance 열이 포함됩니다. 최종 SQL 결과에는 SELECT로 프로젝션한 열만 포함됩니다. Doris는 _distance를 FLOAT로 노출합니다. 이는 일반적인 유사도 점수가 아니라 거리로, 값이 작을수록 두 벡터가 가깝습니다. 원본 테이블에 _distance라는 열이 이미 있어서는 안 됩니다. SQL 관계는 최종 표시 순서를 보장하지 않으므로 결정적인 최근접 이웃 순서가 필요하다면 ORDER BY _distance ASC를 명시하세요. 거리가 같은 행의 순서를 구분하려면 고유 열을 추가하는 것이 좋습니다.

table은 정확히 세 부분의 catalog.database.table 이름으로 파싱되어야 합니다. 다단계 Lance Namespace는 .이 포함된 하나의 Doris 데이터베이스 이름에 매핑되므로 데이터베이스 부분을 백틱으로 감싸세요. 예를 들어 Namespace doris.analytics의 items 테이블에는 다음 값을 사용합니다.

"table" = "lance_catalog.`doris.analytics`.items"

인용하지 않은 lance_catalog.doris.analytics.items는 네 부분의 이름으로 파싱되어 거부됩니다. 테이블 이름만 지정하거나 database.table 형식으로 지정해도 거부됩니다.

매개변수​

매개변수필수기본값설명
table예-완전한 세 부분의 catalog.database.table 이름입니다. 다단계 Namespace가 .이 포함된 데이터베이스 이름으로 매핑되면 데이터베이스 부분을 백틱으로 감싸세요. Lance Catalog의 테이블이어야 하며 사용자에게 테이블 SELECT 권한이 있어야 합니다.
column예-벡터 열 이름입니다. 단일 벡터 fixed_size_list<float16|float32|float64|uint8|int8> 및 다중 벡터 list<fixed_size_list<float16|float32|float64, D>> 열을 지원합니다. 다중 벡터 검색을 참조하세요.
query_vector예-단일 벡터 열에는 JSON 숫자 배열, 다중 벡터 열에는 비어 있지 않은 JSON 행렬을 지정합니다. 각 하위 벡터의 차원은 열과 같아야 하며 각 값은 유한하고 요소 유형으로 표현 가능해야 합니다.
top_k아니요10offset을 건너뛴 뒤 반환할 결과 수이며 양의 정수여야 합니다.
offset아니요0벡터 검색 내에서 건너뛸 최근접 이웃 수이며 음수가 아닌 정수여야 합니다. top_k + offset은 부호 없는 32비트 정수의 최댓값을 초과할 수 없습니다.
metric아니요l2거리 척도: l2, cosine, dot, hamming. dot_product는 dot의 별칭입니다. uint8 벡터는 hamming만 지원하고, 현재 지원되는 나머지 벡터 요소 유형은 l2, cosine, dot을 지원합니다. cosine, dot, hamming 인덱스를 쿼리할 때는 metric을 명시하세요. 정수 다중 벡터 열과 다중 벡터 hamming은 지원하지 않습니다.
distance_lower_bound아니요제한 없음단일 벡터 검색의 포함 하한이며 유한한 FLOAT여야 합니다.
distance_upper_bound아니요제한 없음단일 벡터 검색의 제외 상한이며 유한한 FLOAT여야 합니다. 두 경계를 모두 설정하면 FLOAT로 변환한 후 하한이 상한보다 작아야 합니다.
filter아니요-Lance가 평가하는 Lance SQL 조건입니다. 실행 시점은 prefilter로 제어합니다.
prefilter아니요falseTVF filter가 실행되는 시점을 제어합니다. true는 벡터 후보 생성 전, 기본값인 false는 생성 후에 필터링하므로 결과가 top_k행보다 적을 수 있습니다. 이 매개변수는 외부 Doris WHERE에 영향을 주지 않습니다.
nprobes아니요최솟값 1, 최댓값 제한 없음탐색할 IVF 인덱스 파티션 수이며 양의 정수여야 합니다. 설정하지 않으면 Lance는 파티션 하나로 시작하고 prefilter=true로 인해 후보가 부족하면 추가 파티션을 탐색할 수 있습니다. 명시적으로 N으로 설정하면 최소 및 최대 탐색 수가 모두 N으로 고정됩니다.
refine_factor아니요단일 벡터: 비활성화, 다중 벡터: 1후보 재정렬 배수이며 양의 정수여야 합니다. 단일 벡터 검색에서 설정하지 않으면 Lance는 원본 벡터로 거리를 다시 계산하지 않으므로 양자화 인덱스의 _distance가 근삿값일 수 있습니다. N으로 설정하면 Lance가 먼저 (top_k + offset) × N개 후보를 가져온 뒤 원본 벡터로 정확한 거리를 재계산하고 순서를 다시 정합니다. 재정렬에서는 이러한 후보의 원본 벡터 데이터를 읽습니다. N이 커질수록 더 많은 후보를 읽고 평가하므로 I/O가 크게 증가하고 쿼리 성능이 떨어질 수 있습니다. 단일 벡터에서 1로 설정하면 재정렬이 활성화되므로 생략한 경우와 다릅니다. 다중 벡터 검색은 기본 배수 1로 항상 원본 값에 대해 후보를 재정렬합니다.
ef아니요floor(1.5 × (top_k + offset))HNSW 그래프 검색 중 유지할 후보 폭이며 양의 정수여야 합니다. refine_factor도 설정했다면 기본값은 floor(1.5 × (top_k + offset) × refine_factor)입니다. HNSW 이외 인덱스에는 효과가 없습니다.
approx_mode아니요네이티브 기본값, 현재 normal근사 검색 정책 문자열: fast, normal, accurate. 대소문자와 앞뒤 공백은 무시합니다. 숫자 문자열을 포함한 다른 값은 거부됩니다. 단일 및 다중 벡터 쿼리에 적용됩니다. 근사 모드 선택을 참조하세요.
use_index아니요truetrue이면 Doris가 벡터 열 및 거리 척도와 호환되는 Lance 벡터 인덱스를 우선 사용하고, 사용할 수 있는 인덱스가 없으면 자동으로 Flat Search를 사용합니다. false이면 벡터 인덱스를 비활성화하고 데이터에 대해 Flat Search를 수행합니다.
version, timestamp, tag, branch아니요-main의 최신 버전 대신 과거 버전, 특정 시점의 버전, 태그 또는 브랜치를 검색합니다. 과거 버전 또는 브랜치 검색을 참조하세요.

metric을 생략하면 인덱스 경로와 Flat Search 경로 모두 l2를 선택합니다. uint8 열에는 "metric" = "hamming"을 명시하세요. 기본값 l2는 지원되지 않습니다. 다중 벡터 검색에는 아래의 후보 예산도 적용됩니다.

근사 모드 선택​

처음에는 normal을 기준으로 삼으세요. approx_mode는 네이티브 검색 알고리즘의 정책을 변경하며 효과는 선택한 인덱스와 실행 경로에 따라 달라집니다. nprobes, ef, refine_factor, 저장된 인덱스 또는 split 계획은 변경하지 않습니다. 생략하면 현재 normal인 Lance 기본값을 유지합니다.

노트

이 옵션에는 근사 모드 구현이 포함된 Doris 빌드가 필요합니다. 아래 내용은 해당 구현에서 사용한 네이티브 Lance 리비전을 설명합니다. 이전 FE는 이 속성을 거부하고 이전 BE는 선택적 필드를 무시합니다. 순차 업그레이드 중 쿼리의 후보 BE 중 이전 업그레이드 원본으로 표시된 노드가 있으면 FE는 명시적인 fast 또는 accurate를 거부하여 split별로 서로 다른 정책이 사용되는 것을 막습니다. 오류에는 BE와 요청한 모드가 표시됩니다. 해당 BE를 업그레이드하거나 옵션을 생략하거나 normal을 사용하세요. 생략 및 명시적 normal은 네이티브 기본값을 유지하며 이러한 업그레이드 원본과 호환됩니다. 기본값 이외의 모드에 의존하기 전에 참여하는 FE와 BE를 모두 업그레이드하세요. 이 보호 기능은 순차 업그레이드 원본으로 표시되지 않은 임의의 구형 빌드까지 감지하지는 못합니다.

모드 간 차이​

검색 경로fastnormalaccurate
RabitQ 원시 쿼리 거리 추정여러 비트의 인덱스에서도 1비트 점수를 사용하고 확장 비트 계산 및 하한 게이트를 건너뜁니다.표준 8비트 양자화 거리 조회표를 사용하며, 가능하면 확장 비트를 계산합니다.더 높은 정밀도의 16비트 거리 조회표를 사용하며, 가능하면 확장 비트를 계산합니다.
사전 필터링을 사용하는 HNSW아래 실행 조건이 허용하면 ACORN 탐색을 활성화합니다.일반적인 필터 적용 그래프 탐색을 사용합니다.normal과 같은 탐색 정책을 사용하지만 양자화 거리 계산은 다를 수 있습니다.
IVF_FLAT 또는 전체 Flat Search모드별 양자화 변경 없음.동일한 점수 계산 정책.동일한 점수 계산 정책.

8비트와 16비트 값은 인덱스에서 차원마다 저장하는 비트 수가 아니라 쿼리 시 사용하는 거리 조회표를 나타냅니다. 예를 들어 5비트 RabitQ 인덱스에서 fast는 원시 쿼리 경로에 1비트 점수를 사용하고, 다른 모드에서는 추가 4비트를 사용할 수 있습니다. 잔차 쿼리 점수 계산을 포함한 다른 RabitQ 경로의 동작은 다를 수 있습니다. 모드 설명이 지원되는 벡터 유형이나 거리 척도 조합을 확대하지는 않습니다.

accurate 역시 근사 검색입니다. 정확한 Top-K, 모든 후보의 평가 또는 양자화 인덱스에서 원본 벡터의 _distance를 보장하지 않습니다. refine_factor는 원본 벡터를 사용하는 재정렬을 제어합니다. 값을 늘리면 점수와 재현율이 개선될 수 있지만 앞 단계에서 놓친 모든 후보를 복구할 수는 없습니다. 다중 벡터 쿼리에서는 필수 원본 벡터 재정렬이 유지됩니다.

모드별 사용 시점​

워크로드시작 선택확인할 항목
일반적인 워크로드 또는 원인을 모르는 병목normal정책을 변경하기 전에 재현율과 지연 시간의 기준값을 측정합니다.
효과적인 사전 필터가 있고 지연 시간이 중요한 HNSW_FLATfast와 normal 비교ACORN은 불필요한 거리 계산을 줄일 수 있습니다. 재현율, 결과 수, 지연 시간 중앙값 및 p95를 확인하세요. 속도 향상은 보장되지 않습니다.
재현율이 중요한 다중 비트 RabitQnormalfast는 유용한 인코딩 정보를 버려 재현율을 크게 낮출 수 있습니다. 대표 쿼리가 품질 목표를 충족하는 경우에만 사용하세요.
원본 벡터 재정렬 없이 근사 점수 오차가 중요한 RabitQaccurate와 normal 비교원본 벡터 거리와 비교해 점수 오차, 순위 품질, 추가 지연 시간을 측정하세요. 추정이 개선되어도 재현율이 높아진다는 보장은 없습니다.
이미 원본 벡터 재정렬을 사용하는 RabitQnormalaccurate의 최종 점수 이점은 작을 수 있습니다. 후보 선택은 여전히 달라질 수 있으므로 재현율을 별도로 검증하세요.
IVF_FLAT 또는 효과적인 사전 필터가 없는 HNSW_FLATnormal 유지현재 구현에서는 모드 변경에 따른 양자화나 ACORN 이점이 없습니다. IVF_FLAT의 재현율은 탐색한 파티션에 계속 좌우됩니다.

RabitQ를 기반으로 하는 호환 HNSW 인덱스에서 fast는 ACORN 탐색을 활성화하는 동시에 해당 원시 쿼리 경로의 RabitQ 거리 추정을 단순화할 수 있습니다. 두 효과는 하나의 옵션에 묶여 있습니다. HNSW_FLAT 테스트만으로 재현율을 추정하지 말고 결합 인덱스를 별도로 검증하세요. normal과 accurate의 HNSW 탐색 정책은 같지만 RabitQ 점수 계산은 다를 수 있습니다.

권장 튜닝 순서:

  1. 동일한 데이터셋 스냅샷, 쿼리 집합, 거리 척도, 필터 및 인덱스 적용 범위를 고정합니다. use_index=false로 같은 적격 행에 대해 정확한 Top-K 기준값을 구하고, 적격 조건은 TVF filter에 넣어 prefilter=true로 설정합니다.
  2. normal 기준값을 측정합니다. Recall@K, 결과 수, 관련된 경우 점수 오차, 지연 시간 중앙값 및 p95를 측정하세요.
  3. 재현율이 부족하면 IVF nprobes와 HNSW ef를 조정합니다. 원본 벡터 점수나 재정렬할 후보가 더 필요하면 추가 벡터 읽기와 계산을 고려해 refine_factor를 설정합니다.
  4. 다른 설정을 고정한 상태에서 필터가 적용된 HNSW에는 fast를, RabitQ 추정 정밀도에는 accurate를 비교하세요. 품질 목표를 충족하고 측정한 이점이 충분할 때만 모드를 선택합니다. 차가운 캐시와 따뜻한 캐시를 별도로 측정하세요.

필터 적용 HNSW에 ACORN 활성화​

선택된 호환 인덱스에 HNSW 그래프가 있어야 합니다. approx_mode="fast"를 설정하고 조건자를 TVF 안에 넣어 prefilter="true"로 설정하세요. 외부 Doris WHERE는 이 사전 필터를 제공하지 않으며 use_index=true만으로 HNSW 인덱스를 강제로 선택할 수도 없습니다.

구문 예제의 4차원 embedding, user_id, 문자열 category 열과 기존의 호환 L2 HNSW 인덱스 및 SELECT 권한이 있는 경우:

SELECT user_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" = "10",
"filter" = "category = 'book'",
"prefilter" = "true",
"use_index" = "true",
"approx_mode" = "fast",
"ef" = "64"
)
ORDER BY _distance, user_id;

이 쿼리는 조건에 맞는 행을 최대 10개까지 거리순으로 반환합니다. 실제 ID와 재현율은 데이터 및 ANN 검색에 따라 다릅니다. EXPLAIN에는 lanceApproxMode=FAST가 표시되어 요청한 정책을 확인할 수 있지만 모든 하위 인덱스에서 ACORN이 실행되었음을 증명하지는 않습니다.

현재 네이티브 구현은 각 HNSW 하위 인덱스(IVF 파티션) 내부에서 사전 필터를 통과한 노드 수에 따라 실행 경로를 별도로 결정합니다.

실행 조건경로
효과적인 사전 필터가 없거나 모든 노드가 통과일반 HNSW 탐색.
통과하는 노드가 약 10% 미만남은 노드에 대한 Flat 점수 계산.
일부 노드만 통과하고 희소 필터 규칙은 적용되지 않음fast는 ACORN을 사용하고 normal과 accurate는 일반적인 필터 적용 그래프 탐색을 사용합니다.
ACORN이 필요한 후보를 충분히 반환하지 못하고 쿼리에 거리 경계가 없음일반적인 필터 적용 그래프 탐색으로 다시 시도합니다.

이 임계값은 전체 테이블이 아니라 각 하위 인덱스 안에서 적용됩니다. 이 인터페이스에는 ACORN을 강제하는 별도 속성이 없습니다. Doris에서는 거리 범위의 안전성 규칙에 따라 네이티브 그래프 실행 전에 Flat Search가 선택될 수도 있습니다. 반경 및 범위 검색을 참조하세요.

반경 쿼리에는 distance_upper_bound를 설정하고, [lower, upper) 구간에는 두 경계를 모두 설정하세요. 생략한 경계에는 제한이 없습니다. 거리는 선택한 거리 척도에 따릅니다. l2는 유클리드 거리의 제곱이므로 기하학적 반경 r의 상한은 r * r입니다. dot 같은 거리 척도에 유용한 음수 경계도 허용됩니다. 저장된 벡터가 Float64여도 경계는 FLOAT 정밀도를 사용합니다. 유한하지 않은 경계, 오버플로, 같은 값의 경계, 순서가 반대인 구간은 거부됩니다.

예를 들어 user_id와 3차원 Float32 embedding 열이 있는 기존 Lance Catalog 테이블 lance_catalog.default.items의 데이터가 다음과 같다고 가정합니다.

user_idembedding[0, 0, 0]까지의 제곱 L2 거리
1[0, 0, 0]0
2[1, 0, 0]1
3[2, 0, 0]4
SELECT user_id, _distance
FROM vector_search(
"table" = "lance_catalog.default.items",
"column" = "embedding",
"query_vector" = "[0, 0, 0]",
"metric" = "l2",
"top_k" = "10",
"use_index" = "false",
"distance_lower_bound" = "1",
"distance_upper_bound" = "4"
)
ORDER BY _distance;
+---------+-----------+
| user_id | _distance |
+---------+-----------+
| 2 | 1 |
+---------+-----------+

하한을 제거하면 행 1도 반환됩니다. 상한을 0으로만 설정하면 행이 반환되지 않습니다. prefilter=true이면 filter는 벡터 검색 전에 후보를 제한하고, 기본값 prefilter=false이면 후보 생성 후 필터링합니다. offset은 거리 범위를 적용한 후 결과를 건너뜁니다. 출력 스키마는 테이블 열과 FLOAT 유형의 _distance로 변하지 않습니다. EXPLAIN에는 lanceDistanceRange=[1.0, 4.0)가 표시됩니다.

거리 경계는 준비된 문장에서 ? 매개변수를 지원합니다. 실행할 때마다 해당 경계 값을 검증해 사용하며 NULL 경계는 거부합니다. 한쪽을 제한하지 않으려면 해당 속성을 생략하세요.

이는 여전히 Top-K 쿼리이며 반경 안의 모든 행을 반환하는 API가 아닙니다. 결과는 top_k로 제한되고 그보다 적을 수도 있습니다.

범위 쿼리에서 Doris는 두 경계를 모두 제공하고 선택한 인덱스가 명시적으로 IVF_FLAT으로 식별된 경우에만 인덱스 검색을 사용합니다. 단측 범위, 양자화 인덱스, 하위 유형을 알 수 없는 인덱스는 use_index=true여도 정확한 Flat Search로 전환됩니다. 범위 안전성 때문에 인덱스 선택을 막으면 EXPLAIN은 lanceVectorIndexStatus=DISTANCE_RANGE_FALLBACK을 보고합니다. 이러한 전환으로 스캔 비용이 증가할 수 있습니다.

인덱스를 사용하는 IVF_FLAT 쿼리도 nprobes로 선택한 파티션에 좌우됩니다. 모든 후보를 평가하려면 use_index=false를 사용하세요. 인덱싱되지 않은 추가 데이터는 Flat Search로 검색합니다. 다중 벡터 열은 거리 경계를 지원하지 않습니다.

지원되는 벡터 인덱스 유형​

단일 벡터 열에서 Doris는 다음 Lance 벡터 인덱스 조합을 지원합니다. 다중 벡터 인덱스의 제한은 다중 벡터 검색에 설명합니다.

인덱스 유형설명주요 쿼리 매개변수
IVF_FLAT각 파티션 안에서 원본 벡터로 거리를 계산하는 IVF 파티션nprobes
IVF_SQScalar Quantization을 사용하는 IVFnprobes, refine_factor
IVF_PQProduct Quantization을 사용하는 IVFnprobes, refine_factor
IVF_HNSW_FLAT그래프 노드에 원본 벡터가 유지되는 HNSW를 사용하는 IVFnprobes, ef
IVF_HNSW_SQScalar Quantization을 사용하는 IVF 및 HNSWnprobes, ef, refine_factor
IVF_HNSW_PQProduct Quantization을 사용하는 IVF 및 HNSWnprobes, ef, refine_factor

vector_search()는 Lance에 이미 존재하는 벡터 인덱스만 사용합니다. 인덱스를 생성하거나 사용자가 인덱스 유형 또는 이름을 지정하도록 하지는 않습니다. use_index=true이면 Doris가 벡터 열과 거리 척도에 맞는 인덱스를 자동 선택합니다. 사용할 수 있는 인덱스가 없는 데이터와 선택한 인덱스가 적용되지 않는 데이터는 자동으로 Flat Search를 사용하므로 누락되지 않습니다. use_index=false이면 모든 데이터가 Flat Search를 사용합니다.

같은 열에 호환 인덱스가 여러 개 있으면 Doris는 쿼리마다 인덱스 이름순으로 가장 앞선 하나를 선택합니다. 다른 인덱스가 Fragment를 포함하더라도 선택한 인덱스가 포함하지 않는 Fragment에는 Flat Search를 사용하므로 실행은 항상 EXPLAIN에 보고된 인덱스 범위와 일치합니다. Doris가 계획에 사용할 수 없는 인덱스는 전혀 사용하지 않고 전체 검색을 Flat Search로 수행합니다. 예를 들어 Fragment 범위 메타데이터가 없는 인덱스(lanceVectorIndexStatus=UNKNOWN_COVERAGE), 기록된 거리 척도가 없는 인덱스(METRIC_MISMATCH), 또는 인덱스 세부 정보가 없어 Lance가 파일에서도 추론할 수 없는 오래된 인덱스(NO_MATCH)가 이에 해당합니다. Lance가 세부 정보를 추론할 수 있는 오래된 인덱스는 정상적으로 사용됩니다.

지원되는 벡터 요소 유형과 거리 척도​

단일 벡터 열에서 인덱스가 지원하는 거리 척도는 벡터 요소 유형에 따라 다릅니다. 다음 표에서 지원되는 조합을 선택하세요. 지원되지 않는 조합은 벡터 인덱스를 사용할 수 없습니다.

벡터 요소 유형l2cosinedothamming
float16지원 [1]지원지원지원하지 않음
float32지원지원지원지원하지 않음
float64지원지원지원지원하지 않음
uint8지원하지 않음지원하지 않음지원하지 않음IVF_FLAT 및 IVF_HNSW_FLAT만 [2]
int8Flat Search만 [3]Flat Search만 [3]Flat Search만 [3]지원하지 않음

각주에 별도 설명이 없다면 “지원”은 위의 인덱스 유형 6개 모두에서 작동한다는 뜻입니다. “Flat Search만”은 벡터를 쿼리할 수 있지만 벡터 인덱스는 사용하지 않는다는 뜻입니다.

  1. 값이 큰 float16 데이터에 l2 인덱스를 구축하면 시간이 너무 오래 걸리거나 실패할 수 있습니다. 벡터 값을 관리 가능한 범위로 유지하세요. 인덱스 생성이 실패하면 Flat Search를 사용하거나 애플리케이션에 적합한 경우 cosine을 사용하세요.
  2. Lance는 uint8 벡터를 이진 벡터로 취급하며 hamming 거리 척도만 지원합니다. Product Quantization과 Scalar Quantization은 uint8을 지원하지 않으므로 IVF_FLAT과 IVF_HNSW_FLAT만 사용할 수 있습니다.
  3. 현재 int8 벡터는 벡터 인덱스를 사용할 수 없으며 Flat Search로 제한됩니다.

쿼리 거리 척도는 인덱스 거리 척도와 일치해야 함​

쿼리 metric은 인덱스를 구축할 때 사용한 거리 척도와 일치해야 합니다. 그렇지 않더라도 요청한 거리 척도가 열 유형에 지원된다면 Doris는 Flat Search를 사용합니다. 결과는 정확하지만 벡터를 직접 스캔해야 하므로 일반적으로 성능이 낮아집니다.

metric을 생략하면 Doris는 인덱스 선택 시 l2로 처리합니다. 따라서 cosine, dot, hamming 인덱스를 사용할 때는 metric을 명시하세요. 예를 들어 uint8 벡터 인덱스에는 "metric" = "hamming"이 필요하며, 생략하면 지원되지 않는 l2를 사용해 쿼리가 실패합니다.

EXPLAIN으로 인덱스 사용을 확인하세요. lanceSearchIndexSegments 값이 0보다 크면 인덱스 검색, 0이면 Flat Search입니다.

Doris는 벡터 열마다 벡터 인덱스를 하나만 고려합니다. 서로 다른 거리 척도로 구축한 인덱스 사이에서 선택이 모호해지지 않도록 각 벡터 열에는 인덱스를 최대 하나만 유지하세요.

다중 벡터 열은 테이블의 각 행에 문서의 토큰 임베딩처럼 여러 하위 벡터를 저장합니다. 하나의 쿼리 행렬은 테이블 행에 순위를 매긴 하나의 결과 집합을 생성합니다. 독립적인 벡터 검색을 여러 개 일괄 실행하거나 여러 열을 검색하는 기능이 아닙니다.

데이터 준비 및 쿼리 실행​

위의 AWS S3 Filesystem Catalog 예제에서 Catalog lance_fs_s3와 warehouse s3://my-bucket/lance를 사용하세요. 쓰기 환경에 Lance Python SDK(pylance)와 pyarrow를 설치합니다. 예를 들어 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION으로 S3 자격 증명과 리전을 설정합니다. writer에는 쓰기 액세스가, Doris에는 같은 위치의 읽기 액세스가 필요합니다. 사용자 지정 endpoint나 다른 인증 방식에는 Catalog와 일치하도록 SDK의 객체 스토어 옵션을 설정하세요.

Doris가 lance_fs_s3.default.documents로 검색하도록 warehouse 바로 아래에 documents.lance를 생성하세요. Catalog와 Python 예제 모두에서 버킷을 교체하고 새로운 데이터셋 위치를 사용하세요.

import lance
import pyarrow as pa

vector_type = pa.list_(pa.float32(), 2)
schema = pa.schema([
pa.field("id", pa.int64()),
pa.field("embeddings", pa.list_(
pa.field("item", vector_type, nullable=False)
)),
])
data = pa.Table.from_pylist([
{"id": 1, "embeddings": [[1.0, 0.0], [0.0, 1.0]]},
{"id": 2, "embeddings": [[1.0, 0.0]]},
{"id": 3, "embeddings": []},
{"id": 4, "embeddings": None},
], schema=schema)
lance.write_dataset(
data,
"s3://my-bucket/lance/documents.lance",
data_storage_version="2.1",
)

외부 목록을 사용하면 각 행의 하위 벡터 수가 달라도 됩니다. 내부의 nullable=False는 필수입니다. 하위 벡터 자체는 NULL일 수 없습니다. 2차원 JSON 배열로 테이블을 쿼리합니다.

SELECT id, _distance
FROM vector_search(
"table" = "lance_fs_s3.default.documents",
"column" = "embeddings",
"query_vector" = "[[1,0],[0,1]]",
"top_k" = "2",
"metric" = "l2",
"use_index" = "false"
)
ORDER BY _distance, id;

예상 결과:

id_distance
10.0
22.0

행 1에는 각 쿼리 하위 벡터와 완전히 일치하는 값이 있습니다. 행 2는 첫 쿼리 하위 벡터와 거리 0으로 일치하고, 두 번째와는 제곱 L2 거리 2를 갖습니다. 외부 목록이 비어 있거나 NULL인 행에는 순위를 매기지 않습니다. "offset" = "1"을 추가하고 "top_k" = "1"로 바꾸면 행 2만 반환됩니다. "filter" = "id > 1"을 추가하고 "prefilter" = "true"로 설정하면 일치하는 행으로 후보 검색을 제한하며, 이 데이터셋에서는 행 2만 반환됩니다.

행 단위 점수 계산​

쿼리 하위 벡터 Q와 행에 저장된 하위 벡터 V에 대해:

_distance = sum(q in Q, min(v in V, distance(q, v)))
metric두 하위 벡터 사이의 거리
l2(기본값)제곱 유클리드 거리
cosine1 - cosine_similarity
dot 또는 dot_product1 - dot_product. 음수일 수 있습니다.

_distance는 작을수록 좋습니다. 각 쿼리 하위 벡터는 한 번씩 기여하며, 저장된 하나의 하위 벡터가 여러 쿼리 하위 벡터와 일치할 수 있습니다. 점수는 평균이 아니라 합계이므로 쿼리 하위 벡터를 반복하면 기여도 반복됩니다. Float64 입력에서도 _distance의 Doris 유형은 FLOAT입니다. top_k와 offset은 하위 벡터가 아닌 테이블 행을 셉니다. Top-K 경계에서 동점인 행의 포함 여부나 순서는 보장되지 않으며 외부 ORDER BY는 반환된 행만 정렬합니다.

유형, 차원 및 NULL 값​

항목요구 사항
Lance 열list<fixed_size_list<T, D>>. 여기서 D > 0이고 T는 float16, float32, float64 중 하나입니다.
Doris 유형Float16/Float32에는 ARRAY<ARRAY<FLOAT>>, Float64에는 ARRAY<ARRAY<DOUBLE>>
쿼리 형태비어 있지 않은 JSON 행렬이며 내부 배열마다 정확히 D개 요소가 있어야 합니다. 쿼리 하위 벡터가 하나뿐이어도 [[...]]로 작성해야 합니다. 일반 단일 벡터 열에는 여전히 [...]가 필요합니다.
쿼리 값T로 표현 가능한 유한한 수입니다. 빈 행렬, 서로 다른 차원, NULL, 숫자가 아닌 요소 및 범위를 벗어난 값은 거부됩니다.
저장 값하위 벡터는 NULL 불가로 선언하고 유한하며 NULL이 아닌 요소를 포함해야 합니다. 점수 계산 경로에서 실제 NULL이나 유한하지 않은 요소를 만나면 거부합니다. 요소의 스키마 플래그가 nullable인 것만으로는 허용됩니다.
비어 있거나 NULL인 행외부 목록이 NULL 또는 []이면 일치할 하위 벡터가 없어 결과에서 제외됩니다.
Cosine의 영 노름영 노름 쌍의 cosine 거리는 정의되지 않아 일치할 수 없습니다. 쿼리 하위 벡터 중 하나라도 정의된 일치 항목이 없으면 해당 행은 제외됩니다. 따라서 영 노름 쿼리 하위 벡터가 있으면 결과가 없습니다.
지원되지 않는 형태 및 유형외부 large_list 또는 fixed_size_list, 길이가 가변적인 내부 목록, 정수 다중 벡터 요소, extension/dictionary 인코딩, 다중 벡터 hamming 검색은 지원하지 않습니다.

매개변수 제한​

M을 쿼리 하위 벡터 수, K = top_k, O = offset, R = refine_factor(다중 벡터 검색 기본값 1)로 둡니다.

제약제한
쿼리 하위 벡터 수1 <= M <= 128
결과 수와 오프셋K > 0, O >= 0
쿼리 후보 예산M × (K + O) <= 100000
재정렬 후보 예산R × (K + O) <= 100000, R > 0
명시적으로 설정한 nprobes, ef, refine_factor최대 2147483647인 양의 32비트 부호 있는 정수. 후보 예산이 refine_factor를 추가로 제한합니다.

두 후보 예산은 use_index=false에서도 각각 독립적으로 적용됩니다. 예를 들어 M=128, K=1000, O=0은 쿼리 후보 예산을 초과해 거부됩니다. M=4, K=1000, O=0, R=10은 두 예산을 모두 충족합니다. 이 제한은 후보 처리 작업량을 제한할 뿐 특정 메모리 사용량이나 쿼리 지연 시간을 보장하지 않습니다.

인덱스 사용과 재정렬​

통합된 Lance 버전은 cosine 거리 척도를 사용하는 다중 벡터 인덱스를 지원합니다. cosine IVF_FLAT 또는 IVF_PQ 같은 기존 호환 인덱스를 사용하려면 "metric" = "cosine"을 설정하고 use_index=true를 유지하세요. metric을 생략하면 l2가 선택되며, 호환 인덱스가 없으면 L2 및 dot 검색은 Flat Search를 사용합니다. Doris는 이 함수로 인덱스를 생성하지 않습니다. SHOW INDEX(Filesystem Catalog 전용)로 인덱스 메타데이터를 확인하고 EXPLAIN에서 lanceSearchIndexSegments > 0인지 확인하세요.

인덱스 검색의 후보 선택은 근사 방식입니다. refine_factor를 생략하거나 1로 설정해도 다중 벡터 후보는 항상 원본 벡터로 재정렬하므로 인덱싱된 행과 인덱싱되지 않은 행은 같은 행 단위 점수를 사용합니다. refine_factor를 늘리면 추가 벡터 읽기와 계산을 수행하며 후보 풀을 넓히지만 모든 결과의 재현을 보장하지는 않습니다. nprobes는 IVF 탐색을 제어하고 ef는 HNSW 인덱스에 적용됩니다. 재현율과 지연 시간을 측정한 후에만 조정하세요. 인덱스 생성 후 추가된 행도 Flat Search로 포함됩니다.

선택한 행을 모두 대상으로 점수를 계산하려면 "use_index" = "false"를 사용하세요. 검색 전에 후보를 제한하려면 TVF filter와 prefilter=true를 사용합니다. 기본값 prefilter=false는 후보 생성 후에 조건을 적용합니다. 외부 SQL WHERE는 생성된 후보를 Doris에서 평가하며 split의 결과를 보충하지 않습니다. 다음 절의 필터링 규칙은 단일 벡터와 다중 벡터 검색 모두에 적용됩니다.

Prefilter와 Post-filter​

Prefilter와 Post-filter를 한 쿼리에서 함께 사용할 수 있습니다.

SELECT user_id, category, _distance
FROM vector_search(
"table" = "lance_catalog.default.items",
"column" = "embedding",
"query_vector" = "[0.1, 0.2, 0.3, 0.4]",
"top_k" = "10",
"prefilter" = "true",
"filter" = "category = 'book'"
)
WHERE user_id > 100
ORDER BY _distance ASC, user_id;
필터 유형조건 예시실행 시점결과에 미치는 영향
TVF PrefilterTVF 매개변수 "filter" = "category = 'book'" 및 "prefilter" = "true"Lance가 벡터 후보를 생성하기 전category = 'book'인 행 중에서만 최근접 이웃을 검색합니다.
TVF Post-filterTVF 매개변수 "filter" = "category = 'book'", prefilter는 생략하거나 false로 설정Lance가 후보를 생성한 후생성된 후보에서 일치하지 않는 행을 제거합니다. 후보를 보충하지 않으므로 결과가 top_k행보다 적을 수 있습니다.
Doris Scan Post-filter외부 WHERE user_id > 100Lance가 후보를 생성한 뒤 Doris가 최종 TopN을 적용하기 전생성된 후보에서 일치하지 않는 행을 제거합니다. 후보를 보충하지 않으므로 최종 결과가 top_k행보다 적을 수 있습니다.

user_id > 100도 후보 생성에 반영해야 한다면 "filter" = "category = 'book' AND user_id > 100"처럼 TVF filter에 포함하고 prefilter=true를 설정하세요. 기본값 prefilter=false에서는 후보 생성 후 해당 조건을 적용합니다. 옵티마이저가 외부 WHERE를 Doris Lance Scan으로 이동하더라도 이는 Doris Scan Post-filter로 남으며 Lance Prefilter로 변환되지 않습니다.

filter에서만 참조하는 열은 Lance가 내부적으로 읽고 평가합니다. SELECT나 다른 Doris 표현식에서 해당 열을 참조하지 않으면 Doris에 반환할 필요가 없습니다.

FE 테이블 액세스 캐시​

이 설정에는 테이블 액세스 캐시 변경 사항이 포함된 Doris 빌드가 필요합니다.

각 FE는 Catalog 내에서 확인된 테이블의 Dataset URI와 액세스 옵션을 캐시하여 쿼리 계획 중 파일 시스템 검색이나 REST describeTable 요청의 반복을 피할 수 있습니다. 각 Catalog 클라이언트는 최대 10,000개 항목을 보관합니다. lance.table_access_cache_ttl_seconds의 기본값은 60이며 캐시 적중으로 수명이 연장되지는 않습니다. 이 캐시는 Dataset 버전을 저장하지 않습니다. 읽을 때마다 여전히 Dataset을 열고 스냅샷을 선택합니다.

Namespace가 버전을 관리하는 테이블은 읽을 때마다 다시 확인하므로 두 리더가 해당 읽기에 대해 Namespace가 반환한 위치와 스토리지 옵션을 사용합니다. 제공된 스토리지 옵션이 있는 응답도 expires_at_millis가 있더라도 읽을 때마다 다시 확인합니다. 사용자 정보, 쿼리 매개변수(사전 서명된 자격 증명 또는 SAS 자격 증명 포함), 프래그먼트가 포함된 URI도 캐시하지 않습니다. 고정된 자격 증명 만료 여유 시간으로는 스캔 전체에 걸친 유효성을 보장할 수 없으며 BE는 스캔 중 자격 증명을 갱신할 수 없습니다. 이러한 응답 필드가 없는 REST Catalog는 캐시를 사용할 수 있습니다.

명시적으로 테이블 또는 데이터베이스를 새로고침하면 Catalog의 모든 테이블 액세스 항목이 지워집니다. 해당 로컬 데이터베이스나 테이블 객체가 제거된 팔로어 FE에서 새로고침을 재생하는 경우도 포함됩니다. 일상적인 로컬 데이터베이스 객체 제거는 액세스 캐시를 지우지 않습니다. 캐시 무효화를 수반한 Catalog 새로고침, Namespace 제거 또는 Catalog 설정 변경도 액세스 항목을 무효화합니다. 인덱스 조회와 인덱스 작업 대상 검증은 항상 액세스를 새로 확인합니다.

원격 테이블 URI나 액세스 설정이 바뀐 후에는 TTL 만료를 기다리지 않도록 명시적으로 새로고침하세요. 기존 테이블 example_lance.default.items의 경우:

REFRESH TABLE example_lance.default.items;

다음 읽기에서는 테이블 액세스를 다시 확인합니다. 이 작업은 네이티브 Lance Session 캐시를 지우지 않습니다. 같은 URI의 Dataset을 교체한 후에는 캐시 무효화를 수반한 Catalog 새로고침을 사용하세요. 이 새로고침은 FE의 Lance Session만 지우고 BE 캐시는 지우지 않습니다. 과거 버전 또는 브랜치 검색을 참조하세요.

기존 Catalog에서 테이블 액세스 캐시를 비활성화하려면:

ALTER CATALOG example_lance SET PROPERTIES (
"lance.table_access_cache_ttl_seconds" = "0"
);

이후에는 읽을 때마다 테이블 액세스를 확인합니다. 아래에 설명하는 BE 캐시는 독립적으로 설정됩니다.

Lance Cache​

Lance Cache는 인덱스, 메타데이터 및 데이터 파일 콘텐츠를 재사용해 반복 읽기를 줄입니다. 각 BE가 자체 캐시를 관리하며 해당 BE의 Lance 쿼리가 공유합니다. BE 사이에는 캐시 용량이나 내용이 공유되지 않습니다. 일반 스캔, 벡터 검색, 전문 검색 및 두 번째 단계의 가져오기에서 추가 CREATE CATALOG 속성 없이 해당 캐시를 재사용할 수 있습니다.

캐시 유형과 범위​

캐시저장 위치캐시되는 내용
인덱스 캐시BE 메모리로드된 Lance 인덱스 콘텐츠. 인덱스를 반복 로드하는 횟수를 줄입니다.
메타데이터 캐시BE 메모리Lance 리더가 사용하는 메타데이터. 반복 읽기와 파싱을 줄입니다.
데이터 캐시BE 로컬 디스크 및 읽기 블록 하나 크기의 메모리 계층Dataset의 data/ 디렉터리 바로 아래에 있는 .lance로 끝나는 파일에서 읽은 데이터를 블록 단위로 캐시합니다.

데이터 캐시는 Lance 리더 내의 Foyer를 사용하며, 아래 lance_* BE 매개변수로 지정하는 자체 디렉터리와 용량을 갖습니다. Doris 일반 File Cache의 디렉터리나 용량 설정을 사용하지 않습니다. 인덱스 파일, manifest 및 삭제 파일은 이 디스크 데이터 캐시를 거치지 않고 기존 Lance 읽기 및 캐시 경로를 계속 사용합니다.

데이터 캐시는 전체 Dataset을 미리 다운로드하지 않고 필요할 때 채워집니다. 캐시 미스 시 원본 스토리지에서 읽고 백그라운드 디스크 캐시 쓰기를 위해 데이터를 대기열에 넣습니다. 첫 읽기에는 원본 I/O와 캐시 채우기 오버헤드가 여전히 발생합니다. 캐시 적중은 이후 원본 읽기를 줄일 수 있지만 디코딩, 필터링, 정렬 비용은 없애지 못합니다.

BE 설정​

다음 매개변수는 모두 선택 사항입니다. 각 BE의 be.conf에 설정하고 변경 사항을 적용하려면 해당 BE를 다시 시작하세요. 용량은 BE별 값이며 바이트 단위입니다.

매개변수유형기본값설명
lance_index_cache_size_bytesInt6410737418240(10 GiB)인덱스 메모리 캐시 용량. 0이면 용량이 없습니다.
lance_metadata_cache_size_bytesInt641073741824(1 GiB)메타데이터 메모리 캐시 용량. 0이면 용량이 없습니다.
enable_lance_data_cacheBooleantrue디스크 데이터 캐시를 활성화합니다. 비활성화해도 인덱스와 메타데이터 캐시는 유지됩니다.
lance_data_cache_pathString${DORIS_HOME}/lance_data_cache데이터 캐시 디렉터리. BE 프로세스에 디렉터리 생성 및 파일 읽기/쓰기 권한이 필요합니다. 서로 다른 BE 프로세스에는 별도의 디렉터리를 사용하세요.
lance_data_cache_disk_capacity_bytesInt64107374182400(100 GiB)디스크 캐시 용량. 4096의 배수여야 하며 읽기 블록 크기의 최소 2배여야 합니다.
lance_data_cache_read_block_size_bytesInt641048576(1 MiB)데이터 캐시 읽기 블록 크기. 양수이면서 4096의 배수여야 합니다. 데이터 캐시의 메모리 계층 용량도 결정합니다.

예를 들어 기본 용량을 명시하고 별도의 로컬 디스크 디렉터리에 캐시를 배치하려면:

lance_index_cache_size_bytes = 10737418240
lance_metadata_cache_size_bytes = 1073741824
enable_lance_data_cache = true
lance_data_cache_path = /mnt/ssd/lance_data_cache
lance_data_cache_disk_capacity_bytes = 107374182400
lance_data_cache_read_block_size_bytes = 1048576

BE를 다시 시작한 뒤 처음으로 Lance Dataset을 열 때 공유 캐시가 초기화됩니다. 데이터 캐시가 활성화된 상태에서 디렉터리를 사용할 수 없거나 용량 또는 블록 크기가 유효하지 않으면 초기화에 실패하고 Lance 쿼리에 오류가 발생합니다. 데이터 캐시 없이 실행하는 방식으로 자동 전환되지 않습니다. 디스크 데이터 캐시를 비활성화하려면 enable_lance_data_cache를 false로 설정하고 BE를 다시 시작하세요.

쿼리 작업 메모리와 함께 인덱스 및 메타데이터 캐시 용량을 계획하세요. 이 용량은 전체 Lance 쿼리 메모리나 BE 프로세스 메모리를 제한하지 않습니다. 디스크 캐시는 로컬 디스크 공간과 파일 설명자도 사용합니다. 읽기 블록을 크게 하면 블록 수가 줄지만 작은 읽기에는 추가 I/O가 늘 수 있습니다. 읽기 블록 크기나 디스크 구조를 변경할 때는 새 캐시 디렉터리를 사용하세요.

워크로드별 크기 산정 공식, 설정 예제, 공유 캐시 경합 및 인덱스 세그먼트 세분성은 Lance 쿼리 모범 사례를 참조하세요. 필터링과 검색을 함께 사용할 때는 Lance Hybrid Search 성능 모범 사례를 참조하세요.

캐시 효과 확인​

완료된 쿼리 Profile에서 LanceReader → LanceIndex 아래의 인덱스 캐시 카운터와 LanceReader → LanceDataCache 아래의 데이터 파일 캐시 카운터를 확인하세요.

카운터의미
LanceIndexCacheHits, LanceIndexCacheMisses계측된 인덱스 경로에서 해당 실행에 대해 보고한 캐시 조회입니다. BE 전체 Session 지표와는 별개입니다.
LanceIndexObjectLoads, LanceIndexComponentsLoaded로드된 최상위 인덱스 객체와 내부 구성 요소입니다. 어느 것도 캐시 미스 수와 같은 의미가 아닙니다.
LanceDataCacheBytesReadFromCache해당 Dataset 핸들의 데이터 파일 캐시에서 제공한 논리적 요청 바이트 수.
LanceDataCacheBytesReadFromRemote캐시 미스 또는 대체 경로 사용 후 원본 스토리지에서 제공한 논리적 요청 바이트 수. 원본은 로컬 파일일 수도 있습니다.

두 데이터 캐시 바이트 카운터에는 블록 읽기에 따른 증폭이나 인덱스 및 manifest 읽기처럼 데이터 캐시를 거치지 않는 I/O가 포함되지 않습니다. 전체 원격 트래픽 카운터가 아니며 합계가 LanceIOReadBytes와 같을 필요도 없습니다. 데이터 캐시를 비활성화하면 둘 다 0이지만 원본 읽기가 없었다는 뜻은 아닙니다. Doris는 Dataset 핸들을 닫기 전에 누적 스냅샷을 기록합니다. 두 번째 단계의 데이터 가져오기에는 별도의 리더 통계가 있고 Row ID Fetch/Materialization Profile 정보로도 내보냅니다. 스캔 카운터만으로는 쿼리 전체의 가져오기 I/O를 확인할 수 없습니다. 수집 시점과 지표 범위는 Lance 쿼리 Profile 참조를 참조하세요.

BE의 /metrics endpoint에는 doris_be_lance_session_index_cache_ 및 doris_be_lance_session_metadata_cache_로 시작하는 지표도 있습니다. 접미사 capacity_bytes, usage_bytes, entries, hits_total, misses_total은 설정한 용량, 현재 사용량, 항목 수, 누적 적중 수, 누적 미스 수를 나타냅니다. 이 지표는 공유 Lance Session이 초기화된 후 등록되며 개별 쿼리가 아니라 BE 전체 캐시를 설명합니다.

캐시 효과를 비교할 때는 Dataset 버전과 쿼리 매개변수를 같게 하고 쿼리가 실제로 같은 BE에서 실행되는지 확인한 다음, 최초 읽기와 반복 읽기의 지연 시간, 캐시 카운터 및 원본 I/O를 기록하세요. 총 지연 시간만으로는 캐시 이점과 인덱스 검색, 디코딩 또는 두 번째 단계 데이터 가져오기 비용을 구분할 수 없습니다.

2단계 TopN 읽기와 지연 구체화​

vector_search()와 full_text_search()는 최종 top_k보다 더 많은 후보 행을 생성하는 경우가 많습니다. 검색 중 title이나 payload처럼 폭이 넓은 출력 열을 읽으면 대부분의 후보 행이 TopN에서 버려지므로 작업이 낭비됩니다. 2단계 읽기는 이 열의 읽기를 미룹니다. 1단계에서는 필터링, 정렬, TopN에 필요한 열만 읽고 전역 TopN 후 2단계에서 남은 행에 대해서만 나머지 출력 열을 읽습니다.

이 방식은 스토리지 I/O, 네트워크 전송, 메모리 사용량을 줄입니다. top_k가 작거나 검색에서 후보가 많이 생성되거나 쿼리가 폭이 넓은 문자열, JSON 또는 유사한 열을 반환할 때 이점이 가장 큰 편입니다.

단계읽는 데이터
1단계검색에 내부적으로 사용되는 열, _distance 또는 _score, Post-filter나 정렬에 필요한 열, 내부 Row Location.
2단계최종 프로젝션에만 사용되는 최상위 열을 전역 TopN에 남은 행에 대해서만 읽습니다.

적용 조건​

다음 조건을 모두 충족할 때만 Doris가 2단계 읽기를 사용합니다.

  1. 쿼리가 vector_search() 또는 full_text_search()로 Lance를 읽고 계획에 지연 구체화에 적합한 TopN이 있습니다. 이러한 검색 TVF로 재작성된 일반 SQL 쿼리도 대상이 될 수 있지만, 재작성되지 않은 스칼라 테이블 스캔과 Lance 파일 TVF는 대상이 아닙니다.
  2. Boolean 세션 변수 enable_lance_lazy_materialization이 기본값인 true입니다.
  3. 읽기를 미룰 수 있는 최상위 열이 하나 이상 있습니다. 최종 SELECT 프로젝션에만 사용되는 열은 미룰 수 있습니다. Post-filter, ORDER BY 또는 TopN 이전의 다른 표현식에 사용되는 열은 1단계에서 읽어야 합니다. 현재 중첩 하위 열은 미룰 수 없습니다.

enable_lance_lazy_materialization은 topn_lazy_materialization_threshold와 독립적입니다. 후자가 -1이거나 top_k가 해당 임계값을 초과해도 Lance는 2단계 읽기를 사용할 수 있습니다. 지연 구체화를 지원하는 다른 테이블은 계속 일반 임계값을 따릅니다. 스위치를 활성화하면 최적화가 허용되지만 실제 적용 여부는 계획 구조와 적격 열에 따라 결정됩니다.

다음 쿼리에서 category는 Post-filter를 위해 1단계에서 읽어야 하지만 user_id, title, payload는 TopN 후에 가져올 수 있습니다.

SET enable_lance_lazy_materialization = true;

SELECT user_id, title, payload, _distance
FROM vector_search(
"table" = "lance_catalog.default.items",
"column" = "embedding",
"query_vector" = "[0.1, 0.2, 0.3, 0.4]",
"top_k" = "10",
"offset" = "3"
)
WHERE category = 'book';

TVF filter에서 참조하는 열은 Lance가 내부적으로 사용합니다. filter 문자열에 있다는 이유만으로 Doris에 반환되는 1단계 결과 열이 되지는 않습니다. prefilter는 후보 생성에 대한 조건 적용 시점만 변경합니다.

계획 확인​

위 쿼리 앞에 EXPLAIN VERBOSE를 붙이세요. 2단계 읽기가 활성화되면 계획에 VMaterializeNode가 포함되고 column_descs_lists에 2단계에서 가져올 열이 나열됩니다. 스위치를 비활성화한 후 같은 간단한 검색 쿼리에 EXPLAIN VERBOSE를 다시 실행하면 해당 구체화 노드가 사라져야 합니다. 활성화한 상태에서도 없다면 출력 열이 TopN 이전의 필터링, 정렬 또는 다른 계산에 사용되는지와 계획이 이 최적화를 지원하는지 확인하세요.

2단계 읽기 비활성화​

다음 세션 설정을 사용하면 2단계 읽기를 비활성화할 수 있습니다.

SET enable_lance_lazy_materialization = false;

미룰 수 있는 열이 없거나 계획이 최적화를 지원하지 않으면 Doris는 단일 단계 읽기를 사용합니다. top_k가 크다는 이유만으로 Lance의 2단계 읽기가 자동으로 비활성화되지는 않습니다. 다음 경우에는 비활성화 후 성능을 비교해 보세요.

  • top_k가 커서 2단계에서도 대부분의 후보 행을 읽는 경우.
  • 쿼리가 폭이 좁은 열 몇 개만 반환해 2단계 읽기로 절약할 I/O가 적은 경우.
  • 기반 스토리지에서 무작위 Row-ID 읽기의 지연 시간이 높아 추가 2단계 Fetch 비용이 지연 읽기로 절약한 비용보다 큰 경우.

이 최적화를 비활성화하면 출력 열을 읽는 시점만 바뀝니다. 벡터 또는 FTS 인덱스를 비활성화하거나 Prefilter, Post-filter, TopN의 의미 체계를 변경하지는 않습니다. 대표적인 쿼리와 스토리지 조건에서 두 설정을 비교하세요.

full_text_search()는 Lance 테이블의 기존 FTS 역색인 인덱스를 통해 BM25 검색을 수행하는 관계형 TVF입니다. 분석된 쿼리 용어를 OR 또는 AND로 결합하고 Phrase 쿼리로 순서와 거리를 일치시킬 수 있습니다. Doris는 물리적 인덱스 세그먼트의 후보를 전역 Top-K로 병합하고 원본 열에 _score를 추가합니다. 점수가 높을수록 관련성이 높습니다.

TVF를 사용하기 전에 Lance SDK나 다른 Lance writer로 대상 문자열 열에 FTS 역색인 인덱스를 생성하세요. Phrase 쿼리를 사용하려면 인덱스 생성 시 토큰 위치도 활성화해야 합니다. 예를 들어 Lance 인덱스 매개변수에 with_position=true를 설정합니다. Doris는 이 인덱스를 생성하지 않으며 사용 가능한 FTS 인덱스가 없을 때 행 단위 전문 스캔으로 전환하지도 않습니다.

구문 및 예제​

다음 쿼리는 범주가 storage인 문서에서 lance database를 검색한 다음 published = true를 Doris Post-filter로 적용합니다.

SELECT document_id, title, _score
FROM full_text_search(
"table" = "lance_catalog.default.documents",
"column" = "body",
"query" = "lance database",
"query_type" = "match",
"operator" = "and",
"top_k" = "10",
"offset" = "0",
"prefilter" = "true",
"filter" = "category = 'storage'",
"coverage_mode" = "strict"
)
WHERE published = true
ORDER BY _score DESC, document_id;

이 예제는 Match AND를 사용하므로 문서에 분석된 두 용어 lance와 database가 모두 있어야 합니다. prefilter=true이면 Lance는 FTS 후보를 생성하기 전에 filter를 평가합니다. prefilter를 생략하면 기본값이 false이므로 후보 생성 후 filter를 적용하며 결과가 top_k행보다 적을 수 있습니다. 외부 WHERE는 Doris Post-filter이고 제거된 후보를 보충하지 않습니다. published = true가 후보 생성에 영향을 줘야 한다면 이를 filter에 포함하고 prefilter=true로 설정하세요.

다음 Phrase 쿼리는 인접한 쿼리 용어 사이에 다른 토큰을 최대 하나 허용합니다. 예를 들어 lance search engine과 일치할 수 있습니다.

SELECT document_id, title, _score
FROM full_text_search(
"table" = "lance_catalog.default.documents",
"column" = "body",
"query" = "lance engine",
"query_type" = "phrase",
"slop" = "1",
"top_k" = "10"
)
ORDER BY _score DESC, document_id;

반환되는 관계에는 원본 Lance 테이블의 모든 열과 NULL을 허용하는 FLOAT 유형의 _score 열이 포함됩니다. 원본 테이블에 같은 이름의 열이 이미 있어서는 안 됩니다. TVF는 _score 내림차순으로 Top-K를 선택하지만 SQL 관계는 최종 표시 순서를 보장하지 않습니다. 안정적인 순서가 필요하면 ORDER BY _score DESC를 명시하고 동점에 대비해 고유 열을 추가하세요.

매개변수​

매개변수필수기본값설명
table예-Lance Catalog의 테이블을 식별하는 완전한 세 부분의 catalog.database.table 이름입니다. 다단계 Namespace가 .이 포함된 데이터베이스 이름에 매핑되면 데이터베이스 부분을 백틱으로 감싸세요. 사용자에게 테이블 SELECT 권한이 있어야 합니다.
column예-FTS 역색인 인덱스가 있는 문자열 열입니다. Doris TEXT로 매핑되는 Arrow utf8과 large_utf8을 지원합니다.
query예-비어 있지 않은 검색 텍스트입니다. FTS 인덱스를 생성할 때 설정한 토크나이저로 처리됩니다.
query_type아니요match쿼리 유형입니다. 유효한 값은 match, phrase입니다.
operator아니요orMatch 쿼리에만 적용합니다. or는 쿼리 용어 하나 이상이, and는 모든 쿼리 용어가 일치해야 합니다. Phrase 쿼리에는 설정할 수 없습니다.
slop아니요0Phrase 쿼리에만 적용합니다. 0부터 2147483647까지의 정수여야 합니다. 0은 정확한 구를 요구하고 양수는 인접한 쿼리 용어 사이에 허용할 중간 토큰의 최댓값입니다. Match 쿼리에는 설정할 수 없습니다.
top_k아니요10offset을 건너뛴 뒤 반환할 결과 수이며 양의 정수여야 합니다.
offset아니요0관련성 기준으로 건너뛸 결과 수입니다. 음수가 아닌 정수여야 하며 top_k + offset은 부호 없는 32비트 정수의 최댓값을 초과할 수 없습니다.
filter아니요-Lance SQL 조건입니다. 실행 시점은 prefilter로 제어합니다.
prefilter아니요falseTVF filter의 실행 시점을 제어합니다. true는 전문 검색 후보 생성 전에, 기본값인 false는 생성 후에 필터링하므로 결과가 top_k행보다 적을 수 있습니다. 이 매개변수는 외부 Doris WHERE에 영향을 주지 않습니다.
coverage_mode아니요strictFTS 인덱스가 현재 스냅샷의 모든 Fragment에 적용되지 않을 때의 동작입니다. 유효한 값은 strict, index_only입니다.
version, timestamp, tag, branch아니요-main의 최신 버전 대신 과거 버전, 특정 시점의 버전, 태그 또는 브랜치를 검색합니다. 과거 버전 또는 브랜치 검색을 참조하세요.

쿼리 유형​

쿼리 유형일치 동작관련 매개변수인덱스 요구 사항
Match OR분석된 쿼리 용어 하나 이상과 일치합니다. 기본값입니다.query_type=match, operator=or표준 FTS 역색인 인덱스.
Match AND분석된 쿼리 용어 모두와 일치합니다.query_type=match, operator=and표준 FTS 역색인 인덱스.
Phrase분석된 쿼리 용어의 순서를 유지하고 slop에 지정된 거리를 적용합니다.query_type=phrase, slop=NFTS 인덱스가 토큰 위치를 저장해야 합니다.

Boolean, Boost, Prefix, Wildcard, Regex 및 여러 필드에 걸친 복합 FTS 쿼리는 현재 지원하지 않습니다.

인덱스 적용 범위 모드​

모드동작사용 시점
strict선택된 FTS 인덱스가 고정된 쿼리 스냅샷의 모든 Fragment에 적용되어야 합니다. 적용되지 않은 Fragment가 하나라도 있으면 쿼리가 실패합니다.결과에서 데이터가 누락되면 안 되는 경우의 기본 권장 모드입니다. 데이터 추가 후에는 쿼리 전에 FTS 인덱스를 업데이트하거나 다시 구축하세요.
index_onlyFTS 인덱스가 적용된 Fragment만 검색하고 적용되지 않은 데이터는 무시합니다.불완전한 결과를 허용할 수 있고 인덱스 업데이트를 기다리는 동안 검색을 계속 제공해야 하는 경우에만 사용하세요.

두 모드 모두 대상 열에 적용 범위 메타데이터가 완전한 커밋된 FTS 인덱스가 필요합니다. 사용 가능한 인덱스가 없으면 쿼리가 실패합니다. 열마다 논리적 FTS 인덱스는 하나만 유지하세요. 일치하는 인덱스가 여러 개면 Doris가 모호함 없이 선택할 수 없어 쿼리를 거부합니다.

EXPLAIN으로 실행을 확인하세요. lanceFtsQueryType은 Match 또는 Phrase를, lanceFtsMatchOperator는 Match OR/AND 연산자를, lanceFtsPhraseSlop은 Phrase 거리를 표시합니다. lanceFtsCoverageMode는 적용 범위 모드를, lanceSearchIndexSegments는 사용된 물리적 FTS 인덱스 세그먼트 수를, lanceSearchUnindexedFragments는 현재 스냅샷에서 인덱스가 적용되지 않은 Fragment 수를 표시합니다.

과거 버전 또는 브랜치 검색​

기본적으로 vector_search()와 full_text_search()는 main의 최신 버전을 검색합니다. 선택적 매개변수 4개로 다른 스냅샷을 선택할 수 있으며 의미는 테이블 Time Travel 구문과 같습니다.

매개변수값동등한 테이블 구문함께 사용 가능
version양의 정수 버전FOR VERSION AS OF Nbranch
timestamp세션 시간대의 yyyy-MM-dd HH:mm:ss 또는 yyyy-MM-dd HH:mm:ss.SSSFOR TIME AS OF '...'branch
tag태그 이름@tag(name)없음
branch브랜치 이름. main은 기본 계열@branch(name)version 또는 timestamp
-- Search version 3.
SELECT id, _distance
FROM vector_search(
"table" = "lance_catalog.db.documents",
"column" = "embedding",
"query_vector" = "[0.1, 0.2, 0.3]",
"version" = "3")
ORDER BY _distance;

-- Search the branch "dev" as it was at a point in time.
SELECT id, _score
FROM full_text_search(
"table" = "lance_catalog.db.documents",
"column" = "content",
"query" = "database",
"branch" = "dev",
"timestamp" = "2026-09-20 12:00:00")
ORDER BY _score DESC;
  • 스냅샷은 브랜치를 가리키는 태그와 REST Namespace가 버전을 관리하는 테이블을 포함해 테이블 구문과 같은 규칙으로 선택합니다. version은 버전 번호만 받습니다. FOR VERSION AS OF와 달리 태그 이름을 받지 않습니다. tag 매개변수로 모호함 없이 지정할 수 있기 때문입니다. 이름이 123인 태그는 "tag" = "123"으로 선택합니다.
  • version과 timestamp는 함께 사용할 수 없고 tag는 나머지 매개변수 중 어느 것과도 함께 사용할 수 없습니다. 빈 값으로 설정한 매개변수는 무시되지 않고 오류가 됩니다.
  • 출력 스키마, 검색 열, 벡터 인덱스 및 FTS 인덱스 메타데이터, Fragment는 모두 선택된 스냅샷에서 가져옵니다. 후보 생성, 필터링, Top-K 및 2단계 읽기도 해당 스냅샷을 읽습니다. 과거 버전은 그 버전에 커밋된 인덱스를 사용합니다. 벡터 인덱스가 생성되기 전 버전은 Flat Search를 사용하고, FTS 인덱스가 생성되기 전 버전은 full_text_search()로 검색할 수 없으며, coverage_mode는 해당 버전의 Fragment를 검사합니다.
  • 선택할 수 없는 스냅샷이나 인덱스 파일이 없는 스냅샷은 오류입니다. 검색이 최신 버전으로 전환되지는 않습니다. Lance 정리 과정에서 삭제된 버전은 찾을 수 없는 것으로 보고됩니다. 태그가 유지한 버전의 인덱스 파일은 정리 중에도 보존됩니다. 다른 방식으로 인덱스 파일이 삭제되면 실행 시 데이터셋 버전이 표시된 오류가 발생합니다.
  • 준비된 문장의 각 EXECUTE를 포함해 실행할 때마다 스냅샷을 다시 선택합니다. 따라서 최신 버전, 미래 시각, 브랜치 및 이동한 태그는 실행마다 다른 버전을 선택할 수 있습니다. 네 매개변수는 상수여야 합니다. 준비된 문장에서 vector_search()는 query_vector, top_k, offset, filter에만 자리표시자(?)를 허용하고 full_text_search()는 어느 매개변수에도 허용하지 않습니다.
  • 각 검색 관계는 자체 스냅샷을 선택합니다. 한 문장에서 같은 테이블에 대한 두 관계를 사용하면, 예를 들어 vector_search()를 테이블 자체와 조인하는 사이에 새 버전이 커밋될 경우 서로 다른 버전을 읽을 수 있습니다. 같은 버전을 읽으려면 검색 함수에는 version, 테이블에는 FOR VERSION AS OF를 지정하여 둘 다 고정하세요.
  • CREATE TABLE AS SELECT는 새 테이블의 스키마와 행을 위해 쿼리를 각각 계획하므로, 변하는 선택자가 실행마다 다른 버전을 선택할 수 있습니다. 한 버전을 정확히 복사하려면 version을 설정하세요.
  • EXPLAIN에는 선택한 버전이 lanceVersion, 브랜치가 lanceBranch, REST Namespace의 버전 관리 여부가 lanceManagedVersioning으로 표시됩니다. EXPLAIN도 스냅샷을 다시 선택하므로 실제 실행에서 읽은 내용은 쿼리 Profile의 LanceDatasetVersion, LanceDatasetUri에 LanceSearchType 등 다른 Lance Scanner 정보와 함께 기록됩니다. URI에는 브랜치 디렉터리가 포함되지만 쿼리 문자열과 비밀번호는 제외됩니다.
  • 다른 브랜치나 얕은 복제본에서 생성한 브랜치는 상속한 인덱스 파일을 찾지 못할 수 있습니다. 버전 13 이전 Lance writer가 이를 중간 브랜치 아래에 기록하기 때문입니다. 이 경우 검색이 오류로 실패하며 브랜치 자체의 인덱스는 영향을 받지 않습니다.
  • 브랜치를 삭제하고 같은 이름으로 다시 만들거나 같은 URI의 Dataset을 교체하면 새 데이터가 이전 버전 번호를 재사용합니다. BE는 캐시 항목이 제거되거나 BE를 다시 시작할 때까지 해당 버전 번호에 캐시된 내용을 계속 사용할 수 있습니다. 안정적인 행 ID가 있는 Dataset에서는 벡터 및 전문 검색이 이전 데이터의 유효 행을 기준으로 필터링할 수 있어 새 데이터에서 삭제된 행이 결과에 나타나거나 현재 유효한 행이 누락될 수 있습니다. Manifest가 Lance의 이전 명명 방식(_versions/<version>.manifest)을 사용하거나 객체 스토어가 ETag를 반환하지 않으면 BE와 FE가 이전 인덱스 메타데이터를 사용할 수도 있습니다. FE는 Catalog 새로고침 후 이를 중단합니다. 재생성한 브랜치에 새 이름을 사용하거나 BE를 다시 시작하세요. Catalog 새로고침은 이러한 BE 캐시를 지우지 않습니다.
  • 토큰 위치 없이 구축한 FTS 인덱스는 Phrase 쿼리를 실행할 수 없습니다. 과거 버전에서 계속 사용될 수 있는 오래된 writer의 인덱스에는 위치 정보가 없는 경우가 많습니다.

현재 실행 모델​

vector_search()와 full_text_search()의 실행 순서는 다음과 같습니다.

Dataset snapshot selected by version / timestamp / tag / branch, or the latest version of main
-> FE search Split planning
-> Vector search: physical vector Index Segments + Flat Search for uncovered Fragments
-> Full-text search: physical FTS Index Segments (coverage checked by coverage_mode)
-> Per Split: vector/full-text search; the TVF `filter` runs before candidate generation when `prefilter=true`, and after candidate generation when `prefilter` is omitted or `false`
-> at most K+n candidates
-> Doris Scan Post-filter
-> Doris local TopN
-> Exchange
-> Doris global TopN (applies offset=n and limit=K)
-> Optional lazy-materialization Fetch

쿼리 Profile​

Profile 수집 및 확인은 쿼리 Profile 분석 가이드를 참조하세요. Lance 전용 그룹, 카운터, 측정 시간 범위 및 2단계 가져오기 통계는 Lance 쿼리 Profile 참조를 참조하세요.

현재 제한 사항 및 권장 사항​

  • Lance Catalog와 Lance TVF는 읽기 전용입니다. CREATE TABLE, INSERT, UPDATE, DELETE, TRUNCATE TABLE 및 Lance에 데이터 다시 쓰기는 지원하지 않습니다.
  • 지원되지 않는 열 유형이 있는 테이블에서는 SELECT *로 해당 열을 프로젝션하지 말고 읽을 열을 명시적으로 나열하세요.
  • 일반 스캔에서는 EXPLAIN의 lancePushdownPredicate를 확인해 어떤 조건이 푸시다운되었는지 검증하세요.
  • 인덱스를 사용하는 벡터 검색 전에 의도한 쿼리와 일치하는 벡터 인덱스를 Lance에서 생성하세요. 작은 데이터셋이나 검증에는 "use_index" = "false"로 Flat Search를 수행하세요.
  • 전문 검색 전에 Lance에서 FTS 역색인 인덱스를 생성하세요. 기본 strict 모드는 인덱스가 현재 스냅샷의 모든 Fragment에 적용되지 않으면 쿼리를 거부합니다. 데이터 누락을 허용할 수 있을 때만 index_only를 사용하세요.
  • Phrase 쿼리에는 토큰 위치를 저장한 FTS 인덱스가 필요합니다. Phrase 쿼리가 필요하면 인덱스 생성 시 with_position=true를 설정하세요.
  • 벡터 또는 전문 검색 전에 인덱스 이름, 유형, 인덱싱된 필드를 확인하려면 Filesystem Catalog 테이블에서 SHOW INDEX로 논리적 Lance 인덱스를 조회하세요. REST Catalog는 SHOW INDEX를 지원하지 않습니다.
  • 결정적인 벡터 결과 순서가 필요하면 ORDER BY _distance ASC를 명시하고 순서를 구분할 고유 열을 추가하세요.
  • 결정적인 전문 검색 결과 순서가 필요하면 ORDER BY _score DESC를 명시하고 순서를 구분할 고유 열을 추가하세요.
  • 벡터 또는 전문 검색 후보 생성 전에 필터링하려면 TVF filter와 prefilter=true를 설정하세요. 기본값 prefilter=false에서는 TVF filter가 후보 생성 후에 적용됩니다. 외부 WHERE는 각 검색 split이 생성한 후보를 전역 TopN 전에 Doris에서 평가하므로 최종 결과가 top_k행보다 적을 수 있습니다.
  • EXPLAIN으로 lanceSearchFragments와 lanceSearchIndexSegments를 확인하세요. 전자는 고정된 스냅샷에서 보이는 Fragment 수이고 후자는 FE가 선택한 물리적 인덱스 세그먼트 split 수입니다. 벡터 검색에는 대체 경로인 Flat Search Fragment split도 포함될 수 있지만 전문 검색은 Flat Search로 전환되지 않습니다.