이승학 버즈니 서비스엔지니어링 팀장

 

▲ 이승학 버즈니 서비스엔지니어링 팀장

[컴퓨터월드] 많은 애플리케이션들은 서비스 사용성과 품질을 보장하기 위해 상당부분 ‘검색’ 기술에 의존하고 있다. 하지만 검색은 다양한 기술의 융·복합체로 쉽게 접근하기 어렵고 여러 가지 어려운 배경 지식을 필요로 한다.

이런 검색 기술의 접근성을 높이고 구현을 용이하게 해주는 솔루션들로는 아파치 루씬(Apache Lucene) 라이브러리에서 파생된 아파치 솔라(Apache Solr), 엘라스틱서치(Elasticsearch)등이 있다. 엘라스틱서치의 필수 구성 요소들을 통해 검색 엔진에 대한 이해도를 높이고 서비스로서의 활용 가능성과 효용 가치를 공유하고자 강좌(6회)를 신설했다. <편집자 주>

 

1. 엘라스틱서치 파이썬 클라이언트를 이용한 ‘검색’ 입문 (2018.9)
2. 엘라스틱서치로 검색엔진 전환하기 (2018.10)
3. 엘라스틱서치 활용(1): 자동완성 (2018.11)
4. 엘라스틱서치 활용(2): 로그 시스템 (2019.1)
5. 엘라스틱서치 단계별 최적화 (이번호)
6. 검색엔진 관리 자동화

 
엘라스틱서치는 기본 설정이 비교적 잘 되어 있기 때문에 설치하고 바로 사용해도 별다른 어려움을 겪지 않는다. 또한 인덱싱, 질의(검색) 등의 기본적인 가이드만으로도 그 활용성에 제약을 받지 않는다. 하지만 그럼에도 불구하고 규모의 사용성과 부분적인 특수성에 따라서 최적화를 추구하게 된다.

대단위 규모의 서비스/시스템을 위해 엘라스틱서치를 운영한다면 다양한 최적화 포인트를 다루어야 한다. 하지만, 각기 다른 머신환경과 네트워크 환경 그리고 쓰임에 따라서 필요한 최적화 요소들이 다르기 때문에 모든 것을 만족시키는 최적화 방법을 얻기는 힘들다. 그중에서 어느 조건이든지 공통적으로 쉽고 간단하게 적용할 수 있는 방법들을 소개해 보려고 한다.

 

▲ 엘라스틱서치 단계별 최적화 포인트

# Indexing
‘_all’ field[indexing]
‘_all’ 필드는 text타입 필드다. text타입은 analyzer가 동작함을 의미하고, analyzer의 동작은 인덱싱 성능에 영향을 준다는 것을 의미한다. 이 필드는 사용자가 설정/정의한 필드가 아니고 엘라스틱서치가 생성/관리하는 필드이며, 문서에 포함된 모든 필드를 하나의 문자열로 병합하여 갖고 있게 된다. 문서의 사용목적과 관계없이 추가로 인덱싱 정보가 늘어나게 된다.

엘라스틱서치가 이 필드를 제공할 때 주는 이점은 명확하다. 명시적으로 검색대상의 필드를 설정하지 않아도 검색이 가능하도록 사용성을 확장시켜준다. 언뜻 보면 검색엔진의 친절함과 배려로 보이는 이 필드의 기능을 위해 인덱싱 성능에 영향을 주는 포인트가 된다.
* 버전 6.0.0부터는 ‘_all’ 필드는 기본 활성화가 아닌 custom으로 사용가능하도록 변경됐다.

Index fields mapping[indexing]
엘라스틱서치는 태생이 정형/비정형 데이터 형태를 지원하기 때문에 명시적인 스키마구성을 하지 않아도 된다. 이는 데이터를 유연하게 사용할 수 있고 전통적인 RDB의 단점을 상쇄시켜주는 역할을 해주지만, 반대로 성능 관리 측면에서는 손실을 발생시킨다.

사용할 데이터를 최대한 정형화 시키고 추가적인 비정형 데이터에 대해서는 탄력적인 맵핑구조를 사용한다면 데이터의 유연성을 취하면서 성능저하의 단점을 커버할 수 있다.

 

 

▲ 명시적 필드 타입 맵핑

 

 

▲ 명시적 필드 타입 맵핑 인덱싱 시간 비교

Refresh interval[indexing, querying]
새로운 문서가 추가되거나 기존의 문서가 변경/삭제 되는 경우 엘라스틱서치는 해당 내용을 곧바로 적용하는 것이 아니라 설정된 refresh interval의 수치에 따라 서비스 가능한 문서 상태로 만들어 준다. 단순한 개념과 간단한 설정 방법이지만 인덱싱과 질의 성능에 큰 영향을 준다.

