[Search] ES Sort: relevance, field, script, geo
ES sort, sort by relevance, _score, doc_values, fielddata, search_after, PIT, track_total_hits
정의
ES 의 기본 정렬 = relevance score (BM25) 내림차순. 명시적 sort 로 변경 가능. doc_values 로 메모리 효율적 정렬.
5가지 정렬
flowchart TB
Sort[Sort 종류]
Sort --> Rel["_score (relevance, 기본)"]
Sort --> Field["field (price asc / desc)"]
Sort --> Script["script (커스텀)"]
Sort --> Geo["_geo_distance"]
Sort --> Nested["nested object"]
1. Field Sort
GET /products/_search
{
"query": { "match_all": {} },
"sort": [
{ "price": "asc" },
{ "created_at": "desc" },
"_score"
]
}
| 옵션 | 의미 |
|---|---|
order: asc / desc | 방향 |
missing: _first / _last / 값 | NULL 처리 |
mode: min / max / avg / sum / median | 배열 필드의 대표값 |
unmapped_type | 인덱스에 없는 필드 무시 |
2. Relevance (_score)
"sort": ["_score"]
기본. 별도 sort 명시 안 하면 적용. 자세한 BM25 는 elasticsearch-relevance-scoring.
3. Script Sort
"sort": {
"_script": {
"type": "number",
"script": {
"lang": "painless",
"source": "doc['price'].value * (1 - doc['discount'].value)"
},
"order": "asc"
}
}
런타임 계산. 무거움. 자주 쓰는 정렬은 index 시 계산해서 저장.
4. Geo Distance
"sort": [
{
"_geo_distance": {
"location": { "lat": 37.55, "lon": 126.97 },
"order": "asc",
"unit": "km",
"distance_type": "arc"
}
}
]
doc_values vs fielddata
flowchart LR
Doc["doc_values<br/>(default, on-disk columnar)"] --> Fast[정렬, 집계 효율]
Field["fielddata<br/>(in-memory, text 전용)"] --> Heavy[메모리 비용 큼]
| doc_values | fielddata | |
|---|---|---|
| 자료구조 | columnar (Lucene) | in-memory (heap) |
| 적용 | 거의 모든 필드 (기본) | text 필드 (옵션) |
| 정렬 / 집계 | 가능 | 가능하지만 비쌈 |
| 비활성 | "doc_values": false | "fielddata": false (기본) |
IMPORTANT
text 필드에 정렬/집계 = 거절 (기본). keyword 또는 multi-field 사용.
"properties": {
"title": {
"type": "text",
"fields": {
"raw": { "type": "keyword" }
}
}
}
→ 정렬 시 "title.raw" 사용.
search_after + PIT (deep pagination)
from + size 의 깊은 페이지네이션 은 비효율. 대용량 결과는 search_after + Point in Time (PIT).
# 1. PIT 생성
POST /products/_pit?keep_alive=5m
→ { "id": "pit_id_xxx" }
# 2. 첫 페이지
GET /_search
{
"size": 100,
"pit": { "id": "pit_id_xxx", "keep_alive": "5m" },
"sort": [{ "created_at": "asc" }, { "_id": "asc" }]
}
# → 결과의 sort 값을 저장: [created_at_value, id_value]
# 3. 다음 페이지
GET /_search
{
"size": 100,
"pit": { "id": "pit_id_xxx", "keep_alive": "5m" },
"sort": [{ "created_at": "asc" }, { "_id": "asc" }],
"search_after": [created_at_value, id_value]
}
TIP
10K offset 이상 은 search_after 만. from + size 는 index.max_result_window 한계 (10000 기본).
track_total_hits
{
"track_total_hits": false // 또는 10000 (정확 카운트 한도)
}
정확한 total count 가 비싸다. 대시보드 / “수천 개+” 표시면 생략 또는 근사.
Sort 와 BM25
flowchart LR
Q[Query] --> Match[matching docs]
Match -->|sort 없음| BM25Sort[BM25 score 내림차순]
Match -->|sort 명시| FieldSort[field 값 정렬]
Note["sort 명시 → score 계산 skip (성능 ↑)"]
TIP
sort 가 score 무관 이면 score 계산 skip (성능 ↑). 단 function_score 같이 score 가 필요한 정렬 은 그대로 계산.
흔한 함정
WARNING
text필드로 정렬 = 에러.keywordmulti-field 사용.- deep
from + size=max_result_window초과.search_after. track_total_hits: true= 대량 매칭 시 2배 느림. 필요 없으면 false 또는 10000.- script sort 남용 = 매 쿼리 Painless 컴파일. runtime field 또는 index 시 저장 으로.
관련 위키
이 글의 용어 (4개)
- [Search] ES Aggregations: metric, bucket, pipelinesearch
- 정의 Aggregations (aggs) = ES 의 집계 분석. SQL 의 GROUP BY + 함수 + window. 검색 결과 또는 전체 위에서. 3가지 카테고리 Metric…
- [Search] ES Mapping: field type, dynamic, multi-fieldsearch
- 정의 Mapping = ES 의 schema. 각 필드의 type + analyzer + indexing 옵션. 대부분 immutable (재인덱싱 필요). Field Type …
- [Search] ES Query: must / should / filter / must_notsearch
- 정의 ES 의 Query DSL = JSON 기반 표현력 풍부한 쿼리 언어. bool query + leaf query 의 조합. bool query: 4가지 절 | 절 | AN…
- [Search] ES Relevance Scoring: BM25, TF-IDF, function_scoresearch
- 정의 Relevance Score ( ) = 쿼리와 문서의 매칭 강도. ES 7+ 의 기본은 BM25 (TF-IDF 의 발전형). BM25 공식 | 부분 | 의미 | |---|-…
💬 댓글