Category Archives: Elasticsearch

[번역] 로그 파일에 대해 Elasticsearch 사용하기

원문 : Using Elasticsearch for logs
By Radu Gheorghe | 19 May 2012

(2012년에 쓴 글이라, 지금으로써는 정보의 효용성이 없어 보인다)

로그를 저장하기 위해 Elasticsearch를 사용하고자 한다면, 이 문서를 통해서 Elasticsearch를 구성하는 방식에 대한 도움을 얻을 수 있다.

다수의 장비에서 로그를 수집하여 Elasticsearch에 저장하고자 한다면, 아래에서 하나를 선택해볼 수 있다.

  • Graylog2
    Graylog2를 중앙 서버에 설치하면, Graylog2가 알아서 Elasticsearch에 로그를 저장해준다. 그러면 깔끔한 인터페이스를 사용해서 저장된 로그를 검색할 수 있다.
  • Logstash
    꽤 다양한 기능을 제공한다. 저장할 수 있는 로그의 종류(input)가 다양하며, 로그를 변환할 수 있는 방식(filter)도 많고, 변환된 로그를 저장할 수 있는 대상(output) 또한 다양하다. 무엇보다도 Elasticsearch에 직접 저장할 수 있는데, 이때 RabbitMQ의 Elasticsearch river를 사용할 수 있다.
  • Apache Flume
    플럼(Flume)을 사용하면 로그를 데이터소스(data source)로부터 수집한 후, 데코레이터(decoratro)를 사용하여 변환한 다음, 다양한 싱크(sink)를 이용해서 로그를 저장할 수 있다. 그중에서도 elasticflume sink를 살펴보라.
  • rsyslog에 대한 omelasticsearch output 모듈
    어플리케이션 서버에서 rsyslog를 사용하여 로그를 Elasticsearch에 직접 저장하거나, 또는 중앙 서버에서 rsyslog를 사용하여 로그를 저장할 수도 있다. 또한 2가지 방식을 모두 활용할 수도 있다. 설정 방식에 대해서는 rsyslog 위키를 살펴보라.
  • 직접 구현
    예를 들어 로그를 Elasticsearch에 저장하도록 커스텀 스크립트를 직접 만들어볼 수도 있다.

위의 방법 중 어느 방식을 사용하느냐에 따라 최적화 방식 또한 달라진다. 하지만 어느 방식을 사용하느냐에 관계 없이, 아래의 가이드는 도움이 될 것이다.

메모리와 열린 파일 갯수

서버를 Elasticsearch 전용으로만 사용한다면, 일반적으로 전체 메모리 중에서 절반정도를 Elasticsearch에 할당하는게 가장 좋다. 나머지 절반은 시스템 캐시를 위해 사용되는데, 이 점 또한 따라야 할 중요한 기준이다. 메모리를 할당하기 위해서는 ES_HEAP_SIZE 환경 변수를 설정한다. Elasticsearch를 기동하기 전에 적당한 값(예를 들면 2G)으로 설정한다. 설정 가능한 또다른 환경 변수는 ES_JAVA_OPTS로, 실행 스크립트(elasticsearch.in.sh 또는 elasticsearch.bat)로 전달된다. 여기에서 해당 프로세스에 할당할 최소 메모리와 최대 메모리량을 설정하는 -Xms 와 -Xmx 부분을 찾는다. 이 두 속성을 모두 ES_HEAP_SIZE와 동일하게 설정하는 것이 좋다.

또한 열린 파일 갯수를 Elasticsearch에 맞게 적당한 숫자로 할당해야 한다. 일반적으로 32000 또는 64000 정도로 할당하는게 좋다. 열린 파일 갯수를 설정하는 방법은 여기를 참고하라.

인덱스의 갯수

