본문으로 건너뛰기
Elasticsearch Query DSL의 match, term, bool, filter 차이와 실전 검색 쿼리 구조
검색 엔진 / · 약 12분

Elasticsearch Query DSL 완벽 가이드: match·term·bool·filter 차이

“상품명에 분명히 ‘무선 키보드’가 있는데 왜 검색 결과가 0건이지?”

Elasticsearch를 처음 운영하면 이런 문제를 자주 만납니다. termtext 필드에 사용해 결과가 사라지거나, 모든 조건을 must에 넣어 불필요한 점수를 계산하거나, should가 필수 조건이라고 생각했는데 전혀 필터링되지 않는 식입니다. JSON 문법은 맞아서 오류도 나지 않지만 검색 결과의 정확도와 정렬 순서가 기대와 달라집니다.

원인은 대부분 Query DSL 자체보다 필드 매핑, 분석 여부, 실행 컨텍스트를 구분하지 않은 것에 있습니다. 이 글에서는 match, term, bool, filter의 차이를 하나의 상품 검색 예제로 연결합니다. 마지막에는 _analyze, _explain, Profile API로 결과가 이상한 이유를 확인하는 순서까지 정리합니다.

이미 인덱스와 매핑 개념이 낯설다면 Elasticsearch 입문 가이드Elasticsearch 매핑과 필드 타입을 먼저 읽어보세요. 특히 textkeyword의 차이를 알아야 이 글의 예제가 명확해집니다.

30초 결론: match·term·bool·filter는 언제 쓸까

먼저 가장 중요한 결론부터 보겠습니다.

요소핵심 역할분석 여부·점수대표 사용처
match전문 검색(Full-text search)검색어를 분석하며 보통 _score에 반영제목, 설명, 본문
term정확한 토큰 일치검색어를 분석하지 않음ID, 상태값, 카테고리, keyword 필드
bool.must반드시 만족할 검색 조건점수 계산에 참여핵심 검색어
bool.filter반드시 만족할 필터 조건점수를 계산하지 않음가격, 날짜, 재고, 권한, 상태값
bool.should선택 조건 또는 OR 조건일치할수록 점수 상승선호 태그, 최신 상품, 브랜드 부스팅
bool.must_not제외 조건필터 컨텍스트에서 실행품절·삭제·차단 문서 제외

실무에서는 다음 조합으로 시작하면 안전합니다.

GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "무선 키보드" } }
],
"filter": [
{ "term": { "status": "active" } },
{ "range": { "price": { "lte": 150000 } } }
]
}
}
}
  • 사용자가 입력한 자연어는 match
  • 정확히 같아야 하는 구조화 값은 term
  • 관련도에 영향을 주는 조건은 must 또는 should
  • 결과 포함 여부만 결정하는 조건은 filter 또는 must_not

실습용 상품 인덱스 만들기

이후 예제를 그대로 실행하려면 다음과 같이 인덱스를 만듭니다. 핵심은 nametext로 저장하면서 정확한 정렬·필터링용 keyword 하위 필드도 함께 두는 것입니다.

PUT /products-query-dsl-demo
{
"mappings": {
"properties": {
"name": {
"type": "text",
"fields": {
"keyword": { "type": "keyword" }
}
},
"description": { "type": "text" },
"sku": { "type": "keyword" },
"category": { "type": "keyword" },
"status": { "type": "keyword" },
"tags": { "type": "keyword" },
"price": { "type": "integer" },
"in_stock": { "type": "boolean" },
"created_at": { "type": "date" }
}
}
}

테스트 문서는 Bulk API로 한 번에 넣겠습니다.

