본 포스팅은 2024년 04월 25일을 기준으로 작성되었습니다.
Elasticsearch의 인덱스는 역인덱스(Inverted_Index)구조로 되어있습니다. 그리고 Elasticsearch의 빠른 검색 속도는 이러한 역인덱스 데이터 구조와 큰 연관이 있습니다.
#역인덱스 구조가 무엇인지, 기존의 관계형 데이터베이스와 어떤 점이 다르고, 어느 부분에서 더 효율적인지 살펴봅니다.
위와 같은 RDBM 데이터가 있습니다. 여기서 "fox"라는 단어를 검색한다고 가정합니다.
검색 결과 "fox"가 들어간 doc을 제외한 나머지 doc는 제외됩니다. 그러나 이러한 검색 과정에서 각각의 doc를 하나하나 전부 읽으며 "fox"가 포함되어 있는지 확인하기 때문에 기본적으로 속도가 느립니다.
Elasticsearch는 완전히 다른 구조의 데이터로 이러한 문제를 해결합니다.
위는 Elasticserach의 역인덱스 구조로 동일한 데이터를 저장한 것입니다.
각 Text를 Term으로 쪼갠 후 각 Term마다 어떤 doc에 포함되는지 배열(array)로 나타내었습니다. (Elasticsearch에서는 이렇게 tokenizing되어 추출된 키워드를 "Term"이라고 부릅니다.)
이러한 구조는 기존의 RDBM 구조처럼 각 행 안의 Text 안에 "fox"의 포함 여부를 일일이 체크할 필요가 없습니다. 대신, 검색한 값과 매칭되는 Term만 빠르게 찾은 다음, 그 Term을 포함하고 있는 doc의 배열을 그대로 가져오기만 하면 됩니다.
그 결과 기존의 RDBM과 비교하면 훨씬 빠른 검색 속도를 보여줍니다.
Term 리스트에서 "fox"만 빠르게 찾고, 그에 해당되는 doc들만 불러오면 끝입니다.
이런 역 인덱스 구조를 데이터가 저장되는 과정에서 만들기 때문에 Elasticsearch는 데이터를 입력할 때 저장이 아닌 색인(indexing)을 한다고 표현합니다.
대부분의 필드는 기본적으로 인덱싱되어 있어 검색이 가능합니다.
앞에서 설명한 역인덱스(inverted_index) 구조를 사용하면, 쿼리를 통해 검색어를 고유한 정렬된 키워드(TERM) 목록에서 검색할 수 있으며, 해당 검색어가 포함된 document를 즉시 불러올 수 있습니다.
숫자 타입, data 타입, boolean 타입, IP타입, geo-point 타입, keyword 타입도 인덱싱되지 않고 doc_value의 값만 활성화시킬 수 있습니다.
doc_value의 쿼리 성능은 인덱싱 구조에 비해 느립니다. 하지만 쿼리 사용 빈도가 낮고, 쿼리 성능이 중요하지 않은 필드에 대해서는 디스크 사용량과 쿼리 성능 사이의 절충점을 제공할 수 있습니다.
예를 들어, Nested 구조 안의 document가 매우 많을 경우의 검색을 가정한다면, 이때는 검색을 수행하는 시간보다 안에 있는 document들을 실제로 fetch해서 가져오는데 걸리는 시간이 더 오래 걸리게 됩니다. (즉, memory와 cpu는 일을 안하는데 검색 속도는 느립니다.)
이런 경우 doc_values를 통해 직접 접근하여 fetch를 수행한다면, 빠르게 검색할 수 있습니다.
session_id 필드는 doc_value 구조만 갖도록 인덱싱이 비활성화 되어있습니다.
기본적으로 모든 필드는 doc_value가 활성화 되어있습니다.
그러나 필드를 정렬 및 집계하거나 script에서 해당 필드의 값에 엑세스할 필요가 없는 경우 디스크 공간 절약을 위해 doc_value를 비활성화 하기도 합니다.
기본적으로 별다른 설정 없이 검색 결과로 나온 json 형식의 검색 결과는 모두 _source 안에 압축되어 반환됩니다.
_source 필드는 script에서 ctx._source.field_name 형태로 접근 가능합니다.
_source 필드는 이렇듯 매우 편리하지만 디스크에서 상당한 양의 공간을 차지합니다.
이를 해결하기 위해 source document를 그대로 디스크에 저장하는 대신에 검색할 때 mapping 설정의 _source에서 mode: synthetic 로 활성화하여 즉석으로 source 콘텐츠를 재구성 가능합니다.
이렇게 하면 source document를 그대로 저장하고 쿼리 명령을 내리는 시점에 로드하는 기존 방식보다 느리지만 저장 공간을 많이 절약할 수 있습니다.
검색시 _source parameter에서 포함/제외시킬 필드를 설정할 수 있습니다.
이는 _source 필드가 저장되기 전에 _source 필드의 일부를 잘라내는 방식입니다.
예시에서는 includes로 해당 조건에 맞는 필드들만 남기고, exclude로 남은 필드들 중 특정 조건에 맞는 필드들을 제외해줍니다.
_source 필드에 접근하는 것은 doc_values를 사용하는 것보다 훨씬 느립니다.
_source 필드는 결과 당 여러 개의 필드를 반환하는데 최적화된 반면, doc_values는 많은 document 중에서 특정 필드 값에 접근하는데 최적화되어 있습니다.
예를 들어, 검색 결과에서 상위 10개의 hits를 반환하는 것에 대한 script를 작성할 때는 ctx._source.field_name를 사용하는 것이 좋지만, 그 외 다른 검색 및 aggregation을 수행할 때는 doc['field_name'].value를 사용하는 것이 좋습니다.
위에서 설명드린 역 인덱스(inverted index) 구조의 경우, 각 term마다 해당 term을 포함하고 있는 document들의 배열을 갖고 있는 형태입니다.
그러나 Fielddata 구조는 key-value 구조로, key값은 각각의 document이고, value값은 각 document 안의 필드 정보를 담고 있습니다.
이러한 구조는 기존 역인덱스 구조처럼 keyword를 검색하고 document를 찾는 대신에, document를 검색하고 해당 document의 필드에 있는 검색어를 찾을 수 있습니다.
또한, aggregation(집계)시 document의 key로 field 정보를 담은 데이터 구조가 필요합니다.
그렇기에 fielddata타입의 필드는 text타입이더라도 이러한 fielddata의 검색 구조를 통해 aggregation이나 dashboard에서 각종 집계가 가능합니다.
하지만 Fielddata의 경우, In-memory 구조로 동작하기 때문에 많은 Heap-memory를 소비하게 됩니다.
(로딩시 segment의 life-cycle 동안 존재하게 됨 -> 높은 비용 발생)
이와 같은 이유로 Fielddata의 기본값은 false이며, 필요한 경우에만 fielddata=true로 옵션을 변경하여 사용하는 것을 권장합니다.