Elasticsearch 입문 19편 — Search Features (highlight·sort·pagination·search_after·scroll·PIT)

2026-05-19Elasticsearch 입문에서 운영까지

Elasticsearch 입문 19편 Search Features. highlight·sort·search_after·scroll·PIT. Deep pagination 회피.

📚 Elasticsearch 입문에서 운영까지 · 19편 — Search Features (highlight·sort·pagination·search_after·scroll·PIT)

이 글은 Elasticsearch 입문에서 운영까지 시리즈 38편 중 19편이에요. 12~15편에서 쿼리로 맞는 문서를 찾는 법 을, 16~18편에서 Aggregations 로 숫자를 뽑는 법 을 봤다면, 이번 편은 그렇게 찾은 결과를 사용자에게 어떻게 보여 줄지 에 대한 자리예요. Search Features — 검색 결과 자체보다 주변 의 부가 기능. highlight·sort·pagination 의 세 갈래가 본업이고, 그 뒤로 deep pagination 을 어떻게 피하느냐가 운영의 절반을 차지합니다.

📚 학습 노트

이 글은 Elasticsearch 8.x 공식 docs 의 Search results · Paginate search results · Highlighting · Sort search results 챕터를 한국어 학습 노트로 풀어쓴 자료예요.

Kibana Dev Tools 콘솔 또는 curl 로 본문 예제를 한 번씩 직접 던져 보면 머리에 훨씬 잘 박혀요.

검색 결과를 사용자에게 보여 주는 자리

15편까지의 쿼리들은 "어떤 문서가 어떤 점수로 맞느냐" 까지만 책임을 져요. 그런데 실제 사용자 화면을 만들려면 그 위에 세 가지가 더 필요해요. 어디가 매칭됐는지 표시 해 주는 highlight, 어떤 순서로 보여 줄지 정하는 sort, 몇 개씩 끊어 줄지 결정하는 pagination. 이 세 가지가 본문의 절반이고, 나머지 절반은 큰 페이지를 어떻게 안전하게 끊느냐 하는 search_after·scroll·PIT 이야기.

이 자리를 잘못 짜면 검색 자체는 잘 도는데 사용자는 느려서 떠나는 사고가 나요. 1편의 7대 사고 중 Deep Pagination 폭망 이 가장 빈도 높게 일어나는 게 바로 여기. 본문 전체를 한 마디로 요약하면 — from + size 는 첫 페이지 몇 장에서만, 그 이상은 search_after + PIT 로 가라.

Highlight — 매칭 단어 강조

Highlight 는 검색어가 문서 어디서 매칭됐는지 를 보여 주는 기능이에요. 사용자는 "왜 이 결과가 떴는지" 한눈에 보고, 결과 신뢰가 올라가요. 구글이 검색 결과 페이지에서 키워드를 굵게 표시해 주는 게 같은 메커니즘.

기본 문법은 이렇게 단순.

GET /articles/_search
{
  "query": {
    "match": { "content": "Elasticsearch" }
  },
  "highlight": {
    "fields": {
      "content": {}
    }
  }
}

응답 안에 hits[].highlight.content 자리에 매칭된 문장 조각<em>키워드</em> 로 감싸여 와요. 디폴트 태그가 <em> 인 이유는 시멘틱 강조 의 표준이라서 — 시각적으로 굵게 하든 색깔을 입히든 CSS 에 맡기는 방식이에요.

주요 옵션 — pre_tags · post_tags · fragment_size · number_of_fragments

태그를 바꾸고 싶으면 pre_tagspost_tags 를 박아요. e-commerce 처럼 브랜드 컬러 가 정해진 곳은 <mark class="hit"> 로 감싸 두고 CSS 한 번에 색을 잡는 패턴이 표준.

"highlight": {
  "pre_tags": ["<mark class=\"hit\">"],
  "post_tags": ["</mark>"],
  "fields": {
    "content": {
      "fragment_size": 150,
      "number_of_fragments": 3
    }
  }
}

fragment_size조각 한 개의 글자 수 한도 예요. 디폴트 100 이고, 100~200 이 사용자 눈에 가장 편한 자리. number_of_fragments몇 조각까지 돌려줄지 — 디폴트 5 이고, 검색 결과 한 행에 3 조각 정도가 일반.

number_of_fragments: 0 으로 박으면 문서 원문 전체 를 그대로 돌려주면서 매칭 위치만 태그로 감싸 줘요. 짧은 제목·요약 필드는 fragment 자르지 말고 전체 가 깔끔.