POST /products-query-dsl-demo/_bulk
{ "index": { "_id": "1" } }
{ "name": "저소음 무선 기계식 키보드", "description": "사무실에서 쓰기 좋은 블루투스 키보드", "sku": "KB-WL-001", "category": "keyboard", "status": "active", "tags": ["wireless", "silent"], "price": 129000, "in_stock": true, "created_at": "2026-08-10" }
{ "index": { "_id": "2" } }
{ "name": "유선 게이밍 키보드", "description": "빠른 응답 속도와 RGB 조명을 제공하는 기계식 키보드", "sku": "KB-GM-002", "category": "keyboard", "status": "active", "tags": ["gaming", "rgb"], "price": 89000, "in_stock": true, "created_at": "2026-07-20" }
{ "index": { "_id": "3" } }
{ "name": "휴대용 무선 키보드", "description": "태블릿과 함께 쓰는 초경량 블루투스 키보드", "sku": "KB-WL-003", "category": "keyboard", "status": "discontinued", "tags": ["wireless", "portable"], "price": 49000, "in_stock": false, "created_at": "2025-11-03" }

Bulk 요청 본문은 마지막 줄바꿈까지 포함해야 합니다. 대량 색인 방식이 궁금하다면 Elasticsearch CRUD와 Bulk API 가이드에서 배치 크기와 오류 처리 방법을 함께 확인할 수 있습니다.

match query: 자연어를 분석해서 찾는다

match는 제목, 설명, 게시글 본문처럼 사람이 읽고 검색하는 텍스트에 사용하는 대표적인 전문 검색 쿼리입니다. 검색어를 필드의 analyzer로 분석한 뒤 생성된 토큰을 역색인에서 찾습니다.

GET /products-query-dsl-demo/_search
{
"query": {
"match": {
"name": "무선 키보드"
}
}
}

기본 operatorOR입니다. 분석 결과가 무선, 키보드 두 토큰이라고 가정하면 둘 중 하나만 포함해도 후보가 될 수 있습니다. 두 단어를 모두 포함한 문서만 원한다면 operator를 명시합니다.

GET /products-query-dsl-demo/_search
{
"query": {
"match": {
"name": {
"query": "무선 키보드",
"operator": "and"
}
}
}
}

여기서 중요한 점은 match가 단순한 부분 문자열 검색이 아니라는 것입니다. 색인 시점과 검색 시점의 analyzer가 텍스트를 토큰으로 변환하고, 토큰의 빈도와 문서 길이 등을 바탕으로 관련도 점수를 계산합니다.

실제 분석 토큰 확인하기

어떤 토큰이 만들어졌는지 추측하지 말고 _analyze API로 확인하세요.

POST /products-query-dsl-demo/_analyze
{
"field": "name",
"text": "Mechanical Keyboard Pro"
}

기본 standard analyzer를 사용한다면 대문자는 소문자로 정규화되고 단어 단위 토큰이 생성됩니다. 한국어 형태소 분석기를 별도로 매핑했다면 결과는 그 analyzer 설정에 따라 달라집니다.

{
"tokens": [
{ "token": "mechanical", "position": 0 },
{ "token": "keyboard", "position": 1 },
{ "token": "pro", "position": 2 }
]
}

실제 응답에는 시작·끝 오프셋, 타입, position length 같은 정보도 포함됩니다. 운영 장애를 분석할 때는 예제 출력보다 현재 인덱스의 _analyze 결과를 기준으로 판단해야 합니다.

match_phrase가 필요한 경우

단어가 등장하는 순서와 인접성까지 중요하면 match_phrase를 고려합니다.

GET /products-query-dsl-demo/_search
{
"query": {
"match_phrase": {
"description": {
"query": "블루투스 키보드",
"slop": 1
}
}
}
}

slop은 토큰 순서와 거리의 허용 범위를 넓힙니다. 값이 커질수록 더 많은 문서가 매칭될 수 있으므로 실제 검색 로그로 품질을 검증해야 합니다. 모든 검색을 phrase query로 바꾸기보다 일반 match로 후보를 찾고 should에서 구문 일치를 부스팅하는 방식이 보통 더 유연합니다.

term query: 분석하지 않고 정확한 토큰을 찾는다

term은 입력값을 analyzer에 통과시키지 않고 역색인에 저장된 정확한 term을 찾습니다. SKU, 사용자 ID, 상태 코드, 카테고리처럼 값이 정해진 keyword 필드에 잘 맞습니다.