대량의 문서가 새로 추가 되는 경우를 가정해보자. refresh 타이밍이 작을 경우 색인되는 문서들을 바로 서비스 가능 시점이 실시간에 가까워지지만, 서비스 가능하도록 문서 관련 프로세스가 자주 동작을 하기 때문에 인덱싱이나 질의를 처리하는 프로세스에 영향을 주게 된다. 반대로 타이밍이 커질 경우 문서 처리 관련 프로세스의 동작이 잦아들어 다른 프로세스들이 비교적 빠르게 동작하게 된다. 하지만 문서의 실시간성을 떨어지게 된다.

단순하게 생각해보자. 인덱싱 주기 마다 종료 시점에 refresh를 해주고 질의를 주로 처리하는 주기에는 refresh가 일어나지 않도록 하는 것이 최선의 선택이다. refresh 주기를 잘 조정함으로서 생기는 가장 큰 이득은 최고의 성능을 만들어내는 것보다 기복없이 평균적인 성능 수준을 유지시켜준다는 것에 있다. 덧붙여 refresh_interval의 기본값은 1(초)다.

 

 

▲ refresh interval 무제한 설정에 따른 인덱싱 시간

 

Primary shard[indexing, querying]
인덱스의 크기에 따라 primary shard 구성의 변화는 저장되는 문서의 파편화 정도로 연결되기 때문에 인덱싱과 질의 성능에 직접적으로 영향을 준다. 문서의 파편화라는 측면에서 엘라스틱서치의 문서 저장 방식을 짚어 보자. 엘라스틱서치는 새로운 문서가 생성될 때 세그먼트 단위로 저장하게 된다. 이 세그먼트단위들이 실제 저장되는 공간이 바로 샤드가 된다. 하나의 문서를 구성하는 세그먼트들이 파편화된 상태로 샤드별로 분산되기 때문에 인덱싱에 영향을 주게 되고 나아가 질의 단계에도 영향을 주게 된다.

극단적인 비교를 통해서 인덱싱 시간의 차이를 확인해보자. 150,000개의 문서를 단일 머신 노드를 기준으로 한 개의 샤드와 50개의 샤드 사용에 따라 인덱싱 시간의 차이는 아래와 같다. 자세히 들여다 보지 않아도 매우 큰 차이를 알 수 있다. 극단적인 실험이지만 엘라스틱서치의 운용 규모와 구축 구조에 따라서 필요한 수준의 샤드를 할당하는 것이 필요하다.

 

▲ Primary shard 조정에 따른 인덱싱 시간

# Querying
Filter context[querying]
질의를 만들 때 query context와 filter context로 크게 나뉘게 된다. 유사도, 스코어 등의 결과를 요하는 경우 query context를 사용하고, 조건에 해당 여부나 스코어에 관계없이 결과를 도출하는 경우 filter context를 사용한다. 이때 query, filter의 선택에 따라서 검색 결과와 상관없이 성능에 영향을 준다.

filter는 오로지 질의에 대해 일치하는 검색 결과에만 집중되어 있고, query와 다르게 내부에서 캐싱이 동작하기 때문에 상대적으로 빠른 검색 결과를 제공한다. query와 filter의 사용으로 각각 유효한 부분을 고려한다면 질의 성능 향상에 도움이 된다.

아래의 비교 예시는 단일 노드상의 동시 발생 되는 쿼리가 없는 환경의 비교 결과이기 때문에 그 차이가 크지 않고 사용상에 의미가 없어 보일 수 있다. 하지만 서비스 상태로 진입했을 때의 간극은 개선점을 찾기 힘들 정도로 성능 편차를 일으키기 때문에 질의 작성 시에 꼭 확인해야 할 사항이다.

 

 

▲ query -> filter에 따른 성능 차이

 

Text -> Keyword field[indexing, querying]
문자열을 지원하는 필드가 Text와 Keyword로 제공된다. 이 두 필드의 큰 차이는 analyzer 사용 유무에 있다. 또한 네이밍에서도 알 수 있듯이 문자열 길이에 따른 선택을 종용한다. 질의에 필요한 필드가 analyzer된 값을 필요하지 않을 경우, 즉 형태소 분석 값의 사용 유무에 따라서 문자열 타입을 선택하면 된다. analyzer를 사용한다는 것은 문서의 인덱싱 시점부터 질의 수준까지의 사용주기에 모두 관여 되는 것을 의미한다.

형태소 분석이 필요한 검색 대상 필드가 아닌 경우, Keyword 타입을 사용함으로써 인덱싱 소요 시간 절약부터 질의에 사용되는 검색 요청 값을 추가로 처리하지 않아도 된다. 이는 최적화 영향 범위가 엘라스틱서치 사용 전역에 걸쳐있는 것을 의미하므로 필히 고려해할 부분이다.


