<색인>
색인 API
PUT [인덱스명]/_doc
덮어쓰기 허용
PUT [인덱스명]/_create
덮어쓰기 금지
refresh
검색 가능하게 만들기
true
: 색인 직후 refresh 하고 응답을 반환한다. → 단점: 너무 작은 세그먼트를 많이 생성해 성능 저하와 병합 부하 커짐
wait_for
: index.refresh_interval (기본값 1초)만큼 기다린다. refresh 대기하는 문서가 index.max_refresh_listeners 값(기본값 1000) 이상이면 바로 refresh 한다.
false
: refresh 하지 않는다
조회 API
GET [인덱스명]/_doc/[_id]
, GET
[인덱스명]/_source/[_id]
refresh 기다릴 필요 없이 바로 확인할 수 있다!
업데이트 API
detect_noop
업데이트 API 는 기존 문서 먼저 읽고 변경할게 있는지 여부를 확인하는데, 변경할 내용이 기존 내용과 동일하면 no operation 요청임을 확인하고 쓰기작업을 하지 않는다.
"result": "noop"
detect_noop 을 false 로 비활성화 할 수있지만 기존 문서 _source 를 읽는 작업은 무조건 수행한다.
커스텀 플러그인 작성시 IndexingOperationListener 에서 noop 요청이면 preIndex(), postIndex() 메소드 수행 안되므로 유의한다.
POST upd_test/_update/1
{
"doc": {"views": 36},
"detect_noop" : false
}
doc_as_upsert
기본적으로 업데이트 할 문서 없으면 실패하는데, doc_as_upsert
true 로 하면 업서트 해준다.
스크립트
painless
: ES 자체 스크립트 언어
params | 매개변수 Map |
ctx._source | 문서의 _source 값 Map 으로 변환한거 |
ctx.op | 작업 종류. [index, none, delete] |
ctx._now | timestamp 을 ms 로 변환한 값 |
ctx._index ctx._id ctx._type ctx._routing ctx._version |
메타데이터 |
벌크 API
-NDJSON 타입이다. (여러 줄 JSON 은 줄바꿈 문자로 구분) (application/x-ndjson)
- bulk API 기술된 작업이 반드시 그 순서대로 수행된다는 보장은 없다. 여러 프라이머리 샤드에 넘어간 요청은 독자적으로 수행되기 때문. 그러나 index, _id, routing 조합 요청이라면 반드시 같은 샤드로 들어간다.
update by query
POST [인덱스명]/_update_by_query
쿼리 요청하면 문서를 일종의 스냅삿을 찍는다. 여기서 버전 충돌 문제가 생기면 conflicts
에 따라 작업한다. abort (기본값) 면 충돌 시 작업 중단하며, proceed 로 하면 무시하고 넘어간다.
중간에 중단되더라도 그때까지 업데이트된 내용이 롤백되진 않는다.
reindex 랑 옵션이 비슷하다.
스로틀링
POST bulk_test/_update_by_query?scroll_size=1000&scroll=1m&requests_per_seconds=500
운영중에 update 하면 부하 줄 수 있으므로 스로틀링을 통해 작업량을 조절할 수 있다.
-scroll_size
: 업데이트 전 먼저 검색을 수행하는데 한 번 검색 수행에 가져올 문서 개수
-scoll
: 검색한 문서 가져오면 search context(검색 문맥)에 보존하는데, 얼마나 보존할지 지정. 한 배치(scoll_size) 작업에 필요한 시간만 지정하면 된다.
- 검색 문맥은 힙 메모리, 디스크 공간, file descriptor 를 차지하므로 너무 큰 값을 지정하지 않아야 한다.
-requests_per_second
: 초 당 몇개까지 수행할건지 지정한다. scroll_size가 1000이고 requests_per_second가 500 (1초에 500개) 이면 한 번 스크롤하고 2초될때까지 기다리고 한 번 스크롤하는 방식으로 시간을 맞춘다. (-1이면 스로틀링 적용 안 함)
response
{
"throttled_millis": 2999,
"requests_per_second":500.0
}
throttled_millis: 작업 진행하지 않고 대기한 총 시간
wait_for_completion
POST [인덱스명]/_update_by_query/wait_for_completion=false
wait_for_completion=false 로 하면 비동기로 update by query 할 수 있다.
작업 확인은 task api 로 한다.
task API
update by query 가 비동기든 아니든 모든 작업은 .task 인덱스에 저장되고, GET .task
혹은 GET_tasks
(이거 쓰는게 대세) 로 확인할 수 있다.
작업 취소는 POST tasks/[task id]/_cancle
로 하며 비동기든 아니든 취소 가능하다.
일괄 업데이트 중 운영 트래픽이 급증했을 때
update by query 가 72시간 수행하고 나머지 2시간 남았는데 운영 트래픽이 급증하는 상황이라면, 두 가지 해결책이 있다.
- 일단 중지하고 검색 조건을 수정해 나머지 변경 안된 건만 대상으로 트래픽 해결한뒤 재수행한다.
- 변경 안된 건만 거를 수 없는 상황이라면,
_rethrottle
api 로 스로틀링을 조절한다.
POST _update_by_query/[task id]/_rethrottle?requests_per_seconds=5000
슬라이싱
POST [인덱스명]/_update_by_query?slices=auto
-스로틀링과 반대로 서비스 중지 후 공지한 시간 안에 업데이트 성능을 최대로 끌어내 빠른 시간 안에 끝내고자 할 때 쓴다.
-검색과 업데이트를 지정한 개수로 쪼개 병렬적으로 수행한다.
-기본값은 1로 병렬이 아니며, slices:auto 로 하면 기본 쪼개는 개수는 프라이머리 샤드 수이다.
-requests_per_second
값도 각 슬라이스에 분배된다. ex. requests_per_second 가 1000이고 슬라이스 가 5개면 각 슬라이스는 200ms만 기다린다.
<검색>
term 쿼리
text 타입일때, 질의어는 normalizer 처리되고 text 타입 필드 값은 analyzer 를 거쳐 역색인을 이용한다. 역색인 결과가 단일텀이고 노멀라이저 거친 질의어와 완전 일치하는 경우에만 검색 결과에 걸린다.
range 쿼리
문자열 필드 range 쿼리는 부하가 큰 쿼리로 분류한다.
prefix 쿼리
단발성 쿼리는 괜찮으나 서비스 쿼리로는 적절하지 못하다. prefix 를 서비스성으로 사용하고 싶으면 mappings 에 index_prefixes
를 넣는 방법이 있다.
PUT prefix_index_test
{
"mappings": {
"properties": {
"fieldName": {
"type": "text",
"index_prefixes": {
"min_chars": 3,
"max_chars": 5
}
}
}
}
}
min_chars(디폴트: 2) 와 max_chars(디폴트: 5) 사이의 prefix 를 미리 별도로 색인한다.
※ elasticsearh.yml
에 search.allow_expensive_queries
:false 로 하면 indx_prefixes 적용안된 prefix 는 사용할 수 없다.
쿼리 문맥과 필터 문맥
필터 문맥(filter context): 점수 매기지 않고 단순히 조건 만족하는지 여부만 따진다.
- filter, must_not, exists, range, constant_score 쿼리
쿼리 문맥(query context): 유사도 점수를 매긴다.
- must, should, match, term 쿼리
쿼리 수행 순서
-must, must_not, filter, should 사이에서 어떤 쿼리가 먼저 수행된다는 규칙은 없다. 루씬의 쿼리로 재조합에 비용 추정 후 유리할 것으로 생각되는 부분을 먼저 수행한다.
-쿼리문맥이어도 일단 조건이 문서와 매치되는지를 참 거짓으로 판단한다. 그 이후에 쿼리문맥은 점수를 계산하고 필터 문맥은 점수 계산을 안 할 뿐이다.
-쿼리문맥이어도 랭킹안에 못 들거같은 문서는 점수 계산을 건너뛰기도 한다.
-그래서 사용자가 임의로 쿼리 순서를 컨트롤하는게 어렵다. 하고싶으면 커스텀 플러그인에서 커스텀 쿼리를 만들어야 한다.
constant_score
filter 쿼리에 유사도 점수를 줄 수 있다. (기본값:1)
GET /_search
{
"query": {
"constant_score": {
"filter": {
"term": { "user.id": "kimchy" }
},
"boost": 1.2
}
}
}
search_type
GET [인덱스명]/_search?search_type=dfs_query_then_fetch
query_then_fetch: (기본값) 각 샤드 레벨에서 유사도 점수 계산을 끝낸다. 약간 부정확할 순 있지만 검색 성능 차이가 크다.
dfs_query_then_fetch: 모든 샤드로부터 정보를 모어 유사도 점수를 글로벌하게 계산한다. 정확도는 올라가나 검색 성능이 떨어진다.
페이지네이션
from, size (비추)
from, size 로 검색하면 from+size 개수 문서를 모두 검색한 뒤 잘라서 응답으로 보낸다.
그러면 CPU와 메모리 사용량을 크게 증가시킨다. 또 문제는 다음 페이지 검색할 때의 인덱스 변경이 있었다면 인덱스 상태가 동일하지 않기 때문에 누락될 수 도 있으므로 from,size 페이지네이션은 사용하지 말아햐 한다.
from+size 가 기본 1만이 넘어가면 검색은 수행이 거부된다.
scroll (전체 순회할 때 유용)
-검색 조건 만족하는 전체 문서 순회할 때 사용한다. 최초 검색 시의 문맥(search context) 이 유지되므로 중복이나 누락이 발생하지 않는다.
-"score":"1m"
에서 시간 설정은 배치와 배치 사이를 유지할 정도의 시간으로 지정하면 된다.
-정렬여부과 상관없다면 “sort”: [”_doc”]
_doc 정렬을 지정하는 것이 좋다. 그러면 유사도 점수를 계산하지 않으며 정렬을 위한 별도의 자원도 사용하지 않아 성능 끌어올릴 수 있다.
search_after (추천)
동점 제거용(tiebreaker) 필드 정렬이 필요하다.
GET twitter/_search
{
"query": {
"match": {
"title": "elasticsearch"
}
},
"sort": [
{"date": "asc"},
{"tie_breaker_id": "asc"}
]
}
다음 페이지 검색 시
가장 마지막 문서의 sort 값을 search_after
에 넣는다.
GET twitter/_search
{
"query": {
"match": {
"title": "elasticsearch"
}
},
"search_after": ["1463538857", "654323"],
"sort": [
{"date": "asc"},
{"tie_breaker_id": "asc"}
]
}
tiebreaker 필드로 _id 를 넣는 것은 좋지 않다. doc_values 가 꺼져 있어서 많은 메모리를 사용하기 때문이다. 필요하면 _id 값이 있는 새 필드를 파는 게 좋다.
point in time API
POST /my-index-000001/_pit?keep_alive=1m
검색 대상의 상태 고정할 때 사용한다. id 난수 값을 반환한다.
keep_alive: 상태 유지할 시간
GET twitter/_search
{
"query": {
"match": {
"title": "elasticsearch"
}
},
"pit": {
"id": "[_pit 에서 준 값]",
"keep_alive": "1m"
},
"sort": [
{"date": "asc"}
]
}
pit 값으로 검색하면
- 인덱스명을 지정안해도 된다. pit 지정하는 것 자체가 검색 대상을 지정하는 것이기 때문이다.
- tie breaker 필드를 지정안 해도 된다. 정렬이 하나있다면 _shard_doc 이라는 동점 제거용 필드 오름차순 정렬이 맨 마지막에 자동으로 추가된다.
- 그래서
search_after
사용하려면 정렬 필드 최소 하나는 지정해야 한다.
- 그래서
<정렬>
size:0 으로 하면 문서 수집도 안 하고 점수도 계산 안해서 성능 상 이득이다.
cardinality
POST /sales/_search?size=0
{
"aggs": {
"type_count": {
"cardinality": {
"field": "type",
"precision_threshold": 100
}
}
}
}
precision_threshold
- 높일수록 정확도가 높고 메모리를 더 사용한다.
- precision_threshold 가 최종 cardinality 보다 높으면 정확도가 충분히 높다.
- 기본값은 3000, 최대 4000
- 필드의 cardinality 가 높고 낮고 상관없이 메모리 사용량은 precision_threshold에만 영향 받는다.
집계 캐시
-집계 요청이 들어오면 샤드 요청 캐시에 올린다. 이후 동일한 집계요청이 같은 샤드로 올라오면 캐시를 활용해 그대로 반환한다. 동일한 집계 요청인지 여부는 요청 본문이 동일한가로 구분한다.
-now 가 포함된 집계 요청은 캐시되지 않은다.
-새 데이터가 색인돼 인덱스 상태가 달라지면 샤드 요청 캐시는 무효화되기 때문에 고정된 인덱스 아니면 캐시 활용도가 떨어진다.
composite 집계
페이지네이션으로 집계 데이터를 가져올 수 있다.
GET my-index-000001/_search?size=0
{
"aggs": {
"my_buckets": {
"composite" : {
"sources" : [
{
"date": {
"date_histogram" : {
"field": "date",
"calendar_interval": "day",
"offset": "+6h",
"format": "iso8601"
}
}
}
]
}
}
}
}
{
...
"aggregations": {
"my_buckets": {
"after_key": { "date": "2015-10-01T06:00:00.000Z" },
"buckets": [
{
"key": { "date": "2015-09-30T06:00:00.000Z" },
"doc_count": 1
},
{
"key": { "date": "2015-10-01T06:00:00.000Z" },
"doc_count": 1
}
]
}
}
}
after_key 에 가장 마지막 버킷의 key 를 넣어준다.
파이프라인 집계
다른 집계 결과를 집계 대상으로 한다.
<클라이언트 코드>
-transport 클라이언트: 8 버전부터 완전히 제거됨
-고수준 REST 클라이언트: 저수준 클라이언트를 추상화해서 쉽게 쓸 수 있도록 래핑. 7.15 부터 지원 중단.
-자바클라이언트: 8버전 이상이면 고려해라. 8.7 이상이면 쓰는 게 좋다. 7.15 는 베타 버전이고 7.16부터 정식 버전이라 고수준이랑 혼용하는게 좋다.
- jackson 같은 라이브러리 사용해서 사용자가 지정한 클래스로 직렬화 역직렬화 알아서 해준다. 내부 구현과 결합도를 낮춤
- 검색 메소드를 쿼리 DSL 과 유사한 느낌으로 내 가독성이 좋다.
- 8.7 부터 고수준 rest client 의 BulkProcessor 를 대체할 수 있는 BulkIngester 가 추가됐다.
레퍼런스
엘라스틱서치 바이블 4장
'SearchDeveloper > 엘라스틱서치 바이블' 카테고리의 다른 글
[7] 운영 도중 발생하는 장애 대응 (0) | 2024.03.31 |
---|---|
[6] 클러스터 운영 (0) | 2024.03.31 |
[5] 서비스 환경에 클러스터 구성 (0) | 2024.03.31 |
[3] 인덱스 설계 (1) | 2024.01.14 |
[2] 엘라스틱서치 기본 동작과 구조 (0) | 2024.01.14 |