Highlighter 3종 — unified · plain · fvh

ES 는 강조 알고리즘을 세 가지 제공해요.

Highlighter 특징 언제
unified (디폴트) BM25 기반, 정확도 OK, 속도 빠름 대부분의 경우
plain 쿼리 그대로 토큰 매칭, 가볍지만 느림 작은 필드, 단순 매칭
fvh (fast vector highlighter) term_vector 필요, 큰 필드에 빠름 대용량 본문 (블로그 본문 등)

fvh 를 쓰려면 mapping 에서 term_vector: with_positions_offsets 가 선언돼 있어야 해요. 이게 안 켜져 있으면 ES 가 fvh 못 쓴다 고 에러를 던지면서 unified 로 fallback 안 함 — 처음부터 mapping 단계에서 선언해 두는 게 안전.

"highlight": {
  "fields": {
    "content": {
      "type": "fvh",
      "fragment_size": 150
    }
  }
}

matched_fields — multi-field 위 강조

같은 텍스트가 분석기를 다르게 색인된 여러 multi-field 위에 있는 경우가 흔해요. 예 — content 는 nori 로, content.english 는 standard 로, content.exact 는 keyword 로 색인. 검색은 셋 다 묶어 multi_match 로 던지는데, 강조는 어느 필드 기준으로 할지 가 헷갈리는 자리.

matched_fields 옵션이 이 자리를 풀어 줘요. "여러 필드에서 매칭된 단어를 같이 모아 본 필드 위에 강조" 하는 방식. 단 fvh같은 분석 단위 인 필드만 묶어야 해요.

"highlight": {
  "fields": {
    "content": {
      "type": "fvh",
      "matched_fields": ["content", "content.english"]
    }
  }
}

Sort 고급 — score 너머의 정렬

쿼리 결과는 디폴트로 _score 내림차순 으로 정렬돼요. 그런데 운영에서는 최신순·가격순·거리순·복합 기준 같은 다른 정렬이 더 자주 필요해요. ES 의 sort 옵션이 이걸 책임집니다.

가장 단순 — 필드 한 개

GET /products/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "price": "asc" }
  ]
}

이렇게 박으면 _score계산조차 안 해요. 풀텍스트 점수가 의미 없는 최신순 게시판 같은 자리에서는 _score 계산을 건너뛰는 게 성능에 큰 차이를 만들어요.

multi-field — 동률은 tiebreaker 로

같은 가격이 100개라면? sort 배열에 2차 기준 을 박아요.

"sort": [
  { "price": "asc" },
  { "created_at": "desc" },
  { "_id": "asc" }
]

마지막 _idtiebreaker (동률 깨는 마지막 기준) 역할. search_after 를 쓰려면 반드시 unique 한 tiebreaker 가 sort 배열 끝에 들어가야 해요 (뒤에서 다시).

nested sort — 배열 내부 필드 기준

nested 타입 필드 안의 값으로 정렬할 때는 nested 옵션을 같이 박아요.

"sort": [
  {
    "reviews.score": {
      "order": "desc",
      "nested": {
        "path": "reviews",
        "filter": { "term": { "reviews.verified": true } }
      }
    }
  }
]

리뷰 배열 중 verified=true 인 리뷰의 score 최댓값 으로 상품을 줄 세우는 식.

script_sort — 임의 수식

정렬 기준이 코드로 표현되는 수식 일 때 — 예: (가격 / 별점) 작은 순, (현재 시각 - 등록일) 가중치script_sort 가 답.

"sort": [
  {
    "_script": {
      "type": "number",
      "script": {
        "source": "doc['price'].value / doc['rating'].value"
      },
      "order": "asc"
    }
  }
]

다만 script_sort모든 문서마다 스크립트를 돌려야 해서 비싼 연산. 대량 데이터에서는 그 값을 색인 시점에 계산해서 별도 필드로 박아 두는 게 표준.

geo_distance sort — 거리순

지도 검색의 단골. 기준점에서 가까운 순 으로 줄 세워요.

"sort": [
  {
    "_geo_distance": {
      "location": { "lat": 37.5665, "lon": 126.9780 },
      "order": "asc",
      "unit": "km"
    }
  }
]

응답의 sort 자리에 km 단위 거리 가 들어와요. "내 위치 기준 가까운 카페 10개" 같은 자리.

missing 처리 — null 어디로 보낼까