GET /products-query-dsl-demo/_search
{
"query": {
"term": {
"sku": "KB-WL-001"
}
}
}

상태값을 필터링할 때도 같은 원리입니다.

GET /products-query-dsl-demo/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "status": "active" } },
{ "term": { "in_stock": true } }
]
}
}
}

text 필드에 term을 쓰면 왜 0건이 나올까

다음 쿼리는 JSON 문법상 정상입니다. 하지만 기대한 문서를 찾지 못할 가능성이 큽니다.

GET /products-query-dsl-demo/_search
{
"query": {
"term": {
"name": "저소음 무선 기계식 키보드"
}
}
}

nametext 필드입니다. 색인 시 analyzer가 전체 문장을 여러 토큰으로 나누지만 term은 검색 문자열을 분석하지 않습니다. 따라서 역색인에 없는 긴 문자열 전체를 하나의 term으로 찾게 됩니다.

의도에 따라 다음처럼 수정합니다.

// 전문 검색: 분석된 텍스트 토큰을 검색
{
"match": {
"name": "저소음 무선 기계식 키보드"
}
}
// 전체 값의 정확한 일치: keyword 하위 필드를 검색
{
"term": {
"name.keyword": "저소음 무선 기계식 키보드"
}
}

Query Context와 Filter Context 차이

Query DSL을 이해할 때 가장 중요한 두 번째 축은 점수를 계산하는가입니다.

Query Context: 얼마나 잘 맞는가

query 아래의 match, bool.must, bool.should 같은 조건은 일반적으로 Query Context에서 실행됩니다. 문서가 조건을 만족하는지 확인할 뿐 아니라 “얼마나 잘 맞는지”를 _score로 계산합니다.

GET /products-query-dsl-demo/_search
{
"query": {
"match": {
"description": "사무실 블루투스 키보드"
}
}
}

검색어와 더 관련 있는 문서를 위로 정렬해야 하므로 상품명·설명 검색에는 점수가 필요합니다.

Filter Context: 맞는가, 아닌가

bool.filterbool.must_not의 조건은 Filter Context에서 실행됩니다. 답은 포함 또는 제외뿐이며 관련도 점수를 계산하지 않습니다.

GET /products-query-dsl-demo/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "category": "keyboard" } },
{ "term": { "status": "active" } },
{ "range": { "price": { "gte": 50000, "lte": 150000 } } },
{ "term": { "in_stock": true } }
]
}
}
}

카테고리나 가격 조건에 “더 잘 맞는다”는 개념은 필요하지 않습니다. 필터는 점수 계산을 생략하고, 반복적으로 사용되는 조건은 Elasticsearch의 캐시 정책에 따라 재사용 대상이 될 수 있습니다.

다만 filter에 넣으면 무조건 캐시되고 항상 빨라진다고 단정하면 안 됩니다. 캐시 여부는 세그먼트와 사용 빈도 등 내부 정책의 영향을 받고, 선택도가 낮거나 데이터가 자주 바뀌는 조건에서는 기대만큼 이득이 없을 수도 있습니다. 핵심은 성능 요령보다 먼저 점수가 필요 없는 조건을 의미에 맞게 filter로 표현하는 것입니다.

bool query: must·filter·should·must_not 조합하기

bool은 여러 쿼리를 논리적으로 조합하는 compound query입니다. 각각의 절이 SQL의 AND, OR, NOT과 비슷해 보이지만 점수와 기본 동작 때문에 완전히 같지는 않습니다.

must: 필수이면서 점수에 반영

must 배열의 모든 조건을 만족해야 하며 각 조건의 점수가 최종 _score에 반영됩니다.

{
"bool": {
"must": [
{ "match": { "name": "무선 키보드" } },
{ "match": { "description": "사무실" } }
]
}
}

두 텍스트 조건 모두 관련도 정렬에 의미가 있을 때 적합합니다. 상태값이나 재고 여부까지 must에 넣을 수는 있지만, 점수가 필요 없다면 filter가 의도를 더 정확히 표현합니다.

