필터링
필터링 (Filtering)
벡터 하나로 객체의 모든 특징을 표현할 수는 없어요. 재고 여부, 사용자 위치, 원하는 가격대 같은 것은 임베딩에 담기기 어려운 정보죠. Qdrant에서는 검색이나 포인트 조회 시점에 조건을 걸어서 이런 비즈니스 규칙을 적용할 수 있어요. 바로 이게 필터링이고, payload와 포인트의 id 모두에 조건을 붙일 수 있어요.
출처: 공식문서 - Filtering
필터링 성능을 위해선 필터링할 필드에 payload 인덱스를 만들어두는 게 좋아요. 가장 좋은 결과를 얻으려면 데이터를 ingest하기 전에 인덱스를 만들어두는 걸 권장해요.
필터링 절 (Filtering clauses)
Qdrant는 조건을 절(clause)로 조합하게 해줘요. 절은 OR, AND, NOT 같은 논리 연산이고, 재귀적으로 중첩해서 임의의 불리언 표현식을 만들 수 있어요. 예를 들어 다음 payload를 가진 포인트들이 있다고 볼게요:
[
{ "id": 1, "city": "London", "color": "green" },
{ "id": 2, "city": "London", "color": "red" },
{ "id": 3, "city": "London", "color": "blue" },
{ "id": 4, "city": "Berlin", "color": "red" },
{ "id": 5, "city": "Moscow", "color": "green" },
{ "id": 6, "city": "Moscow", "color": "blue" }
]
Must
must 안의 모든 조건이 충족될 때만 절이 true가 돼요. AND 연산자와 동일하죠. 위 데이터에서 must로 조건을 걸면 { "id": 2, "city": "London", "color": "red" }가 결과예요.
Should
should는 안에 나열된 조건 중 하나 이상이 충족되면 true가 돼요. OR 연산자와 동일해요. Tip: should 안의 모든 조건이 같은 필드를 대상으로 한다면 match any를 쓰는 게 훨씬 빠르고, 특히 값이 많을 때 효과가 커요.
Must Not
must_not은 안에 나열된 조건이 하나도 충족되지 않을 때 true가 돼요. (NOT A) AND (NOT B) AND (NOT C)와 동일하죠.
절 조합
여러 절을 동시에 쓸 수도 있고, 재귀적으로 중첩할 수도 있어요. 조건들이 AND로 결합되는 식이죠.
필터링 조건 (Filtering conditions)
payload의 값 타입마다 적용할 수 있는 조건이 달라져요. 어떤 조건들이 있는지 살펴볼게요.
Match
저장된 값이 주어진 값과 같은지 확인하는 가장 단순한 조건이에요. 여러 값이 저장돼 있으면 그중 하나라도 일치하면 돼요. keyword, integer, bool payload에 적용할 수 있어요.
Match Any
v1.1.0부터
저장된 값이 여러 값 중 하나인지 확인할 때 써요. 주어진 값들에 대한 논리적 OR로, IN 연산자로도 표현할 수 있어요. keyword와 integer payload에 적용할 수 있어요. 값이 배열이면 주어진 값 중 하나와 일치하는 값이 하나라도 있으면 충족돼요.
Match Except
v1.2.0부터
저장된 값이 주어진 여러 값 중 하나가 아닌지 확인할 때 써요. 논리적 NOR로, NOT IN 연산자로도 표현할 수 있어요. keyword와 integer payload에 적용할 수 있어요.
Nested key
v1.1.0부터
payload는 임의의 JSON 객체라 중첩 필드에 필터링해야 할 때가 많아요. 이를 위해 Jq 프로젝트와 비슷한 점 표기(dot notation) 문법을 써요. 중첩 필드는 country.name처럼 점으로 접근하고, 배열 안의 값을 펼쳐 보려면 [] 문법을 씁니다. 예를 들어 country.cities[].population처럼 배열 요소를 투영할 수 있어요. 마지막 중첩 필드가 배열인 경우도 지원돼요.
Nested object filter
v1.2.0부터
기본적으로 조건은 포인트의 전체 payload를 고려해요. 그래서 배열 안의 서로 다른 요소에 각각 걸린 조건이 우연히 "한 포인트 안의 다른 요소들"에서 충족되면 그 포인트가 통과할 수 있죠. 배열 요소 단위로 조건을 독립 적용하고 싶다면 nested 조건 타입을 써요. nested는 포커스를 둘 payload 키와 적용할 필터로 구성되는데, 키는 객체의 배열을 가리켜야 해요. "data" 또는 "data[]"처럼 괄호 표기 유무 모두 사용할 수 있어요. 부모 문서는 배열의 요소 중 하나라도 중첩 필터를 만족하면 일치한 것으로 봐요.
제한 사항: has_id와 slice 조건은 중첩 객체 필터 안에서 지원되지 않아요. 필요하면 인접한 must 절에 두세요.
Prefix Match
v1.19.0부터
keyword 값이 지정한 문자열로 시작하는지 매칭하는 조건이에요. 예를 들어 prefix "https://qdrant."는 "https://qdrant.tech/documentation"와 일치하지만 prefix "qdrant"는 일치하지 않아요. 매칭은 바이트 단위(유효한 UTF-8 문자열이면 문자 단위)이고 대소문자를 구분해요. Full Text Match와 달리 토큰화를 하지 않으므로 URL, 경로, SKU 같은 식별자에 잘 맞아요. 효율적인 prefix 매칭을 위해선 해당 필드에 prefix 옵션이 켜진 keyword 인덱스를 만들어두세요.
Full Text Match
v0.10.0부터
match 조건의 특수한 경우인 text 조건이에요. 텍스트 필드 안에서 특정 부분 문자열·토큰·구(phrase)를 검색하게 해줘요. 일치하는 정확한 텍스트는 full-text 인덱스 설정에 의존하며, 필드에 full-text 인덱스가 없으면 기본 토크나이저를 사용해요. 쿼리에 여러 단어가 있으면 그 단어가 모두 텍스트에 존재해야 조건이 충족돼요.
Full Text Any
v1.16.0부터
text_any 조건은 text 조건과 비슷하지만 핵심 차이가 있어요. text가 쿼리 용어를 모두 포함한 필드만 매칭하는 반면, text_any는 아무 하나라도 포함한 필드를 매칭해요. 예를 들어 good cheap 쿼리는 cheap hardware는 물론 good performance도 매칭돼요.
Phrase Match
v1.15.0부터
phrase 조건은 full-text 인덱스를 활용해 정확한 구 비교를 수행해요. 텍스트 필드 안의 특정 토큰 구를 검색하게 해주죠. 예를 들어 "quick brown fox"는 "brown fox" 쿼리와 매칭되지만 "fox brown"과는 매칭되지 않아요. 주의: 인덱스가 phrase_matching 파라미터를 true로 설정해야 phrase 조건이 동작해요. 꺼져 있으면 phrase 조건은 아무것도 매칭하지 않아요.
Range
range 조건은 저장된 payload 값의 가능한 범위를 지정해요. 여러 값이 저장돼 있으면 하나라도 일치하면 돼요. 사용 가능한 비교는 다음과 같아요:
gt- 초과 (greater than)gte- 이상 (greater than or equal)lt- 미만 (less than)lte- 이하 (less than or equal)
float와 integer payload에 적용할 수 있어요.
Datetime Range
v1.8.0부터
datetime payload를 위한 고유한 range 조건이에요. RFC 3339 형식을 지원해서 날짜를 UNIX 타임스탬프로 변환할 필요가 없어요. 비교 중에 타임스탬프가 파싱되어 UTC로 변환돼요.
UUID Match
v1.11.0부터
UUID 값 매칭은 문자열에 대한 일반 match 조건과 비슷하게 동작해요. keyword와 uuid 인덱스에서 기능적으로 동일하게 동작하지만, uuid 인덱스가 메모리 효율이 더 좋아요.
Geo (지리 조건)
- Geo Bounding Box —
top_left(왼쪽 위 모서리)와bottom_right(오른쪽 아래 모서리) 좌표로 만든 직사각형 안의location매칭 - Geo Radius —
center를 중심으로radius미터 반지름 원 안의location매칭 - Geo Polygon — 불규칙한 모양의 영역(예: 국가 경계, 산림 경계) 안의 포인트를 찾을 때 유용해요. 폴리곤은 항상 외부 링(exterior ring)을 가지며 내부 링(interior ring)을 선택적으로 가질 수 있어요(호수 위의 섬 같은 경우). 링의 첫 번째와 마지막 포인트는 같아야 해요. 일치는 폴리곤 외부 경계 안(경계 포함)이면서 어떤 내부 링 안쪽은 아닌 위치로 판정돼요.
여러 위치 값이 저장돼 있으면 그중 하나라도 매칭되면 결과 후보에 포함돼요. 이 조건들은 geo-data 형식에 맞는 payload에만 적용할 수 있어요.
Values count
값의 직접 비교 외에도 값의 개수로 필터링할 수 있어요. 예를 들어 댓글이 2개보다 많은 항목만 검색할 수 있죠. 저장된 값이 배열이 아니면 값 개수가 1이라고 간주해요.
Is Empty
어떤 값이 빠진 레코드를 걸러낼 때 써요. IsEmpty 조건은 필드가 없거나 null 또는 [] 값을 가진 모든 레코드를 매칭해요. 논리 부정 must_not과 함께 쓰면 비어있지 않은 값을 모두 선택할 때 자주 유용해요.
Is Null
match 조건으로는 NULL 값을 테스트할 수 없어요. 그래서 IsNull 조건을 써야 해요. 이 조건은 필드가 존재하고 NULL 값을 가진 모든 레코드를 매칭해요.
Has id
payload와는 무관한 조건이지만 상황에 따라 매우 유용해요. 사용자가 특정 검색 결과를 무관하다고 표시하거나, 지정한 포인트만 검색하고 싶을 때 써요.
Has vector
v1.13.0부터
포인트에 주어진 named vector가 존재하는지로 필터링하는 조건이에요. 예를 들어 컬렉션에 image(size 4, distance Dot)와 text(size 8, distance Cosine) 두 named vector가 있고, 어떤 포인트는 모든 벡터를, 어떤 포인트는 일부만 가질 때 image 벡터가 정의된 포인트만 검색할 수 있어요. 컬렉션에 named vector가 없다면 빈("") 이름을 사용하세요.
Slice
v1.19.0부터
slice 조건은 컬렉션을 특정 개수의 결정적·비겹치는 부분집합으로 나누고 그중 하나의 부분집합에 속한 모든 포인트를 매칭해요. export, migration, re-embedding처럼 컬렉션의 여러 부분집합을 병렬로 scroll할 때 유용해요. 각 워커에 서로 다른 index를 주면 각자 별도 부분집합을 스캔하게 할 수 있죠. random sampling과 달리 주어진 slice는 항상 같은 부분집합을 반환하므로 재현율 평가, train/test 분할, canary rollout에도 재현 가능한 샘플로 쓸 수 있어요.
슬라이싱은 포인트 ID의 해시 기반이에요. 포인트가 total 중 index 슬라이스에 속할 조건은 hash(id) % total == index예요. 해시는 ID 바이트에 대한 zero key의 SipHash-2-4이며 Qdrant 버전이 바뀌어도 변하지 않아요. total이 다른 슬라이스들은 서로 상관관계가 있어요.
더 알아보기 (Learn more)
- 필터링 성능을 위한 payload 인덱스
- 벡터 검색 + 필터를 하나의 Search API로 조합하기
- 희소·밀집 벡터를 함께 쓰는 하이브리드 쿼리
- 벡터 검색 필터링의 심층 가이드 A Complete Guide to Filtering in Vector Search