정렬 기준 필드가 비어 있는 문서 가 섞이면 어디로 보낼지가 모호해요. missing 옵션으로 명시.

"sort": [
  { "price": { "order": "asc", "missing": "_last" } }
]

_last오름차순 정렬에서도 맨 뒤로, _first맨 앞으로, 또는 임의의 값 을 박아 거기 끼워 넣을 수도 있어요. 디폴트는 _last. 이걸 안 박으면 내림차순 정렬 시 null 이 위로 올라와 사용자 눈에 거슬리는 사고가 흔해요.

Pagination 한계 — from + size 의 진실

가장 직관적인 페이징은 from + size 예요.

GET /articles/_search
{
  "from": 20,
  "size": 10,
  "query": { "match_all": {} }
}

21번째부터 10개 를 가져오는 식. 사용자에게 친숙한 1·2·3·4 페이지 네비게이션 을 만들기 좋아요.

문제는 깊은 페이지 일 때예요. ES 는 분산 검색이라 모든 샤드에서 (from + size) 만큼 정렬한 다음 coordinator 가 머지해서 최종 (from + size) 중 상위 size 개 만 돌려줘요. from = 100,000 · size = 10 이라면? 샤드마다 100,010개를 정렬해서 보내야 하고, coordinator 는 (샤드 수 × 100,010) 개를 받아 다시 정렬. 수백 MB 메모리 + 수십 초 응답 폭증.

이 위험을 막으려고 ES 는 index.max_result_window 라는 안전 가드를 박아 둬요. 디폴트 10,000 — 즉 from + size > 10,000 요청은 그냥 에러 로 막혀요.

result_window is too large, from + size must be less than or equal to: [10000]

해결책은 두 갈래.

  1. 앱 정책에서 막기 — 10,000건 넘는 페이지는 그냥 검색을 더 좁히라고 UI 에서 유도. (e-commerce 표준)
  2. search_after · PIT · scroll 로 갈아타기 — 깊은 페이지·전수 추출이 필요한 자리. (뒷섹션)

max_result_window 자체를 50,000·100,000 으로 올리는 건 안티 패턴 이에요. 임시방편으로는 통할지 몰라도, 메모리 폭증·OOM 사고로 이어지는 길.

search_after — sort 값 기반 다음 페이지

search_after"방금 받은 마지막 문서의 sort 값을 가지고 다음 페이지를 달라" 라고 묻는 방식이에요. 페이지 번호 대신 cursor 로 페이징한다고 보면 돼요. deep pagination 의 표준 해법.

핵심은 sort 배열의 마지막 값을 그대로 들고 가서 search_after 에 넣는다 는 점.

1차 요청 — 첫 페이지

GET /articles/_search
{
  "size": 10,
  "query": { "match_all": {} },
  "sort": [
    { "created_at": "desc" },
    { "_id": "asc" }
  ]
}

응답의 마지막 hit 의 sort 자리에 [1715000000, "abc123"] 같은 배열이 들어와요.

2차 요청 — 다음 페이지

GET /articles/_search
{
  "size": 10,
  "query": { "match_all": {} },
  "sort": [
    { "created_at": "desc" },
    { "_id": "asc" }
  ],
  "search_after": [1715000000, "abc123"]
}

이걸 끝없이 반복하면서 cursor 만 밀어 줘요. 페이지 번호가 사라지는 대신 메모리·응답시간이 일정 (페이지가 깊어져도 일정 비용).

tiebreaker 가 반드시 unique 해야 한다

가장 흔한 사고가 tiebreaker 누락 또는 unique 하지 않음. created_at 만 sort 에 박으면, 같은 시각에 만든 문서 두 개가 cursor 로 구분이 안 돼서 페이지 사이에 중복 또는 누락 이 생겨요. 그래서 항상 마지막 sort 키로 _id 또는 unique 한 필드 를 박아야 해요. 이게 search_after 의 Rule 1.

무상태 — 서버에 컨텍스트 X

search_after 의 장점은 서버에 페이징 컨텍스트를 안 남긴다 는 점이에요. 한 사용자가 페이징하다 중간에 끊어도 서버 리소스 누수가 0. scroll API 의 가장 큰 단점이 세션 보존 메모리 였던 걸 search_after 가 깔끔하게 풀어 줘요.

snapshot — 일관성 보장은? → PIT 와 묶어 쓴다