filter: 필수이지만 점수에서 제외

filter 배열도 모든 조건을 만족해야 합니다. 차이는 _score에 영향을 주지 않는다는 점입니다.

{
"bool": {
"must": [{ "match": { "name": "무선 키보드" } }],
"filter": [{ "term": { "status": "active" } }, { "term": { "in_stock": true } }]
}
}

이 쿼리의 순위는 name의 관련도로 정하고, 판매 중이며 재고가 있는 상품만 남깁니다.

should: 선택적 부스팅과 OR

should의 동작은 형제 절의 존재에 따라 달라져 실수가 많습니다.

{
"bool": {
"should": [{ "term": { "tags": "wireless" } }, { "term": { "tags": "silent" } }]
}
}

should만 있고 mustfilter가 없다면 기본 minimum_should_match1입니다. 즉, 둘 중 하나 이상 일치해야 하므로 OR처럼 동작합니다.

반면 다음처럼 must 또는 filter가 함께 있으면 기본값은 0입니다.

{
"bool": {
"must": [{ "match": { "name": "키보드" } }],
"should": [{ "term": { "tags": "wireless" } }, { "term": { "tags": "silent" } }]
}
}

이때 should는 결과 포함 여부를 제한하지 않고 일치한 문서의 점수만 높입니다. 하나 이상 반드시 만족시켜야 한다면 명시적으로 설정합니다.

{
"bool": {
"must": [{ "match": { "name": "키보드" } }],
"should": [{ "term": { "tags": "wireless" } }, { "term": { "tags": "silent" } }],
"minimum_should_match": 1
}
}

minimum_should_match에는 정수뿐 아니라 75%, 2<-25% 같은 조건식도 사용할 수 있습니다. 다만 복잡한 값은 검색 정책을 이해하기 어렵게 만들 수 있으므로, 왜 그 기준이 필요한지 테스트 케이스와 함께 관리하세요.

must_not: 결과에서 제외

must_not은 조건을 만족하는 문서를 제외하며 Filter Context에서 실행됩니다.

{
"bool": {
"must": [{ "match": { "name": "키보드" } }],
"must_not": [
{ "term": { "status": "discontinued" } },
{ "term": { "tags": "refurbished" } }
]
}
}

삭제 상태, 차단된 사용자, 접근 금지 문서처럼 순위와 무관한 제외 규칙에 적합합니다.

실전 패턴 1: 전문 검색과 상품 필터 결합

전자상거래 검색에서 가장 흔한 형태입니다. 상품명과 설명은 관련도를 계산하고, 판매 상태·카테고리·가격·재고는 필터링합니다.

GET /products-query-dsl-demo/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "저소음 무선 키보드",
"fields": ["name^3", "description"],
"operator": "and"
}
}
],
"filter": [
{ "term": { "category": "keyboard" } },
{ "term": { "status": "active" } },
{ "term": { "in_stock": true } },
{ "range": { "price": { "lte": 150000 } } }
]
}
}
}

name^3은 상품명 일치가 설명 일치보다 점수에 더 크게 반영되도록 부스팅합니다. 숫자 3이 모든 서비스에 정답이라는 뜻은 아닙니다. 검색 로그와 클릭·구매 같은 품질 지표를 보고 조정해야 합니다.

실전 패턴 2: 정확한 식별자와 상태 조회

관리자 화면에서 SKU로 상품을 찾을 때는 전문 검색보다 정확한 일치가 중요합니다.

GET /products-query-dsl-demo/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "sku": "KB-WL-001" } },
{ "terms": { "status": ["active", "paused"] } }
]
}
}
}

한 값은 term, 여러 정확한 값 중 하나는 terms를 사용합니다. 애플리케이션에서 SKU의 대소문자와 공백을 먼저 정규화하면 운영 중 “값은 같은데 검색되지 않는” 문제를 줄일 수 있습니다.

