-
이 포스트는 elasticsearch 7.17 버전에 대한 이야기 입니다.
운영 중인 7.17 버전 elasticsearch의 한 인덱스의 모든 도큐먼트를 S3로 옮겨야 하는 상황이 발생했다.
elasticsearch 에서는 _search API로 한 인덱스의 전체 도큐먼트를 가지고 올 수 없다.
elasticsearch의 _search API의 경우, 아래 처럼 size를 지정해 가져오고 싶은 도큐먼트의 갯수를 지정할 수 있다.
curl -X POST "localhost:9200/my-index-000001/_search?pretty" -H 'Content-Type: application/json' -d' { "size": 100, "query": { "match_all": {} } } '
그런데 이 size의 경우 기본값은 10이고, 최대 10000까지만 설정이 가능하다.(이상으로 지정해도 최대 1만개 까지만 적용된 결과를 얻음)
한 인덱스의 전체 데이터를 가져오기 위해서는 페이징 방식과 유사한 기능을 제공하는 _scroll API를 사용할 수 있다.
_scroll API
curl -X POST "localhost:9200/my-index-000001/_search?scroll=1m&pretty" -H 'Content-Type: application/json' -d' { "size": 100, "query": { "match_all": {} } } '
쿼리 파라미터의 scroll=1m 이부분은 search context를 1분 동안만 유지 하겠다는 것이다.
위 요청을 처음 보내면 아래와 같은 결과를 받는데 _scroll_id를 통해 다음 페이지의 데이터를 가지고 올 수 있다.
{ "_scroll_id" : "FGluY2x1ZGVf...", "took" : 2, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 280881, "relation" : "eq" }, "max_score" : 1.0, "hits" : [ { 다큐먼트 내용 }, . . . ] }
다음 페이지의 정보를 가지고 오기 위해서 보내는 두번째 요청
POST /_search/scroll { "scroll" : "1m", "scroll_id" : "FGluY2x1ZGVf..." }
제너레이터 함수와 for wait 문법으로 페이지네이션 방식의 API 멋지게 가져오기
제너레이터 함수와 for wait 문법을 사용하면, 최초 한번 요청 후 _scroll_id 결과가 응답에 있을 때 까지 요청을 반복적으로 보내는 로직을 깔끔하게 작성할 수 있다.
(공식 문서 설명 : Another cool usage of the scroll API can be done with Node.js ≥ 10, by using async iteration!)
import { Client, ApiResponse, } from '@elastic/elasticsearch' import 'dotenv/config' const INDEX: string = process.env.ES_INDEX_PATTERN || ''; type ESDocument = { _index: string _type: string _id: string _score: number, _source: object } type QueryResults = ESDocument[]; type SearchParam = { index: string, size?: number, body: object | string scroll?: string } const client: Client = new Client({ node: process.env.ES_CLOUD_NODE || '', auth: { username: process.env.ES_CLOUD_USERNAME || '', password: process.env.ES_CLOUD_PASSWORD || '' } }) async function* scrollSearch (params: SearchParam) { let response: ApiResponse = await client.search(params) while (true) { const sourceHits: QueryResults = response.body.hits.hits if (sourceHits.length === 0) { break } for (const hit of sourceHits) { yield hit // 제너레이터 함수: yield로 리턴 후 이 함수가 다시 실행될 때는 이 밑부터 실행 됨 } if (!response.body._scroll_id) { break } console.log(response); response = await client.scroll({ scroll_id: response.body._scroll_id, scroll: params.scroll }) } } const runScroll = async (index: string): Promise<QueryResults> => { try { // new definitions const params = { index, scroll: '30s', // size: 10000, // max 10000 body: { query: { match_all: {} } } } let results: ESDocument[] = []; for await (const hit of scrollSearch(params)) { results.push(hit); } return results; } catch (error: unknown) { throw error; } } const main = async () => { const res: QueryResults = await runScroll(INDEX); console.log([res[0]], res.length); } main();
search context란 무엇일까?
elasticsearch는 한 샤드 검색 작업이 일어나는 동안 검색에 필요한 상태를 유지하는데 이것을 search context라고 한다.
동시에 많은 양의 검색작업이 생기면 그만큼 search context의 갯수도 많아지고 메모리를 차지하게 된다.
현재 search context 확인하기
GET /_nodes/stats/
stats API로 현재 노드에 대한 여러 정보를 확인 할 수 있다.
GET /_nodes/stats/indices/search
그 중에서 indices/search 부분에서 현재 활성 중인 search context를 확인 할 수 있다.
참고
https://soyoung-new-challenge.tistory.com/96
https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/master/scroll_examples.html
'🏢 업무 리서치 기록' 카테고리의 다른 글
Clinic.js로 Node.js 서버 프로파일링 하기 (0) 2023.01.17 로그인 기능 구현의 과정 (feat. OAuth2.0 와 JWT) (0) 2022.12.16 OLTP VS OLAP (feat. 트랜잭션 ACID 특성) (0) 2022.12.15 ssh 포트포워딩(터널링) 뚫기 (feat. 로컬에서 AWS VPC내 RDS 접속하기) (3) 2022.12.15 [BigQuery] Streaming Insert VS Insert Into DML Query (1) (0) 2022.12.01