Elasticsearch 마스터 노트 시리즈 4편. Query DSL의 JSON 기반 검색 구조, Query Context vs Filter Context의 결정적 차이, 핵심 쿼리(match·term·bool·range·exists·prefix·wildcard·fuzzy), bool 쿼리의 must·should·must_not·filter, 점수 계산 vs 필터 (성능 차이), 페이징과 정렬까지.
이 글은 Elasticsearch 마스터 노트 시리즈의 네 번째 편입니다. 1~3편이 데이터 구조였다면, 이번엔 그것을 어떻게 검색하나 — Query DSL.
JSON 기반·풍부한 표현력. 다만 match vs term, Query Context vs Filter Context 차이가 함정. 한 번 잡으면 거의 모든 검색 표현 가능.
처음 Query DSL이 어렵게 느껴지는 이유
처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, match vs term 헷갈립니다. 둘째, Query Context vs Filter Context 차이가 막연합니다.
해결법은 한 가지예요. "match = 분석 + 검색 / term = 정확 매칭" + "Query = 점수 / Filter = 필터링만" 두 줄. 이 둘만 잡으면 끝.
기본 검색
GET /products/_search
{
"query": {
"match_all": {}
}
}
모든 문서. 점수 1.0.
match vs term — 가장 중요
match — Full-text (분석)
GET /products/_search
{
"query": {
"match": { "name": "Spring Framework" }
}
}
흐름:
"Spring Framework" → analyzer → ["spring", "framework"]
→ 두 단어 중 하나라도 있는 문서 매칭
→ 점수 계산 (BM25)
text 필드용.
term — 정확 매칭 (분석 X)
GET /products/_search
{
"query": {
"term": { "category": "IT" }
}
}
흐름:
"IT" 그대로
→ category 필드에 "IT" 정확히 일치하는 문서
→ 점수 X (필터링만)
keyword·숫자·boolean·date 필드용.
여기서 정말 중요한 시험 함정 — term을 text 필드에 사용 X. text는 분석되어 저장 (["spring", "framework"]). term: "Spring Framework" = 분석 안 된 그대로 매칭 → 결과 X.
# X 잘못
"term": { "name": "Spring Framework" } # 매칭 X
# O 옳음
"match": { "name": "Spring Framework" } # 매칭 O
# O 옳음 (multi-field로 keyword 인덱스 사용)
"term": { "name.keyword": "Spring Framework" }
Query Context vs Filter Context
Query Context
"query": {
"match": { "name": "Spring" }
}
- 점수 계산 (얼마나 매칭하는가)
- 정렬 결과에 영향
- 캐싱 X
- Full-text 검색에
Filter Context
"query": {
"bool": {
"filter": [
{ "term": { "category": "IT" } }
]
}
}
- 점수 X (Yes/No만)
- 캐싱 O (재사용 빠름)
- 정확 필터링 (date·range·term)
여기서 정말 중요한 시험 함정 — 검색 = Query Context / 필터 = Filter Context. 점수 계산 비용 큼. 단순 필터링은 Filter Context가 빠름·캐시.
bool 쿼리 — 조합
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "Spring" } }
],
"filter": [
{ "term": { "category": "IT" } },
{ "range": { "price": { "gte": 10000, "lte": 50000 } } }
],
"should": [
{ "match": { "tags": "java" } }
],
"must_not": [
{ "term": { "status": "discontinued" } }
]
}
}
}
4 절
| 절 | 의미 | Context |
|---|---|---|
| must | AND·반드시 매칭 | Query (점수 ↑) |
| filter | AND·반드시 매칭 | Filter (점수 X) |
| should | OR·매칭하면 점수 ↑ | Query |
| must_not | AND·반드시 매칭 X | Filter |
여기서 정말 중요한 시험 함정 — must vs filter 차이. 의미는 같지만 (AND), filter는 점수 X·캐시. 점수 필요 = must, 단순 필터링 = filter.
should의 minimum_should_match
"bool": {
"should": [
{ "match": { "tags": "java" } },
{ "match": { "tags": "spring" } },
{ "match": { "tags": "kotlin" } }
],
"minimum_should_match": 2
}
3개 중 2개 이상 매칭 시 OK.
range 쿼리
GET /products/_search
{
"query": {
"range": {
"price": {
"gte": 10000, # >=
"lte": 50000, # <=
"gt": 0, # >
"lt": 100000 # <
}
}
}
}
date range
"range": {
"created_at": {
"gte": "2024-01-01",
"lt": "2024-12-31",
"format": "yyyy-MM-dd"
}
}
"range": {
"created_at": {
"gte": "now-7d/d", # 7일 전 (일 단위)
"lt": "now/d"
}
}
상대 시간 표현. now-N{단위} 패턴.
terms — 여러 값 OR
GET /products/_search
{
"query": {
"terms": {
"category": ["IT", "Books", "Electronics"]
}
}
}
category 가 3개 중 하나면 매칭. SQL의 IN 같음.
exists — 필드 존재
GET /products/_search
{
"query": {
"exists": { "field": "discount" }
}
}
discount 필드가 있는 문서만.
prefix·wildcard·regexp
# 접두어
"query": {
"prefix": { "name": "spr" } # "spr"로 시작
}
# 와일드카드
"query": {
"wildcard": { "name": "spr*ing" } # "spr"로 시작·"ing"로 끝
}
# 정규식
"query": {
"regexp": { "name": "[a-z]+" }
}
여기서 시험 함정이 하나 있어요. wildcard·regexp = 느림. 운영 환경 자주 사용 X. 자동완성 = N-Gram 분석기 (3편).
fuzzy — 오타 허용
"query": {
"fuzzy": {
"name": {
"value": "spring",
"fuzziness": "AUTO" # 또는 1, 2 (편집 거리)
}
}
}
spring ↔ sprng·springg 등 오타 허용. 검색 친화.
match_phrase — 정확한 구문
"query": {
"match_phrase": {
"name": "Spring Framework"
}
}
Spring Framework 정확 순서·인접. Framework Spring은 매칭 X.
multi_match — 여러 필드
"query": {
"multi_match": {
"query": "Spring",
"fields": ["name^3", "description", "tags^2"]
}
}
여러 필드 동시 검색. ^3 = 가중치 (name이 더 중요).
function_score — 점수 조작
"query": {
"function_score": {
"query": { "match_all": {} },
"functions": [
{
"filter": { "term": { "premium": true } },
"weight": 2
},
{
"field_value_factor": {
"field": "popularity",
"modifier": "log1p"
}
}
],
"boost_mode": "multiply"
}
}
비즈니스 점수 조정. 인기도·premium 등 반영.
페이징
GET /products/_search
{
"from": 0, # 시작 (0부터)
"size": 10, # 결과 수
"query": { "match_all": {} }
}
여기서 정말 중요한 시험 함정 — from + size > 10000 = 에러. ES의 deep paging 한계. 큰 페이징 = search_after 또는 scroll API 사용.
search_after — 무한 스크롤
GET /products/_search
{
"size": 10,
"query": { "match_all": {} },
"sort": [
{ "created_at": "asc" },
{ "_id": "asc" }
]
}
# 다음 페이지
GET /products/_search
{
"size": 10,
"search_after": ["2024-01-15", "doc-123"],
"sort": [...]
}
이전 페이지 마지막 정렬 값 → 다음 페이지. 깊은 페이징 OK.
정렬
GET /products/_search
{
"query": { "match_all": {} },
"sort": [
{ "price": "asc" },
{ "created_at": "desc" },
"_score"
]
}
여러 필드 정렬. _score로 점수 정렬.
결과 필드 제어
GET /products/_search
{
"_source": ["name", "price"], # 일부 필드만
"query": { "match_all": {} }
}
# 또는
"_source": false # 본문 제외 (메타만)
explain — 점수 설명
GET /products/_search
{
"explain": true,
"query": { "match": { "name": "Spring" } }
}
각 문서의 점수 계산 과정. 디버깅·튜닝.
profile — 성능 분석
GET /products/_search
{
"profile": true,
"query": { ... }
}
쿼리 단계별 시간. 느린 쿼리 분석.
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 4편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- Query DSL = JSON 기반 검색
match(분석·text) vsterm(정확·keyword)- text 필드에
term사용 X (분석된 토큰과 안 맞음) - Query Context (점수) vs Filter Context (캐시·점수 X)
- 단순 필터링 = Filter Context 빠름·캐시
- bool 쿼리 — must / filter / should / must_not
- must = 점수 / filter = 점수 X·캐시
minimum_should_match= should 최소 매칭 수- range — gte·lte·gt·lt + 날짜 (
now-7d/d) - terms = 여러 값 OR (SQL IN)
- exists = 필드 존재
- prefix·wildcard·regexp = 느림 (자동완성 X·N-Gram 권장)
- fuzzy = 오타 허용 (편집 거리 AUTO·1·2)
- match_phrase = 정확한 구문·순서·인접
- multi_match = 여러 필드 + 가중치 (
^N) - function_score = 비즈니스 점수 조정
- 페이징 —
from + size,from + size > 10000에러 - 깊은 페이징 =
search_after또는scroll API - 정렬 =
sort배열·_score가능 _source= 결과 필드 제어explain= 점수 계산 과정profile= 단계별 시간 (성능 분석)
시리즈 다른 편
- 1편 — 기본 개념·Cluster·Shard
- 2편 — Mapping·데이터 타입
- 3편 — Analyzer·Tokenizer·한국어
- 4편 — Query DSL (현재 글)
- 5편 — Full-text Search·Relevance
- 6편 — Aggregations
- 7편 — Bulk API·Reindex
- 8편 — Spring Data Elasticsearch
- 9편 — 검색 엔진 프로젝트 설계
- 10편 — Security·인증·인가·TLS
공식 문서: Query DSL 에서 더 깊이.
다음 글(5편)에서는 Full-text Search — BM25 점수 계산, relevance·boost·tie_breaker, suggester, highlighting까지 풀어 갑니다.