실전 패턴 3: 필수 조건은 유지하고 선호도만 높이기

검색 결과에는 모든 키보드를 포함하되 무선·저소음 상품을 위로 올리고 싶다고 가정해 보겠습니다.

GET /products-query-dsl-demo/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "키보드" } }
],
"filter": [
{ "term": { "status": "active" } }
],
"should": [
{ "term": { "tags": { "value": "wireless", "boost": 2.0 } } },
{ "term": { "tags": { "value": "silent", "boost": 1.5 } } },
{ "match_phrase": { "name": { "query": "무선 키보드", "boost": 3.0 } } }
]
}
}
}

mustfilter가 있으므로 should는 기본적으로 선택 조건입니다. 선호 태그가 없어도 검색 결과에는 들어오지만, 일치하면 점수가 올라갑니다. “적어도 한 가지 선호 조건을 만족해야 한다”는 정책이라면 minimum_should_match: 1을 추가해야 합니다.

실전 패턴 4: 기간·가격·존재 여부 제한

구조화된 범위 조건은 일반적으로 filter에 배치합니다.

GET /products-query-dsl-demo/_search
{
"query": {
"bool": {
"filter": [
{
"range": {
"created_at": {
"gte": "now-90d/d",
"lt": "now+1d/d"
}
}
},
{
"range": {
"price": {
"gte": 50000,
"lt": 150000
}
}
},
{ "exists": { "field": "description" } }
]
}
}
}
  • gte: 이상
  • gt: 초과
  • lte: 이하
  • lt: 미만

날짜 수학의 now는 요청 시점을 기준으로 하므로 결과와 캐시 특성이 고정 날짜 조건과 다를 수 있습니다. 배치 보고서처럼 동일한 결과를 재현해야 한다면 애플리케이션에서 기준 시각을 계산해 절대 시각으로 전달하는 방법도 고려하세요.

실전 패턴 5: 문서 접근 권한 필터링

검색 품질만큼 중요한 것이 보안입니다. 접근 권한은 점수와 무관한 필수 조건이므로 filter에 둡니다.

GET /documents/_search
{
"query": {
"bool": {
"must": [
{ "match": { "content": "장애 대응 절차" } }
],
"filter": [
{ "term": { "tenant_id": "tenant-42" } },
{
"bool": {
"should": [
{ "term": { "visibility": "public" } },
{ "terms": { "allowed_group_ids": ["ops", "backend"] } },
{ "term": { "owner_id": "user-1004" } }
],
"minimum_should_match": 1
}
}
],
"must_not": [
{ "term": { "deleted": true } }
]
}
}
}

권한 조건에서는 minimum_should_match를 빼먹으면 정보 노출로 이어질 수 있습니다. 특히 바깥 boolmustfilter가 있을 때 안쪽 should까지 선택 조건으로 오해하지 않도록, 권한 OR 블록 자체에 필수 개수를 명시하는 편이 안전합니다.

검색 결과가 이상할 때 확인할 4단계

Query DSL 문제는 쿼리를 무작정 바꾸기보다 매핑 → 분석 토큰 → 개별 문서 → 실행 비용 순서로 좁히면 빠르게 찾을 수 있습니다.

1단계: 실제 매핑 확인

인덱스 템플릿을 수정했더라도 이미 생성된 인덱스의 매핑은 다를 수 있습니다. 별칭이 여러 인덱스를 가리키는 경우도 있으므로 실제 대상에서 확인합니다.

GET /products-query-dsl-demo/_mapping/field/name*

확인할 항목은 다음과 같습니다.

  • 필드가 text인지 keyword인지
  • 검색하려는 .keyword 하위 필드가 실제로 존재하는지
  • 색인·검색 analyzer가 무엇인지
  • 숫자와 날짜가 문자열로 잘못 매핑되지 않았는지
  • 동적 매핑 때문에 예상과 다른 타입이 생성되지 않았는지

2단계: _analyze로 토큰 확인

검색어와 대표 문서 값이 각각 어떤 토큰으로 분석되는지 비교합니다.

