brunch

You can make anything
by writing

C.S.Lewis

by 강진우 Oct 20. 2017

ElasticSearch 인덱싱 성능 최적화

ElasticSearch 운영

오늘 다룰 주제는 ElasticSearch (이하 ES)에서 인덱싱 성능을 최적화하기입니다. 사실 ES의 기본 설정은 잘되어 있기 때문에 RPM으로 설치하고 그냥 사용하기만 해도 별다른 어려움 없이 사용할 수 있습니다. 하지만 몇 가지 설정들을 바꿔줌으로써 더 많은 양의 문서를 인덱싱 하게 할 수 있습니다.

먼저 이번 포스트를 이끌어 갈 세 가지 키워드를 소개하겠습니다.


1. _all 필드

2. static mapping

3. refresh_interval

4. primary shard


우리는 오늘 이 네 가지 키워드를 통해서 ES가 더 많은 문서들을 인덱싱 할 수 있도록 튜닝해 나갈 것입니다. 그럼 하나씩 살펴보겠습니다.


기본 설정에서의 성능


우선 본격적인 튜닝에 앞서 아무것도 변경하지 않은 기본 설정에서는 어느 정도의 성능을 발휘하는지 살펴보겠습니다. 테스트는 총 10개의 text 형태의 필드를 가지고 있는 5000개의 문서를 _bulk API를 통해서 색인한 후 그 결과로 반환되는 소요시간, took 값을 비교합니다.

테스트에 사용한 스크립트는 조금 더 다듬어서 github에 올릴 예정입니다.

먼저 기본 설정에서의 결과입니다.

python ./elasticsearch_perf_test.py --url es --index_name normal_test --documents 5000

{"acknowledged":true}
elapsed time : 1057 ms

head 플러그인을 통해서 잘 생성되었는지 확인해 보겠습니다.

head 플러그인을 통해 인덱스의 생성 확인

기본 설정에서는 5000개의 문서를 색인하는데 약 1초 정도의 시간이 소요된 것을 볼 수 있습니다. 그럼 이제 튜닝을 시작해 보겠습니다.


_all 필드


첫 번째 키워드는 _all 필드입니다. _all 필드는 사용자의 문서에 있는 필드가 아니고 특별한 목적에 의해 ES가 생성하는 필드입니다. 

참고 자료 : https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-all-field.html 

이 필드는 해당 문서의 모든 필드를 하나의 스트링처럼 길게 이어서 새로운 text 형태의 필드를 만들고 이 필드의 내용을 인덱싱 합니다. 사용자의 의도와는 다르게 필드 하나가 추가된다고 볼 수 있습니다. 게다가 문서에 존재하는 필드 수에 따라 아주아주 긴 text 필드가 추가될 수 있습니다. text 필드는 analyzer를 거치기 때문에 길면 길수록 인덱싱 성능에 악영향을 끼칩니다. 성능에 악영향을 끼칠 수 있는 필드를 왜 기본적으로 생성할까요? 사용자는 _all 필드를 통해서 필드명 없는 검색을 할 수 있게 됩니다. 예를 들어 아래와 같은 형태의 문서가 있다고 가정해 보겠습니다.

예제 문서

원래대로 라면 검색을 하기 위해서는 first_name : John 과 같은 형태로 쿼리를 짜야 하지만 _all 필드가 켜져 있다면 그냥 John 이라고 쿼리를 입력하기만 해도 검색이 됩니다. 그래서 _all 필드를 disable 하게 되면 인덱싱 성능이 향상되지만, 필드명 없는 검색이 불가능해집니다. 따라서 사용하는 워크로드에서 필드명 없는 검색이 있는지 없는지를 확인하고 enable/disable을 결정해야 합니다.

그렇다면 _all 필드를 disable 하게 되면 얼마나 빨라지는지 확인해 볼까요? 이 작업을 위해 먼저 아래와 같이 템플릿을 생성해서 등록해 보겠습니다.

