_search API로 검색하기

_search API로 검색하기

검색은 하나 이상의 쿼리를 조합해 Elasticsearch로 보내는 일이에요. 쿼리에 맞는 문서는 응답의 hits, 즉 검색 결과로 돌아오죠. Elasticsearch의 검색 REST API인 _search는 데이터 스트림이나 인덱스에 저장된 데이터를 검색하고 집계까지 처리해요. 요청 바디의 query 파라미터에는 쿼리 DSL 문법으로 쿼리를 적어 넣어요.

출처: 공식문서 — The _search API

검색 실행하기

가장 기본적인 예를 볼게요. 아래 요청은 my-index-000001 인덱스에서 user.id 값이 kimchy인 문서를 찾아요. match 쿼리를 쓰고 있죠.

{
  "query": {
    "match": {
      "user.id": "kimchy"
    }
  }
}

API 응답은 쿼리에 맞는 상위 10개 문서를 hits.hits 속성에 담아 돌려줘요. 이렇게 검색은 /index/_search 같은 REST 엔드포인트에 JSON 바디를 실어 보내면 됩니다.

공통 검색 옵션

검색을 입맛대로 다듬을 수 있는 옵션들도 있어요.

  • 쿼리 DSLbool 같은 복합 쿼리, 정확한 일치를 찾는 term-level 쿼리, 검색 엔진에서 흔히 쓰는 전체 텍스트 쿼리, 지리·공간 쿼리를 섞어 쓸 수 있어요.
  • 집계 (Aggregations) — 검색 결과에서 통계·분석을 얻어요. "필드별 문서 수", "평균 · 합계" 같은 질문에 답해 주죠.
  • 여러 스트림·인덱스 검색 — 쉼표 구분 값과 grep 같은 인덱스 패턴으로 여러 데이터 스트림과 인덱스를 한 요청에 검색할 수 있어요. 특정 인덱스의 결과에 부스트를 줄 수도 있어요.
  • 페이징 — 기본적으로 검색은 상위 10개의 히트만 돌려줘요. 더 많거나 적은 문서가 필요하면 페이징 옵션으로 조절해요.
  • 필드 선택hits.hits는 각 히트의 전체 _source를 포함해요. 필요한 필드만 골라 받으려면 필드 선택 옵션을 써요.
  • 정렬 — 기본 정렬은 관련성 점수 _score 기준이에요. script_score 쿼리로 점수 계산을 바꾸거나, 다른 필드 값으로 정렬할 수도 있어요.

검색 타임아웃

기본적으로 검색 요청은 타임아웃이 없어요. 모든 샤드에서 완료 결과가 올 때까지 기다리죠. timeout 값을 설정하면 샤드 단위로 적용돼요. 어느 샤드에서 타임아웃을 넘기면 일부 결과만 돌아오고, 응답에 "timed_out": true가 표시돼요.

{
  "took": 11,
  "timed_out": true,
  "_shards": {
    "total": 40,
    "successful": 40,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 98393,
      "relation": "eq"
    }
  }
}

전체 요청이 부분 결과 대신 오류로 끝나야 한다면 default_allow_partial_resultsfalse로 두면 돼요. 모든 검색에 걸리는 기본 타임아웃을 클러스터 단위로 정하고 싶다면 search.default_search_timeout 설정을 쓸 수 있어요.

동기 vs 비동기 검색

Elasticsearch 검색은 방대한 데이터를 빠르게 처리하도록 설계돼서, 기본은 동기(synchronous) 예요. 요청이 완료 결과를 받아 오기 전까지 기다리죠. 그런데 큰 데이터 세트나 여러 클러스터를 오가는 검색은 완료까지 오래 걸릴 수 있어요. 이때는 비동기(async) 검색을 써서, 지금은 부분 결과를 먼저 받고 나중에 완료 결과를 가져올 수 있어요.

정확한 히트 수 추적

총 히트 수는 모든 일치 문서를 방문해야 정확히 계산되니, 많은 문서에 걸린 쿼리에서는 비용이 커요. track_total_hits 파라미터로 총 히트 수를 어떻게 추적할지 제어할 수 있어요. 기본값은 10,000으로, 그 지점까지는 정확히 세고 그 뒤로는 하한만 알려줘요. 성능과 정확도의 트레이드오프를 조절하는 셈이죠.

  • true로 두면 항상 정확히 셉니다. total.relation이 항상 "eq"가 돼요.
  • 숫자로 주면 해당 개수까지 정확히 추적해요. 예를 들어 100까지 정확히 세고 싶다면 이렇게요.
{
  "track_total_hits": 100,
  "query": {
    "match": {
      "user.id": "elkbee"
    }
  }
}

응답의 hits.total.relation이 정확한 값("eq")인지 하한 값("gte")인지 알려줘요. 보통은 어느 선 이상의 정확한 수치가 필요 없을 때가 많아서, track_total_hits를 낮출수록 쿼리가 빨라지고 false가 가장 빠르답니다.

일치 문서가 있는지만 빠르게 확인

결과 자체가 아니라 '일치하는 문서가 있는지'만 궁금할 때가 있어요. 이때는 size0으로 두면 결과는 돌려주지 않고 히트 수만 확인할 수 있어요. terminate_after1로 두면 샤드마다 첫 번째 일치 문서를 찾는 즉시 쿼리 실행을 끝내요. 응답에 terminated_early: true가 표시되고, 얼마나 빨리 종료됐는지 알 수 있어요.

더 알아보기