POST /products-query-dsl-demo/_analyze
{
"field": "name",
"text": "저소음 무선 키보드"
}

동의어, 불용어, 형태소 분석기, edge n-gram을 사용한다면 예상하지 못한 토큰이 생기거나 필요한 토큰이 사라질 수 있습니다. 분석 결과가 잘못됐다면 Query DSL을 복잡하게 만들기 전에 analyzer와 매핑을 고쳐야 합니다.

3단계: _explain으로 한 문서 확인

특정 문서가 왜 검색되거나 검색되지 않는지 확인할 때 Explain API를 사용합니다.

GET /products-query-dsl-demo/_explain/1
{
"query": {
"bool": {
"must": [
{ "match": { "name": "무선 키보드" } }
],
"filter": [
{ "term": { "status": "active" } }
]
}
}
}

응답의 matched로 최종 일치 여부를 확인하고, explanation 트리에서 어떤 절이 점수에 기여했는지 살펴봅니다. Explain은 한 문서를 깊게 분석할 때 유용하지만 대량 요청 경로에 상시 적용할 기능은 아닙니다.

4단계: Profile API로 느린 구간 확인

결과는 맞지만 느리다면 대표 쿼리에 profile: true를 추가합니다.

GET /products-query-dsl-demo/_search
{
"profile": true,
"query": {
"bool": {
"must": [
{ "match": { "name": "무선 키보드" } }
],
"filter": [
{ "range": { "price": { "lte": 150000 } } }
]
}
}
}

Profile 응답은 샤드별 query와 collector의 세부 실행 시간을 보여줍니다. 다만 프로파일링 자체에 오버헤드가 있으므로 일반 사용자 요청에 켜두지 말고, 운영과 유사한 데이터에서 제한적으로 사용하세요. 병목 분석 방법은 Elasticsearch 느린 쿼리 최적화 가이드에서 slow log, 샤드, 집계 문제까지 더 자세히 다룹니다.

자주 하는 Query DSL 실수 7가지

1. text 필드에 term 사용하기

긴 자연어를 분석 없이 찾기 때문에 결과가 없거나 예상과 다릅니다. 전문 검색은 match, 전체 값의 정확한 일치는 keyword 필드의 term을 사용하세요.

2. 필터 조건까지 전부 must에 넣기

상태·가격·재고처럼 순위와 무관한 조건까지 Query Context에 두면 쿼리의 의도가 흐려지고 불필요한 점수 계산이 생깁니다. 점수가 필요 없는 필수 조건은 filter로 옮깁니다.

3. should가 항상 필수라고 생각하기

mustfilter가 함께 있으면 should의 기본 minimum_should_match0입니다. 필수 OR 조건이라면 값을 명시하세요.

4. keyword 값의 대소문자와 공백을 무시하기

term은 분석하지 않은 정확한 term을 찾습니다. 색인 전 정규화 정책과 normalizer를 설계하고, 실제 저장값을 확인하세요.

5. 매핑을 확인하지 않고 쿼리만 수정하기

같은 필드 이름도 인덱스별 타입이 다를 수 있습니다. alias 뒤에 여러 세대의 인덱스가 있다면 매핑 충돌까지 점검해야 합니다.

6. bool을 지나치게 깊게 중첩하기

복잡한 논리를 표현할 수 있지만 깊은 중첩은 사람이 검토하기 어렵고 실행 비용도 커질 수 있습니다. 같은 의미라면 절을 평평하게 구성하고, 권한 블록처럼 논리적 경계가 필요한 부분만 중첩하세요.

7. Profile 결과 하나로 최적화를 확정하기

샤드 상태, 캐시, 데이터 분포, 동시 요청에 따라 지연 시간이 달라집니다. 워밍업 전후와 여러 대표 검색어를 비교하고, p95·p99 지연 시간과 검색 품질을 함께 측정하세요.

운영 성능 체크리스트