curl -XPUT 'http://es:9200/_template/all_field_test' -d '{
  "template":"all_field_test",
  "mappings": {
    "type1": {
      "_all": {
        "enabled": false
      }
    }
  }
}'

all_field_test라는 이름의 템플릿을 만들었으며, 이 템플릿은 all_field_test라는 이름을 가진 인덱스가 생성될 때 type1 이라는 타입은 _all 필드를 생성하지 않게 합니다. 그리고 테스트를 다시 돌려 보겠습니다.

python ./elasticsearch_perf_test.py --url es --index_name all_field_test --documents 5000

{"acknowledged":true}
elapsed time : 770 ms

기존에 1초가량 소요되었던 시간이 700 ms 단위로 내려왔습니다. 무려 30% 정도의 성능 향상이 이루어졌습니다. 


static mapping


두 번째 키워드는 static mapping 입니다. ESschemaless 구조이기 때문에 미리 인덱싱할 문서의 필드를 정의하지 않아도 됩니다. ES는 첫 번째로 인입된 문서의 내용을 분석해서 가장 적합한 형태로 각각의 필드를 매핑합니다. 그렇기 때문에 편하지만 최적화되지는 않았습니다. 특히 text 필드와 keyword 필드가 그런데요, 둘 다 문자열을 나타내는 필드이지만, 큰 차이를 가지고 있습니다.

이번는 아래와 같은 템플릿을 만들어 보겠습니다.

curl -XPUT 'http://infra-es-perftest01.dakao.io:9200/_template/static_mapping_test' -d '{
  "template":"static_mapping_test",
  "mappings": {
    "type1": {
      "_all": {
        "enabled": false
      },
      "properties": {
        "field-0": {
          "type": "keyword"
        },
.... (중략) ....
        "field-9": {
          "type": "keyword"
        }
      }
    }
  }
}'

아까 _all 필드를 disable 했던 템플릿의 내용에 static mapping 내용을 추가합니다. static_mapping_test라는 이름으로 인덱스가 만들어지면 field-0부터 field-9까지의 필드가 정의된 채로 생성됩니다.

이때의 결과는 어떨까요?

python ./elasticsearch_perf_test.py --url infra-es-perftest01.dakao.io --index_name static_mapping_test --documents 5000

{"acknowledged":true}
elapsed time : 356 ms

역시 이번에도 많이 줄어들었습니다. 700ms 대에서 300ms 대로 절반 이상 줄어들었습니다. 

text 필드가 keyword 필드로 많이 바뀔수록 그 차이는 더 크게 날 겁니다.

_all 필드와 static mapping 만 적용했는데도 벌써 1초에서 300ms 수준으로 상당한 양의 성능 향상 일어났습니다.


refresh_interval


세 번째 키워드는 refresh_interval입니다. ES는 새로운 문서가 인입되었을 때 인덱싱을 하고 그 결과를 세그먼트라는 단위로 저장합니다. 이렇게 생성되는 작은 크기의 세그먼트들은 백그라운드로 머지되어 점점 큰 세그먼트가 되어 갑니다. 이때 ES가 세그먼트를 생성하는 것을 refresh라고 부르며 이 세그먼트를 생성하는 주기를 refresh_interval을 통해서 조절할 수 있습니다. refresh_interval의 기본값은 1초입니다. 그래서 새롭게 인덱싱 된 문서는 1초 이내에 검색이 되며 이를 통해 near realtime search engine이라는 콘셉트를 구현할 수 있게 됩니다. 당연히 세그먼트를 생성하는 작업은 성능에 영향을 줄 수밖에 없습니다. 그래서 이 값을 기본 1초에서 더 크게 늘리면 더 많은 양의 데이터를 인덱싱 할 수 있게 됩니다. 