# External
External Cache[querying]
엘라스틱서치는 filter에 대해서만 캐싱을 지원한다. 하지만 필요에 따라 유사도나 스코어를 기반으로 한 정렬된 검색 결과를 사용하게 된다. 다양한 질의들이 동시간에 엘라스틱서치를 사용하고 요청하고 처리하는 데는 한계가 있기 때문에, 일정 시간 동안 문서들이 변하지 않거나 항상 최신의 문서를 검색결과로 제공되지 않아도 된다면 외부의 캐싱을 구성해 검색 결과를 일정 시간동안 사용하는 것을 추천한다.

실시간으로 검색 결과를 제공받는 것이 검색 품질 측면에서 가장 최신의 정확한 가치를 만들어 주지만, 이것이 항상 유효하고 유지되어야 하는 것은 아니다. 때문에 적절한 캐시 사용으로 순간적으로 집중될 수 있는 엘라스틱서치의 부담을 줄여주고 사용자에게 보다 빠른 검색 결과를 제공 할 수 있다.

캐시는 다양하게 구현될 수 있지만, 캐시 시스템 구성에 큰 노력을 들이지 않고 효과를 보고자 한다면 ‘AWS ElasticCache Memcached’ 사용을 권한다. 직접 시스템 관리에 리소스를 할애하지 않고 사용에만 초점을 맞출 수 있다. 물론 여타 다른 클라우드 기반의 서비스 제품군들도 비슷한 사용을 제공할 것이다.

 

▲ 검색 결과에 대한 캐싱 운용 예시

Pagination pre set querying[querying]
질의가 발생될 것을 사전에 예측할 수 있거나 반복적으로 사용되는 질의를 External Cache에 적재함으로서 엘라스틱서치의 실시간 상황에 관계없이 단시간에 검색결과에 도달할 수 있다. 이 방법은 사용하는 도메인에 따라서 필요한 측면을 다양하게 고려해야 하지만, 기능적으로는 단순한 아이디어에서 시작되기 때문에 적용이 간단하다.

커머스 서비스를 예를 들어보자. 쇼핑하는 하는 사용자들은 상품을 검색하고 습관적으로 하는 행동으로 다양한 상품 정보를 얻기 위해 스크롤링을 하면서 추가적인 정보를 취득한다. 여기서 검색의 행위에 페이지네이션의 기능적 요소가 숨어있다. 단순하게 사용자가 1페이지를 요청했으면, 별도의 명시적인 요청이 없어도 다음 2페이지의 내용을 캐시에 준비해둔다면 요청-응답 시간에 대한 큰 이점을 가질 수 있다.

이러한 요소는 특정 도메인에 국한된 내용이 아니기 때문에 범용적으로 엘라스틱서치의 부담을 줄여주고 서비스-시스템적으로 운용의 이점을 줄 것이다.

 

 

▲ pagination pre set 적용 예시

 


# 마치며
개발자 또는 사용자가 엘라스틱서치 또는 검색엔진을 사용하면서 가장 중요하게 여길 수 있는 부분이 성능이다. 아무리 좋은 검색 결과를 내보내더라도 1초, 10초, 1분, 심하게는 수분이 넘게 걸리는 응답/지연 시간이 발생하면 좋은 서비스 또는 제품으로 인식하기 어렵다.

엘라스틱서치가 활용되는 영역 또는 서비스 형태는 매우 다양하다. 때문에 그에 따른 최적화 방법에 정답을 쉽게 제시하지 못한다. 도메인별로 클러스터 세팅이 다른 것은 물론, 데이터 노드와 마스터 노드, 인제스트 노드 등의 구성과 구조를 획일화 시키지 못한다는 점은 엘라스틱서치의 활용 가능성을 높여주기는 하지만 최소한의 공용 가이드를 지속적으로 추가 개선하도록 요구한다.

최적화라는 키워드가 어렵고 무거운 인식과 부담감을 주지만 몇 가지 간단한 방법들을 통해 비교적 효과적인 결과로 이어질 수 있음을 간접적으로 확인했다. 엘라스틱서치의 최적화는 버전에 따라서 유효한 부분과 더 이상 고려하지 않아도 되는 부분으로 나뉜다. 다행히도 직전 버전이나 지속적인 문제를 일으키는 부분에 대해서 자체적으로 최적화를 위한 변경사항이 빈번하게 발생하기 때문에 가능한 항상 최신의 스테이블 버전을 유지-관리 하는 것을 추천한다.

저작권자 © 컴퓨터월드 무단전재 및 재배포 금지