Query DSL을 배포하기 전 다음 항목을 확인하면 정확도와 성능 문제를 동시에 줄일 수 있습니다.

  • 자연어 필드는 text, 식별자·상태·카테고리는 keyword 등 목적에 맞게 매핑했는가?
  • 전문 검색은 match/multi_match, 정확한 일치는 term/terms를 사용했는가?
  • 점수가 필요 없는 조건을 bool.filter 또는 must_not에 배치했는가?
  • should가 선택 조건인지 필수 OR 조건인지 확인하고 minimum_should_match를 명시했는가?
  • _analyze로 실제 토큰을 확인했는가?
  • 대표 문서에 _explain을 실행해 결과와 점수 근거를 검증했는가?
  • 운영과 유사한 데이터에서 Profile과 slow log를 확인했는가?
  • 검색 결과 품질을 판단할 대표 검색어와 기대 문서 목록이 있는가?
  • 권한·테넌트 필터를 사용자가 우회할 수 없도록 서버에서 강제하는가?
  • 깊은 bool 중첩, 고비용 wildcard·script query가 정말 필요한지 검토했는가?

match와 term은 언제 각각 써야 하나요?

사람이 입력한 문장이나 단어를 분석해 제목·설명·본문에서 찾을 때는 match, SKU·상태값·카테고리처럼 저장된 정확한 값을 찾을 때는 term을 사용합니다. 필드 타입으로 보면 text에는 보통 match, keyword에는 보통 term이 출발점입니다.

단, term은 “문자열 전체가 무조건 같다”가 아니라 역색인에 저장된 정확한 term을 찾는 쿼리입니다. 어떤 term이 저장되는지는 필드 매핑과 analyzer 또는 normalizer에 따라 결정됩니다.

must와 filter는 결과가 같은데 무엇이 다른가요?

둘 다 필수 조건이라 결과 문서 집합은 같을 수 있지만 must는 Query Context에서 관련도 점수에 기여하고, filter는 Filter Context에서 포함 여부만 판단합니다. 점수가 순위에 필요하지 않은 가격·날짜·상태·권한 조건은 대체로 filter가 적합합니다.

필터 조건은 반복 사용될 때 캐시 후보가 될 수 있지만 모든 필터가 항상 캐시되는 것은 아닙니다. 성능 효과는 실제 데이터와 요청 패턴으로 측정해야 합니다.

should는 OR 조건인가요, 점수 부스팅인가요?

둘 다 가능합니다. bool 안에 should만 있으면 기본적으로 하나 이상 일치해야 하므로 OR 조건처럼 동작합니다. 하지만 mustfilter가 함께 있으면 기본 minimum_should_match0이어서 선택적 점수 부스팅으로 동작합니다.

정책상 하나 이상 일치해야 한다면 형제 절의 존재에 기대지 말고 minimum_should_match: 1을 명시하세요.

filter를 사용하면 항상 검색이 빨라지나요?

아닙니다. filter는 점수 계산을 생략하고 반복 조건이 캐시 대상이 될 수 있어 유리하지만, 실제 속도는 데이터 분포·샤드·세그먼트·요청 빈도와 조건 종류에 따라 달라집니다. filter는 먼저 의미가 맞기 때문에 사용하고, 성능은 Profile과 운영 지표로 확인해야 합니다.

now를 사용하는 범위 조건이나 값이 계속 달라지는 필터는 고정된 반복 조건과 캐시 특성이 다를 수 있습니다. “filter니까 빠르다”보다 어떤 절이 시간을 쓰는지 측정하는 습관이 중요합니다.

match query에 keyword 필드를 사용해도 되나요?

실행은 가능하지만 정확한 값 비교가 목적이라면 term이 의도를 더 분명하게 드러냅니다. match는 입력값을 분석하는 전문 검색 쿼리이며, keyword 필드는 일반적으로 분석된 자연어 검색보다 집계·정렬·정확한 필터링을 위해 설계합니다.

대소문자 무시, 부분 검색, 자동완성이 필요하다면 쿼리 하나로 우회하기보다 normalizer, 별도 text 하위 필드, edge n-gram 등 요구사항에 맞는 매핑을 검토하세요.