로그를 인덱싱할 때 고려해볼 수 있는 사항은 로그를 모두 하나의 인덱스에 저장하고, 이후에 ttl 필드를 이용하여 오래된 로그가 자동으로 지워지도록 설정하는 방식이다. 하지만 이 방식에는 문제가 따르는데, 로그가 많은 경우 ttl 필드를 사용하면 추가적인 오버헤드가 든다는 점이다. 뿐만 아니라 거대한 하나의 인덱스를 최적화하는 작업은 상당히 오래 걸릴 뿐만 아니라, 처리하는데 소모되는 자원 또한 많다. 따라서 시간 순서대로 인덱스를 생성하는게 가장 좋다. 예를 들면 인덱스를 YYYY-MM-DD와 같은 형태로, 날짜 단위로 생성할 수 있다. 어느 수준의 날짜 단위를 사용하느냐는 로그를 얼마나 유지하느냐에 따라 달라진다. 만약 로그를 1주일가량만 유지한다면, 일단위로 인덱스를 생성하는게 좋다. 반면에 로그를 1년간 유지해야 한다면, 월단위로 인덱스를 생성하는게 가장 나아 보인다. 너무 많은 인덱스를 생성하면, 인덱스 모두에 대해 검색을 한꺼번에 실행하는 경우 너무 많은 오버헤드가 드므로, 인덱스가 많아지는 일은 좋은 선택이 아니다.

이처럼 시간 순서대로 인덱스를 저장하기로 마음먹었다면, 검색할 떄 관련된 인덱스로만 범위를 좁힐 수도 있다. 예를 들어 대다수의 검색이 최근의 로그에 대해서만 실행된다면, “빠른 검색” 기능을 UI에 추가하여 최근의 인덱스만 검색하게 만들 수도 있다.

인덱스 삭제 및 최적화

시간 순서대로 인덱스를 저장하는 경우, 오래된 로그를 삭제하는 일은 아래와 같이 간단하다.

삭제 작업은 순식간에 처리된다(비슷비슷한 크기의 여러 파일을 삭제하는 정도다). cron 작업에 등록하여 밤에 실행되도록 설정할 수 있다.

사용률이 낮은 시간대에 인덱스를 최적화하는 일도 효과가 있다. 인덱스를 최적화하면 검색 속도를 향상할 수 있다. 시간 순서대로 인덱스를 생성하는 경우에 특히 효과적인데, 오늘 이전의 인덱스는 변경될 일이 전혀 없기 때문이다(가장 최근의 인덱스를 제외하면). 따라서 오래된 인덱스에 대해서 단 한번만 최적화를 수행하면 된다.

샤드와 복제본

elasticsearch.yml를 설정하거나 또는 Rest API를 사용하여 인덱스별로 설정하는 일도 가능하다. 자세한 설정 방법은 여기에서 찾아볼 수 있다.

가장 관심을 가져야 하는 속성은 샤드와 복제본의 갯수다. 기본적으로 각 인덱스는 5개의 샤드로 분할된다. 그리고 클러스터에 하나 이상의 노드가 있다면, 각 샤드는 하나의 복제본을 가지게 된다. 따라서 각 인덱스는 모두 10개의 샤드를 가지게 된다. 클러스터에 새 노드가 추가되면, 샤드는 균형을 맞추기 위해 자동으로 다른 노드로 이동된다. 따라서 기본 값으로 설정된 인덱스가 하나 있고 Elasticsearch 클러스터에 11개의 서버가 있다면, 1개의 서버는 어떤 데이터도 저장하지 않게 된다.

각 샤드는 루씬(Lucene) 인덱스이므로, 샤드의 크기가 작을수록 새로운 데이터를 Elasticsearch에 더 빨리 추가할 수 있다. 따라서 인덱스의 샤드 갯수를 늘리면, 데이터를 더 빨리 추가할 수 있다. 이때 주의할 점은 시간 순서대로 인덱스를 생성한다면, 로그는 오로지 “가장 최신의” 인덱스에만 추가되고, “이전의” 인덱스는 전혀 변경되지 않는다는 점이다. 주의해야하는 이유는 샤드가 많으면 많을수록, 저장 공간을 많이 차지할 뿐만 아니라 검색 속도가 늦어지기 때문이다. 따라서 가장 적당한 샤드 갯수를 선택해야 하며, 이는 데이터 추가/검색에 대한 요구사항 및 사용중인 하드웨어 스펙에 따라 달라진다.

