이 글은 2018년 5월 29일 ObjectRocket.com/blog에 게시된 글을 재편집한 것입니다.
Elasticsearch® 관련 지원 요청 중 Rackspace Technology가 가장 자주 받는 문의는 "응답 시간을 개선할 수 있을까요?" 혹은 "쿼리 처리가 너무 오래 걸리는데 어떻게 해야 하나요?"입니다.
두 가지 접근 방식
이런 질문을 받을 때 우리는 크게 두 가지 영역부터 살펴봅니다.
- 운영(Operations) 측면 – 현재 시스템 리소스와 Elasticsearch 기본 설정 상태를 점검합니다.
- 개발(Development) 측면 – 쿼리 자체의 구조와 검색 대상 데이터의 매핑을 살펴봅니다.
이 글은 Elasticsearch 최적화 시리즈의 첫 번째 포스트로, 두 영역 중 개발 측면에 초점을 맞춥니다. 슬로우 쿼리를 수집하고, DSL(Domain Specific Language) 쿼리 언어를 살펴본 뒤, Elasticsearch 쿼리 성능을 개선하는 데 도움이 되는 옵션들을 다룹니다.
쿼리는 얼마나 느린가?
첫 단계는 클러스터에 쿼리를 보내고 결과를 받기까지 걸리는 시간을 확인하는 것입니다. Elasticsearch 공식 문서에서는 슬로우 로그(slow log)를 활성화하는 방법이 명확하게 설명되어 있지 않기 때문에, 이 글에서 몇 가지 예시를 통해 알아보겠습니다.
먼저 Elasticsearch에는 두 종류의 슬로우 로그가 있습니다. 바로 인덱스 슬로우 로그(index slow log)와 서치 슬로우 로그(search slow log)입니다. 지금 해결하려는 문제는 느린 쿼리이므로 서치 슬로우 로그에 집중하지만, 만약 색인(indexing)이나 문서 추가 시 성능 문제라면 인덱스 슬로우 로그를 확인해야 합니다.
모든 버전의 Elasticsearch에서 슬로우 로그는 기본적으로 비활성화되어 있으므로, 클러스터 설정과 인덱스 설정을 모두 업데이트해야 합니다. 아래 예시는 Elasticsearch 6.2 기준이며, 이전 버전에 대한 정보는 해당 버전 문서에서 확인할 수 있습니다. 명령어의 $ES_version 부분을 실제 사용 중인 버전(예: 5.5)으로 바꿔 사용하면 됩니다.
_cluster API에 PUT 요청을 보내 활성화할 슬로우 로그 레벨을 지정합니다. warn, info, debug, trace 네 가지 레벨을 사용할 수 있습니다.
curl -XPUT https://localhost:$ES_PORT/_cluster/settings -H 'Content-Type: application/json' -d' { "transient" : { "logger.index.search.slowlog" : "DEBUG", "logger.index.indexing.slowlog" : "DEBUG" } }'
Elasticsearch는 모든 슬로우 로깅을 인덱스 단위로 활성화하므로, 인덱스 _settings API에 요청을 보내 켤 수 있습니다. 인덱스를 월간 또는 분기별로 롤테이션한다면 인덱스 템플릿에도 동일한 설정을 추가해야 합니다.
인덱스 설정 API 호출 시 원하는 슬로우 로그 임계값(threshold)에 맞게 조정합니다. 값을 0으로 설정하면 인스턴스를 프로파일링하면서 전송된 모든 쿼리를 수집할 수 있고, -1로 설정하면 슬로우 로그가 비활성화됩니다.
_cluster 설정에서 사용한 것과 동일한 로그 레벨(여기서는 DEBUG)을 사용합니다. ES_PORT는 환경 변수입니다.
curl -XPUT https://localhost:$ES_PORT/*/_settings?pretty -H 'Content-Type: application/json' -d '{ "index.search.slowlog.threshold.query.debug": "-1", "index.search.slowlog.threshold.fetch.debug": "-1" }'
슬로우 로그 수집과 분석
이제 로그를 수집해야 합니다. 슬로우 로그는 샤드(shard) 단위로 생성되고 데이터 노드별로 모입니다. 예를 들어 프라이머리 샤드 5개(기본값)를 가진 데이터 노드가 하나뿐이라면, 하나의 쿼리에 대해 슬로우 로그에 5개의 항목이 기록됩니다. Elasticsearch의 검색은 각 샤드 내부에서 수행되기 때문입니다. 슬로우 로그는 데이터 노드별로 다음 기본 경로에 저장됩니다.
/var/log/elasticsearch/$ClusterID_index_slowlog_query 및 /var/log/elasticsearch/$ClusterID_index_slowlog_fetch
보시다시피 서치 슬로우 로그는 검색 단계(phase)에 따라 fetch와 query 두 개의 로그 파일로 다시 나뉩니다.
로그에 결과가 쌓였다면 이제 항목 하나를 꺼내 세부 내용을 분석해 볼 차례입니다.
[2018-05-21T12:35:53,352][DEBUG][index.search.slowlog.query] [DwOfjJF] [blogpost-slowlogs][4] took[1s], took_millis[0], types[], stats[], search_type[QUERY_THEN_FETCH], total_shards[5], source[{"query":{"match":{"name":{"query":"hello world","operator":"OR","prefix_length":0,"max_expansions":50,"fuzzy_transpositions":true,"lenient":false,"zero_terms_query":"NONE","boost":1.0}}},"sort":[{"price":{"order":"desc"}}]}]
로그 항목에서 확인할 수 있는 정보는 다음과 같습니다.
- 타임스탬프(날짜/시간)
- 로그 레벨
- 슬로우 로그 유형
- 노드 이름
- 인덱스
- 샤드 번호
- 소요 시간
- 쿼리 본문(_source)
너무 오래 걸린다고 판단되는 쿼리를 확보했다면, 다음 도구들을 사용해 쿼리를 세분화해서 분석할 수 있습니다.
_profile API
_profile API는 검색에 대한 방대한 정보를 제공하며, 각 샤드에서 무슨 일이 일어났는지 검색 구성 요소 하나하나의 개별 실행 시간까지 세분화해서 보여줍니다. 검색이 복잡할수록 _profile 출력도 더 장황해집니다.
Kibana 프로파일링 도구
Kibana®의 프로파일링 도구는 _profile API와 함께 사용하기 좋습니다. 개별 검색 구성 요소와 각각 완료되는 데 걸리는 시간을 폭포(waterfall) 형태로 시각화해 주므로, 쿼리의 문제 지점을 직관적으로 파악할 수 있습니다.
Elasticsearch의 두 단계: Query Then Fetch
이제 느린 쿼리를 찾아냈고 프로파일러로 분석도 했습니다. 하지만 구성 요소별 실행 시간만 봐서는 검색이 빨라지지 않습니다. 그다음은 무엇일까요? 쿼리가 동작하는 방식, 즉 다음의 두 단계를 이해하면 속도와 정확도(관련성) 모두에서 최상의 결과를 얻도록 쿼리를 재설계할 수 있습니다.
Query 단계
- 코디네이터 노드(coordinator node)가 쿼리를 접수합니다.
- 코디네이터가 검색 대상 인덱스(또는 인덱스들)를 식별합니다.
- 코디네이터가 해당 인덱스의 샤드를 보유한 노드 목록을 생성합니다(프라이머리와 레플리카가 섞여 있음).
- 코디네이터가 쿼리를 해당 노드들에 전송합니다.
- 각 노드의 샤드가 쿼리를 처리합니다.
- (기본 설정 기준) 상위 10개 문서에 대해 스코어링이 수행됩니다.
- 결과 목록이 코디네이터 노드로 반환됩니다.
Fetch 단계
- Fetch 단계는 코디네이터 노드에서 시작되며, 각 샤드가 보낸 50개(5개 샤드 × 10개)의 결과 중 최종 상위 10개 문서를 결정합니다.
- 코디네이터가 해당 상위 10개 문서를 달라고 샤드에 요청합니다. (최고 점수 문서들이 한 샤드에 몰려 있을 수도 있고, 여러 샤드에 흩어져 있을 수도 있습니다.)
목록이 반환되면 마스터가 해당 문서들을 쿼리 응답의 _hits 섹션에 담아 제시합니다.
결과 스코어(관련성 점수)
결과 스코어는 Elasticsearch에서 매우 중요합니다. 일반적으로 검색 엔진을 사용할 때 우리는 가장 정확한 결과를 원합니다. 예를 들어 과일인 키위를 검색했는데 결과에 신발 연고(Kiwi shoe polish)가 섞여 있다면 곤란하겠죠. Elasticsearch는 사용자가 지정한 파라미터를 기반으로 쿼리 결과에 점수를 매깁니다. 쿼리 관련성(relevance)은 별도의 블로그 포스트에서 자세히 다룰 예정이지만, 여기서 언급할 필요가 있습니다. 검색이 아무리 빨라도 결과가 원하는 것이 아니라면 그 검색은 시간 낭비에 불과하기 때문입니다. 그렇다면 어떻게 검색 속도를 높일 수 있을까요?
필터(Filters)
검색 성능을 개선하는 한 가지 방법은 필터를 활용하는 것입니다. 필터 쿼리(filtered query)는 여러분의 가장 좋은 친구가 되어 줄 것입니다. 필터를 먼저 적용하는 것이 중요한데, 검색에서 필터는 문서 스코어에 영향을 주지 않기 때문에 적은 리소스로 검색 범위를 크게 줄일 수 있기 때문입니다.
필터 쿼리와 불리언(boolean) 매치를 조합하면, Y 포함 여부로 스코어링하기 전에 X를 포함하는 모든 문서를 먼저 걸러낼 수 있습니다. 또한 필터는 캐싱도 가능합니다.
필터가 Elasticsearch 쿼리를 빠르게 만드는 유일한 방법은 아닙니다. 쿼리 성능을 개선하는 더 많은 방법은 다음 블로그 포스트에서 다루겠습니다.
요약
몇 가지 간단한 단계만으로도 쿼리를 최적화할 수 있습니다.
- 슬로우 로깅을 활성화하여 오래 실행되는 쿼리를 식별합니다.
- 식별된 검색을 _profile API로 분석하여 개별 구성 요소의 실행 시간을 확인합니다.
- 필터, 필터, 그리고 또 필터를 활용합니다.
Elasticsearch 운영에 대해 궁금한 점이 있으신가요? 저희는 무료 체험을 포함한 모든 인스턴스에 Elasticsearch 전문 지식을 갖춘 데이터베이스 관리자의 지원을 함께 제공합니다. 개발에 집중하세요. Elasticsearch 관리는 저희가 책임지겠습니다.
Elasticsearch 6과 Kibana를 무료로 체험해 보고 싶으신가요? 지금 시작하시고 궁금한 점이 있으면 언제든 알려주세요.
Feedback 탭을 통해 의견을 남기거나 질문할 수 있으며, Sales Chat을 클릭해 지금 바로 상담을 시작할 수도 있습니다.