OpenSearch에서도 같은 Query DSL을 사용할 수 있나요?

match, term, bool, Query/Filter Context 같은 핵심 개념과 기본 JSON 구조는 OpenSearch에서도 대체로 유사합니다. 다만 Elasticsearch와 OpenSearch는 별도로 발전하고 있어 버전별 파라미터, 플러그인, 기본값, 지원 기능이 달라질 수 있습니다.

운영 환경에서는 이 글의 원리를 적용하되 현재 사용하는 OpenSearch 버전의 공식 문서를 함께 확인하고 회귀 테스트를 실행하세요.

마무리: 쿼리 문법보다 의도를 먼저 나누자

Elasticsearch Query DSL을 안정적으로 작성하는 핵심은 쿼리를 외우는 것이 아니라 조건의 의도를 구분하는 것입니다.

  1. 자연어 검색인가, 정확한 값 비교인가?matchterm을 선택합니다.
  2. 순위에 영향을 줘야 하는가? — Query Context와 Filter Context를 나눕니다.
  3. 필수·선택·제외 중 무엇인가?must, filter, should, must_not을 배치합니다.
  4. 결과가 이상한가? — 매핑, _analyze, _explain, Profile 순서로 확인합니다.

가장 실용적인 기본형은 전문 검색을 must에, 구조화된 제한 조건을 filter에, 선호도 조정을 should에 두는 것입니다. 여기에 minimum_should_match를 명시하면 검색 정책이 코드에 분명하게 드러납니다.

함께 보면 좋은 글

공식 문서

My avatar

글을 마치며

이 글이 도움이 되었기를 바랍니다. 궁금한 점이나 의견이 있다면 댓글로 남겨주세요.

더 많은 기술 인사이트와 개발 경험을 공유하고 있으니, 다른 포스트도 확인해보세요.

유럽살며 여행하며 코딩하는 노마드의 여정을 함께 나누며, 함께 성장하는 개발자 커뮤니티를 만들어가요! 🚀


Elasticsearch 검색 엔진 마스터 시리즈
Elasticsearch 검색 엔진 입문 가이드
· 약 12분

Elasticsearch 입문: 검색 엔진이 필요한 이유

RDBMS의 LIKE 검색이 왜 프로덕션에서 문제가 되는지, 역인덱스가 무엇인지, 그리고 Elasticsearch가 어떻게 이 문제를 해결하는지 실제 장애 사례와 함께 알아봅니다.

백엔드 데이터베이스 프로덕션 +3
Elasticsearch 매핑과 필드 타입 가이드
· 약 11분

Elasticsearch 매핑: 필드 타입과 스키마 설계

Elasticsearch의 매핑을 이해하고 올바른 필드 타입을 선택하는 방법을 알아봅니다. text vs keyword, 동적 매핑의 함정, 매핑 폭발 방지, 중첩 객체 처리까지 실무 설계 패턴을 다룹니다.

Elasticsearch OpenSearch 검색엔진 +3
Elasticsearch Docker Kubernetes 설치 가이드
· 약 9분

Elasticsearch 설치: Docker와 Kubernetes 환경 구축

Docker Compose로 로컬 개발 환경을, Kubernetes Helm Chart로 프로덕션 클러스터를 구축합니다. 보안 설정, 볼륨 마운트, 리소스 제한까지 실무에서 필요한 모든 설정을 다룹니다.

Elasticsearch OpenSearch Helm
Elasticsearch CRUD와 Bulk API 가이드
· 약 12분

Elasticsearch CRUD: 문서 색인과 Bulk API 최적화

Elasticsearch에서 문서를 생성, 조회, 수정, 삭제하는 방법과 대량 데이터 처리를 위한 Bulk API 최적화 전략을 알아봅니다. refresh 동작, 라우팅, 버전 관리까지 실무 팁을 다룹니다.

백엔드 Elasticsearch OpenSearch +3