반면에 복제본을 저장하면 하나 이상의 노드가 다운되더라도, 클러스터 자체는 정상적으로 운영할 수 있게 된다. 복제본이 많으면 많을수록, 클러스터가 정상적으로 운영하고자 할 때 필요로 하는 노드의 갯수는 적어도 된다. 또한 검색할 때 복제본도 사용되므로, 복제본이 많을수록 검색 속도가 빨라진다. 하지만 인덱스를 생성하는 시간은 늦어지는데 primary 샤드에 변경이 발생한 경우, 변경사항을 나머지 복제본에 모두 반영해야하기 때문이다.

매핑. _source과 _all

매핑이란 문서를 색인하고 저장하는 방법을 정의하는 일이다. 에를 들어 각 필드가 어떤 타입을 가질지를 정의할 수 있다. 매핑이란 문서를 색인하고 저장하는 방법을 정의하는 일이다. 에를 들어 각 필드가 어떤 타입을 가질지를 정의할 수 있다. syslog 메시지라면 문자열 타입으로 정의할 수 있고, 심각도(severity)라면 정수형으로 정의할 수 있다. 매핑을 직접 정의하고자 한다면 여기를 살펴보라.

매핑은 적당한 기본값을 가지며, 각 필드의 타입은 문서가 인덱스에 처음 추가될 때 자동으로 탐지된다. 하지만 기본값을 그대로 활용하는 대신 일일이 설정하길 원할 때도 있다. 예를 들어 새로운 인덱스에 맨 처음 입력된 로그가 “message” 필드에 오로지 숫자 값만을 가진다면, 해당 필드는 long 타입으로 매핑되게 된다. 그런 후 이후에 들어오는 로그에서 99% 이상이 해당 필드에 문자열을 가진다면, Elasticsearch는 해당 로그는 인덱스에 추가하지 않고, 로그에서 “message” 필드에 해당하는 부분이 long 타입이 아니라고 로깅을 남기게 된다. 이러한 경우에는 “message” : {“type” : “string”}와 같이 매핑을 명시적으로 정의해야만 한다. 매핑을 명시적으로 등록하려면 여기를 살펴보라.

특히 시간 순서대로 인덱스를 생성한다면, 설정 파일에 인덱스 템플릿(index templatge)을 생성하는 편이 낫다. 상세한 내용은 여기를 살펴보라. 매핑 뿐만 아니라,샤드의 갯수와 같이 인덱스와 관련된 나머지 속성들도 인덱스 템플릿에 정의할 수 있다.

매핑할 때 문서의 _source에 대한 압축 여부도 설정할 수 있다. _source는 실제 로그 라인으로, 압축 여부를 true로 설정하면 인덱스의 크기를 줄일 수 있다. 또한 설정 방식에 대해 성능이 향상될 수도 있다. 일반적으로 CPU 성능보다는 메모리 사이즈나 디스크 I/O에서 부하가 발생했을 때, source를 압축하면 더 빠른 속도를 어등ㄹ 수 있다. source 필드에 대한 더 자세한 내용은 여기를 살펴보라.

기본적으로 모든 필드는 개별적으로 인덱싱되지만, 이 외에도 Elasticsearch는 모든 필드를 합쳐서 _all이라고 부르는 새로운 필드에 인덱싱한다. 이렇게 하면 좋은 점은 특정 조건으로 _all 필드에 대해 검색을 수행하면, 필드에 관계 없이 매칭되는 결과를 얻을 수 있다는 점이다. 단점은 인덱스를 만들 때 CPU를 추가적으로 사용하게 되며, 인덱스의 크기가 증가된다는 점이다. 따라서 _all 필드를 사용하지 않을 계획이라면, 비활성화하는 편이 낫다. 또는 일부 필드만 _all 필드에 추가되도록 정의할 수도 있다. 자세한 내용은 여기를 살펴보라.

리프레쉬 주기

Elasticsearch는 문서가 인덱스된 후, 해당 문서를 검색할 때 찾을 수 있으려면 인덱스를 리프레쉬해야한다는 점에서 준실시간 처리를 한다고 볼 수 있다. 기본적으로 인덱스는 매초마다 비동기적으로 자동으로 리프레쉬된다.