search_after 자체는 그때그때의 인덱스 상태 를 봐요. 페이징 중간에 새 문서가 들어오면 결과가 흔들릴 수 있어요. 일관된 스냅숏 위에서 페이징하고 싶으면 PIT (Point In Time) 와 묶어 쓰는 게 8.x 표준 (다음 섹션).

Scroll API — 대량 데이터 추출용 (deprecated 인 추세)

Scroll"수십만~수천만 건을 차례차례 다 끌어와야 하는" 자리에서 쓰던 옛 표준이에요. 데이터 마이그레이션·전수 백업·배치 처리 같은 자리.

흐름

1차 요청에 scroll 파라미터를 박아 컨텍스트 보존 시간 을 알려 줘요.

POST /articles/_search?scroll=1m
{
  "size": 1000,
  "query": { "match_all": {} }
}

응답에 _scroll_id 가 한 자리 따라와요. 다음 페이지부터는 쿼리 없이 _scroll_id 만 들고 가서 호출.

POST /_search/scroll
{
  "scroll": "1m",
  "scroll_id": "DXF1ZXJ5..."
}

scroll_id서버에 보존된 인덱스 스냅숏의 cursor 역할. 끝까지 다 받았으면 반드시 DELETE /_search/scroll컨텍스트를 해제 해야 해요. 안 해제하면 scroll context 가 서버 메모리에 쌓여서 사고로 이어져요.

일관성 보장

Scroll 의 장점은 시작 시점의 인덱스 스냅숏 위에서 페이징하므로 중간에 들어온 새 문서가 결과에 끼지 않는다 는 점. 진짜 전수 추출 에서는 이 보장이 중요.

Deprecated — 8.x 부터 PIT + search_after 로 옮기는 추세

작성 시점(2026-05) 기준 ES 8.x 공식 docs 는 Scroll 을 "deep pagination 또는 사용자 페이징에는 쓰지 말 것" 으로 권하고 있어요. transparent · 무상태 · 서버 리소스 적게 라는 세 축에서 PIT + search_after 가 우위. 새 프로젝트는 처음부터 PIT + search_after 로 짜는 게 표준.

다만 진짜 대량 전수 추출 (예: 1억 건 reindex 위한 read) 자리에서는 여전히 scroll 이 가장 단순 한 답이에요. 완전히 사라지지는 않을 것.

PIT (Point In Time) — 8.x 표준 페이징

PIT"이 시점의 인덱스 스냅숏을 한 동안 잡고 있어 줘" 라고 명시적으로 요청하는 API 예요. 이름 그대로 시간상의 한 점 을 묶어 두는 핸들. 7.10+ 에서 도입돼 8.x 부터 scroll 대체 표준.

흐름 — 4단계

1. PIT 열기

POST /articles/_pit?keep_alive=5m

응답에 pit_id 가 와요. 이게 그 시점 스냅숏의 핸들.

2. 검색 — PIT + search_after 묶기

POST /_search
{
  "size": 10,
  "query": { "match_all": {} },
  "pit": {
    "id": "46ToAwMD...",
    "keep_alive": "5m"
  },
  "sort": [
    { "created_at": "desc" },
    { "_shard_doc": "asc" }
  ]
}

주의 — PIT 검색은 인덱스 이름을 URL 에 넣지 않아요. POST /_search 만 쓰고 인덱스는 pit.id 안에 이미 박혀 있어요.

특수 tiebreaker 로 _shard_doc 을 쓰는 게 PIT 의 권장 패턴이에요. _id 보다 가볍고 unique 함이 보장.

3. 다음 페이지 — search_after 로 cursor 전진

응답 마지막 hit 의 sort 값을 가지고 search_after 에 넣어 다음 요청.

4. PIT 닫기

DELETE /_pit
{ "id": "46ToAwMD..." }

다 끝났으면 반드시 닫아 줘요. scroll context 와 마찬가지로 서버 메모리 누수 가 됩니다.

PIT vs Scroll — 한 표

PIT + search_after Scroll
일관성 보장 (스냅숏) 보장 (스냅숏)
서버 메모리 적음 많음 (scroll context)
무상태 페이징 OK NG (scroll_id 필수)
깊은 페이지 OK OK
동시 사용자 잘 견딤 부담 큼
8.x 권장 YES 점진적 deprecated
진짜 전수 추출 가능 가장 단순

새 프로젝트는 거의 모든 자리에서 PIT + search_after 가 답.

Search Templates / mustache — 쿼리 템플릿화

검색 쿼리가 복잡하고 자주 바뀌는 자리에서는, 코드에 JSON 을 박아 두는 대신 템플릿을 ES 에 등록 해 두고 파라미터만 던지는 방식이 깔끔해요. Search Templates 가 이걸 풀어요. 템플릿 엔진은 Mustache 문법.

템플릿 등록

PUT _scripts/article-search
{
  "script": {
    "lang": "mustache",
    "source": {
      "query": {
        "multi_match": {
          "query": "{{q}}",
          "fields": ["title^3", "content"]
        }
      },
      "size": "{{size}}",
      "from": "{{from}}"
    }
  }
}

호출

POST /articles/_search/template
{
  "id": "article-search",
  "params": {
    "q": "Elasticsearch",
    "size": 10,
    "from": 0
  }
}

장점 셋. (1) 앱 코드에서 쿼리 빌드 코드가 사라져 깔끔. (2) 쿼리 튜닝이 재배포 없이 가능. (3) 운영팀과 개발팀의 책임 분리. 단점은 디버깅이 멀어진다 는 점 — 검색 품질 사고가 났을 때 템플릿 안 mustache 까지 추적해야 해서 진입장벽이 한 번 더 있어요.

작은 규모는 그냥 코드에서 JSON 빌드 가 답. 검색이 서비스의 본업 인 자리부터 템플릿이 진가.

자주 만나는 사고 6가지

사고 1 — from = 100,000 폭망

원인 — 사용자 페이지네이션 UI 에 1·2·3·... 무한 페이지를 그려 두고 사용자가 마지막 페이지로 점프. from + size 가 10,000 을 넘으면서 result_window 에러로 검색이 죽거나, 어쩌다 한도가 늘려져 있으면 응답이 수십 초.

해결 — UI 자체를 "검색 결과가 10,000건 넘어요. 더 좁혀 보세요" 안내로 막거나, 무한 스크롤 + search_after 로 바꾸는 게 표준. 1편의 7대 사고 중 가장 자주 만나는 자리.

사고 2 — scroll context 미해제

원인 — scroll API 를 DELETE 안 하고 종료. 페이징 시작만 했다가 사용자가 나가거나 에러로 끊겼는데, scroll context 가 keep_alive 끝까지 서버 메모리에 남아요. 동시 사용자 100명에서 수 GB 메모리 누수 사고.

해결try/finally 패턴으로 반드시 DELETE 호출. 그리고 keep_alive짧게 (1m~5m) 잡아 사고 시 자동 회수. 가능하면 처음부터 PIT + search_after 로.

사고 3 — search_after tiebreaker 누락

원인sort: [{ created_at: desc }] 하나만 박고 search_after 를 돌림. 같은 created_at 인 문서가 cursor 로 구분이 안 돼서 페이지 사이에 중복 또는 누락. 사용자는 "방금 본 글이 다시 보여요" 라고 신고.

해결 — sort 배열 맨 뒤에 unique tiebreaker 박기 — _id, _shard_doc (PIT 권장), 또는 unique 필드 (예: order_no). 검증은 연속 페이지 두 개의 마지막 / 첫 hit 가 다른지 직접 확인.

사고 4 — highlight fragment 폭증

원인 — 큰 본문 필드(예: 블로그 본문 5MB) 에 highlight 를 default 옵션으로 박음. ES 가 fragment 5개 × 100 자 외에 내부적으로 전체 텍스트를 분석 해서 응답 시간이 1초 → 10초.

해결number_of_fragments 명시(보통 1~3), fragment_size 명시(150~200), 큰 본문은 fvh highlighter + term_vector: with_positions_offsets 색인. 그리고 highlight 대상은 검색 첫 페이지에만 박고, 깊은 페이지는 끄는 게 안전.

사고 5 — sort missing 처리 누락

원인price 가 null 인 문서가 섞인 채 sort: { price: desc } 만 박음. ES 는 디폴트 missing: _last내림차순에서도 null 을 맨 뒤 로 보내는데, 이게 사용자에게 부자연스러운 결과 일 수도 있어요. 또는 디폴트가 바뀌어도 모름.

해결항상 missing 명시. missing: _last (눈에서 멀리), missing: _first (눈에 띄게), 또는 임의의 sentinel 값 (예: 0 또는 999999).

사고 6 — PIT 미해제

원인 — PIT 를 열고 페이징하다 DELETE /_pit 를 호출 안 함. scroll context 와 같은 메커니즘의 메모리 누수. PIT 의 keep_alive 가 만료되면 자동 해제되지만, 수만 개의 PIT 가 쌓이는 시간 동안 노드 메모리 폭증.

해결try/finally 로 닫기. keep_alive짧게 (1m~5m) 잡고 매 요청에 연장 해 가는 패턴. PIT 가 서버 자원을 잡는다 는 사실을 코드 옆에 주석으로 박아 두는 게 도움.

운영 권장 패턴 5가지

(1) 페이지 정책을 처음부터 명시. "검색 결과 첫 100건만 페이징 허용, 그 이상은 검색 다시 좁히도록 UI 유도" 가 e-commerce·뉴스 사이트 표준. 무한 페이지 네비게이션deep pagination 사고 1번지.

(2) 무한 스크롤 = search_after + PIT. 모바일 UX 의 무한 스크롤은 거의 search_after + PIT 가 답. 첫 로드에 PIT 를 열고, 사용자가 스크롤할 때마다 search_after 로 cursor 전진. 페이지 떠날 때 PIT 닫기.

(3) 전수 추출 = scroll 또는 PIT, 무조건 try/finally. Reindex·백업·analytics 추출 같은 자리는 scroll 이 가장 단순. try/finally 로 컨텍스트 해제 가 절대 규칙.

(4) sort tiebreaker 는 강제. search_after 가 들어가는 모든 검색은 sort 마지막에 unique tiebreaker 가 의무. 팀 내에 코드 리뷰 체크리스트 로 박아 두면 사고 방지.

(5) highlight 는 첫 페이지에만. highlight 비용이 fragment 개수 × 필드 크기 에 비례. 첫 페이지에는 사용자 확인용으로 켜 두고, 두 번째 페이지 이후는 끄거나 number_of_fragments: 1 로 최소. 검색 응답 시간이 절반으로 줄어들기도.

시험 직전 한 번 더 — 압축 노트

  • Search Features = highlight·sort·pagination 의 세 갈래 + deep pagination 대응.
  • Highlight = <em> 디폴트 태그, pre_tags · post_tags 커스텀, fragment_size 150~200 · number_of_fragments 1~3.
  • Highlighter 3종 — unified(디폴트) · plain(작은 필드) · fvh(큰 필드, term_vector 필요).
  • Sort = _score 디폴트, sort 박으면 점수 계산 X. multi-field, nested, script_sort, geo_distance, missing 처리.
  • from + size = 디폴트 페이징, max_result_window 10,000 안전 가드. 늘리지 말고 다른 길.
  • search_after = sort 값 cursor, 무상태, 깊은 페이지 OK. tiebreaker unique 필수.
  • scroll = 옛 표준, 서버 메모리 컨텍스트, 반드시 DELETE. 8.x 에서 점진적 deprecated.
  • PIT (Point In Time) = 8.x 표준 스냅숏 핸들. keep_alive 짧게, 반드시 close. tiebreaker = _shard_doc 권장.
  • PIT + search_after = 8.x 사용자 페이징 표준 답.
  • scroll = 대량 전수 추출 (reindex·백업) 자리에선 여전히 단순한 답.
  • Search Templates = mustache 템플릿, 재배포 없이 쿼리 튜닝.
  • 7대 사고 — from=100000 폭망 · scroll context 미해제 · search_after tiebreaker 누락 · highlight fragment 폭증 · sort missing 누락 · PIT 미해제.
  • 운영 5규칙 — 페이지 정책 명시 · 무한 스크롤 = search_after+PIT · 전수 추출 try/finally · tiebreaker 강제 · highlight 첫 페이지에만.

시리즈 다른 편

  • 이전 글 = 18편 Aggregations Pipeline — moving_avg·derivative·cumulative_sum
  • 다음 글 = 20편 Suggesters — completion·term·phrase 자동완성·오타 교정
  • 12편 = Search API Basic — _search 엔드포인트 기본 문법
  • 15편 = Compound Queries — bool·boosting·function_score
  • 21편 = Vector Search — dense_vector·kNN
  • 27편 = Shard Allocation — search_after 가 샤드 위에서 동작하는 방식
  • 32편 = Spring Data Elasticsearch — Java 에서 search_after·PIT 호출
  • 38편 = 시리즈 마무리 — 결정 트리·체크리스트·자격증

한 줄 정리from + size 는 첫 페이지 몇 장에서만, 깊은 페이지는 PIT + search_after 가 8.x 표준 답. Highlight 는 첫 페이지에만 가볍게, sort 에는 unique tiebreaker 가 의무.

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

답글 남기기

error: Content is protected !!