Elasticsearch 마스터 — Query DSL

2026-05-03확률과 통계 마스터 노트

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 (편집 거리)
    }
  }
}

springsprng·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) vs term (정확·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 = 단계별 시간 (성능 분석)

시리즈 다른 편

공식 문서: Query DSL 에서 더 깊이.

다음 글(5편)에서는 Full-text Search — BM25 점수 계산, relevance·boost·tie_breaker, suggester, highlighting까지 풀어 갑니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!