리프레쉬 작업은 상당히 자원소모가 많은 작업이므로, 리프레쉬 주기를 늘린다면 인덱스에 문서를 추가하는 속도를 늘릴 수 있게 된다. 리프레쉬 주기를 어느 정도로 설정하지는 사용자의 요구사항에 따라 다르다.

원하는 리프뤠시 주기는 인덱스 템플릿에 저장할 수 있다. 또는 elasticsearch.yaml 설정 파일에 설정하거나, 인덱스의 설정을 Rest API를 사용해도 설정할 수 있다.

또는 리프레쉬 주기를 -1로 설정하여 리프레쉬 자업을 해제하고, Rest API를 사용하여 리프레쉬 작업을 수동으로 할 수도 있다. 대량의 로그를 한꺼번에 추가하는 경우라면 이처럼 수작업으로 리프레쉬하는게 맞다. 하지만 일반적인 상황이라면 두가지 선택권이 있다. 벌크 작업으로 인덱스를 생성할 때마다 리프레쉬 작업을 하거나, 매번 검색을 하기 전에 리프레쉬를 할 수도 있다. 어느 방식을 선택하든, 각각의 작업을 처리하는데 시간이 더 걸리게 된다.

쓰리프트

일반적으로 Rest API는 HTTP 통신을 통해 이루어지지만 쓰리프트 통신을 사용할 수도 있으며, 쓰리프트를 사용하는 경우가 더 빠르다. 쓰리프트를 사용하려면 transport-thrift plugin을 설치해야 하며, 클라이언트에서도 쓰리프트 통신이 가능해야 한다. 예를 들어 클라이언트가 pyes Python client를 사용한다면, HTTP 통신시 기본포트로 사용하는 9200번 대신에 9500번으로 단순히 포트를 바꿔서 접속하게되면, 쓰리프트로 통신하게 된다.

비동기 복제

일반적으로 인덱스 처리는 모든 샤드에서 문서에 대한 인덱싱 작업이 끝나야만 리턴된다. 이 대신에 인덱스 API를 사용하여 replication를 async로 설정하면, 복제가 백그라운드에서 실행되게 된다. 인덱스 작업을 할 떄 API를 직접 호출하는 경우에만 이처럼 설정할 수 있다. 만약 기존에 만들어진 클라이언트(pyes나 rsyslog의 omelasticsearch)를 사용하는 경우라면, 해당 클라이언트에 이 옵션을 지원해야만 설정할 수 있다.

쿼리보다는 필터 사용하기

일반적으로 로그를 인덱싱할 때, 점수보다는 날짜를 기준으로 정렬하기를 원할 것이다. 이러한 경우에는 점수는 거의 의미가 없다. 따라서 조건에 맞는 로그를 찾고자 한다면 쿼리보다는 필터를 사용하는 편이 낫다. 필터의 경우 점수 계산을 실행하지 않을 뿐더러, 그 결과가 자동으로 캐싱되기 때문이다. 더 자세한 내용은 여기를 살펴보라.

벌크 인덱싱하기

인덱싱할 때 벌크 API를 사용하는 게 좋다. 각 로그를 매번 인덱싱하는 것에 비해 벌크로 인덱싱하는 것이 훨씬 빠르다. 이 때 아래 두가지 사항을 고려해야 한다.

  • 적당한 벌크의 크기 : 벌크할 로그의 크기는 설정에 따라 다르다. 만약 처음 시도해 보는 경우라면, pyes에서는 400을 기본값으로 사용한다.
  • 벌크에 대한 제한 시간을 설정 : 버프에 로그를 추가한 후, 벌크가 특정 크기를 넘어야만 인덱스에 추가되는 경우라면 벌크의 크기 뿐만 아니라 벌크 작업에 제한 시간을 설정해야 한다. 만약 제한 시간을 설정하지 않는다면, 로그가 천천히 입력되는 경우 버퍼에 로그가 추가되겠지만 Elasticsearch에 추가되려면 상당한 시간을 기다려야 하기 때문이다.