그래서 이번 테스트는 조금 다르게 단위 시간 동안 얼마나 많은 양의 문서를 인덱싱 할 수 있느냐를 테스트해 보겠습니다. 아래와 같이 한 인덱스는 기본 값으로, 다른 인덱스는 refresh_interval을 60초로 설정해서 120초 동안 데이터를 넣어 보겠습니다.

python ./elasticsearch_perf_test.py --url infra-es-perftest01.dakao.io --documents 1000 --threads 10 --index_name refresh_interval_test --period 120

사실 결과는 조금 놀랍습니다.

refresh_interval 테스트 결과

둘 다 120초 동안 데이터를 입력했는데 기본 1초로 설정된 인덱스는 120초 동안 355,000개의 문서를 refresh_interval이 60초로 설정된 인덱스는 같은 시간 동안 356,000개의 문서를 인덱싱 했습니다. 이 정도면 차이가 없다고 봐도 됩니다. 하지만 다른 사이트에서의 테스트 결과는 refresh_interval을 조절해서 상당한 양의 차이를 얻었다고 합니다. 

참고 자료 : https://sematext.com/blog/elasticsearch-refresh-interval-vs-indexing-performance  

한 번 읽어보시면 좋을 것 같습니다.

아마도 인입되는 데이터의 형태나 기간 등이 영향을 끼친 것이라고 추측합니다.


primary shard


마지막으로 다룰 키워드는 primary shard의 개수입니다. 모든 인덱싱은 primary shard에서 일어납니다. 그래서 primary shard의 개수가 몇 개 이냐에 따라서 인덱싱 성능이 달라질 수 있습니다. 이번에는 primary shard의 개수를 변경해 가면서 테스트해 보겠습니다.

primary shard 갯수에 따른 took의 변화

primary shard의 개수가 늘어날수록 took이 줄어드는 것을 볼 수 있습니다. 점점 큰 폭으로 줄어들다가 어느 순간 반대로 늘어나는 것도 확인할 수 있습니다. 

이는, primary shard가 늘어날수록 인덱싱 성능이 좋아지지만, 무조건 많은 양의 primary shard가 좋은 성능을 이끌어 내는 것은 아니다 라는 것을 보여주고 있습니다. 

실제 문서의 크기, 필드 개수, 필드 타입 등의 환경과 서버의 성능에 따라 적합한 양의 primary shard의 개수가 다를 수는 있겠지만, 어쨌든 적합한 양의 primary shard의 개수가 존재한다는 것을 보여주고 있습니다. 그래서 테스트를 통해서 사용하고자 하는 환경에서 적합한 양의 primary shard의 개수를 찾아야 합니다.


마치며


ES는 튜닝하지 않은 기본값으로도 훌륭한 성능을 보여주지만 조금만 관심을 가지고 살펴보면 동일한 환경에서도 더 많은 양의 문서를 색인하게 할 수 있습니다. 이번 글에서 다루었던 주요 내용은 아래와 같습니다.


1. _all 필드는 불필요하면 끄는 것이 인덱싱 성능을 높이는 효과가 있다.

2. 더 많은 양의 문서를 인덱싱 하고 싶다면 가급적 static mapping을 통해서 인덱싱 작업을 최적화해야 할 필요가 있다.

3. refresh_interval은 새로운 세그먼트의 생성 주기를 변경하기 때문에 늘려주면 인덱싱 성능을 높이는데 효과가 있다. 다만, 환경마다 크게 효과가 없을 수도 있다.

4. 적합한 양의 primary shard를 선정하는 것이 더 많은 문서를 인덱싱 하는데에 도움을 준다.


혹시라도 내용이 잘못되었거나 부연 설명이 필요한 부분이 있으면 말씀해 주세요~


ps. 제일 마지막엔 셀프 책 광고를.. ㅋㅋ

http://www.yes24.com/24/goods/44376723?scode=032&OzSrank=3


매거진의 이전글 rejected exception의 의미와 조치 방법
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari