본문 바로가기
Elasticsearch

[Elasticsearch] 성능 향상을 위한 _id 필드 사용 지양하기

by gungle 2024. 2. 14.

Elasticsearch에서는 종종 몇 개의 필드만 검색해야 하는 경우가 많다. 이런 경우, 해당 필드들에 대해 `doc_values` 를 사용하면 쿼리 성능을 크게 향상시킬 수 있다.

이때, 기본적으로 검색되는 하나의 필드가 바로 `_id` 필드이다. 겉으로 보기에 이 필드는 아무 문제가 없어보이지만, 시스템 성능을 저해할 수 있다.

또 다른 전형적인 Elasticsearch 사용 사례는 보조 데이터베이스로서의 역할, 특히 전문 검색에서 관련 문서의 ID를 검색하는 데 Elasticsearch를 사용하는 경우이다.

An example where Elasticsearch is used for full-text search and PostgreSQL as the source of truth to retrieve the final data.

대부분의 Elasticsearch 를 사용하는 사용자는 외부 ID를 `_id` 필드에 저장하여 일을 간단하게 하고 데이터 중복을 피하는 경우가 많다. 혹은 `_id`를 직접 사용하지 않더라도 기본적으로 반환되기 때문에 간접적으로 사용하게 되는 경우도 있다.

이 경우의 문제점은 `_id` 가 `stored_fields` 를 사용하여 저장되며, 이는 `doc_values` 에 비해 읽기 오버헤드가 더 클 수 있다는 것이다. 특히 Elasticsearch 7.10+에서는 `stored_fields` 가 80KB 블록으로 쓰기/읽기 시작했다(이전에는 16KB였음). 이는 Lucene(Elasicsearch의 기반 검색 엔진)에서 문서 압축을 최적화하기 위해 수행된 최적화 작업이며, 특히 작은 문서에 대해 효과적이다.

 

읽기 성능 향상시키기

Elasticsearch 읽기 성능을 향상시키는 방법은 간단하다. `doc_values` 를 사용하고, (_id 필드를 포함하는) `stored_fields` 의 검색을 비활성화하면 된다.

즉, 다음과 같은 쿼리를:

GET test/_search
{
  "query": {
    "match_all": {}
  },
  "_source": false
}

다음과 같이 변환한다:

GET test/_search
{
  "query": {
    "match_all": {}
  },
  "_source": false,
  "stored_fields": "_none_",
  "docvalue_fields": ["my_id_field"]
}
  • "stored_fields": "_none_": 이 설정은 _source와 _id와 같은 모든 stored_fields의 검색을 비활성화한다.
  • "docvalue_fields": ["my_id_field"]: 이를 통해 선택한 doc_values 필드만 검색할 수 있다.

이러한 변경을 통해, Elasticsearch에서 필요한 필드만 효율적으로 검색하여 시스템의 전반적인 읽기 성능을 크게 향상시킬 수 있다. 특히, `_id` 필드와 같이 기본적으로 검색되는 필드의 검색을 비활성화함으로써, 불필요한 데이터 검색으로 인한 오버헤드를 줄이고, 필요한 정보만을 빠르게 검색하는 것이 가능해진다.

 

벤치마크 측정

  • 인덱스 크기: 130만 개 문서
  • 문서 크기: 약 ~4KB
  • 쿼리: 랜덤 매치 조건을 가진 10,000개의 쿼리

The average latency of 10 000 random queries. Lower is better.

결과는 예상대로 비교적 작은 문서 크기에도 불구하고, 결과에서 `_id` 를 제외하고 오직 `doc_values` 필드만 검색하는 것이 지연 시간을 크게 개선한다는 것을 볼 수 있다. 문서의 크기가 커질 수록 격차는 더 심해진다.

이 데이터는 Elasticsearch에서 _id 필드의 사용을 중단하고 doc_values를 활용할 때 읽기 성능이 얼마나 향상될 수 있는지를 명확하게 보여준다. 특히, 대량의 데이터를 빠르게 처리해야 하는 애플리케이션에서는 이러한 최적화가 검색 속도와 시스템 반응성을 크게 개선할 수 있다. 따라서, 성능 중심의 Elasticsearch 환경을 구축하고자 하는 개발자와 시스템 관리자에게 중요한 참고 자료가 될 것이다.

 

Lucene 내부에 대한 심층 분석

`_id` 를 버리고 `doc_values` 를 사용하여 필드를 검색하는 것이 더 효율적일 수 있다는 것은 위에 내용을 통해 보였다.

좀더 내부를 깊이 있게 살펴보면 다음과 같다.

 

Stored Fields

저장된 필드(Stored Fields)의 경우, 각 문서는 연속적으로 모든 저장된 필드를 포함하는 행으로 저장된다. 첫 번째 필드는 `_id` 이며, 그 다음은 다른 개별 저장된 필드들이고, 마지막 저장된 필드는 `_source` 이다.

이 구조는 Elasticsearch에서 데이터를 관리하고 검색하는 방식에 중요한 역할을 하며, 성능 최적화와 관련된 결정을 내릴 때 고려해야 할 핵심 요소 중 하나이다.

How Lucene retrieves stored fields

앞서 설명한 Lucene에서 블록이 저장되는 방식을 단순화하여 설명했다. 실제로는, 이러한 블록들이 공유 사전을 가진 10*8 KB의 서브 블록으로 나뉜다.

이 방식의 이점은 두 가지다. 작은 문서들이 각 블록 내에 여러 개 존재할 경우 더 나은 압축 효과를 얻을 수 있다.

읽기 작업에 있어서는, 개별 서브 블록을 압축 해제할 수 있으므로, 특정 필드/문서를 검색하기 위해 한 서브 블록만 압축 해제할 수도 있다.

값을 검색하기 위해서, Lucene은 먼저 필드 인덱스(.fdx) 파일을 읽어야 한다. 이 파일은 두 개의 단조롭게 증가하는 배열(오름차순)을 저장하는데, 하나는 압축된 문서의 각 블록에 대한 첫 번째 Doc ID용이고, 다른 하나는 디스크 상의 해당 오프셋용이다. Doc ID를 포함하는 배열은 예상되는 Doc ID를 포함하는 블록을 찾기 위해 이진 검색되며, 두 번째 배열에서 디스크 상의 관련 오프셋이 검색된다.

이로 인해 검색해야 할 각 저장된 필드 값에 대해 두 번의 디스크 탐색이 발생할 수 있다.

이 Doc ID들은 Lucene 내부적인 것으로 Elasticsearch의 _id와는 관련이 없다는 점을 주목할 가치가 있다. 이들은 각 Lucene 세그먼트 내에서만 고유하며, 새 문서가 추가될 때마다 증가한다.

 

DocValues

Lucene은 DocValues 필드의 모든 값을 연속적으로 함께 저장한다. 이 형식은 열 방식(columnar manner)으로도 알려져 있다.

이 값들은 공간 사용을 최적화하는 다양한 형식으로 저장될 수 있으며, 각각 다른 압축 형식을 사용할 수 있는 블록으로 나눌 수 있다.

이들이 docId 순서대로 저장되므로, Lucene은 일치하는 문서의 필드 값들을 검색하기 위해 docId 순서대로 순차적으로 읽기만 하면 된다.

 

 

Reference

https://luis-sena.medium.com/stop-using-the-id-field-in-elasticsearch-6fb650d1fbae

 

Stop using the _id field in Elasticsearch

These simple changes will dramatically improve Elasticsearch's performance.

luis-sena.medium.com