Elasticsearch Build Case by NHN

Elastic/Elasticsearch 2013. 2. 1. 13:41

other tips

[How do i configure the routing]

        "routing" : {

            "required" : true,

            "path" : "indexType.user_uniq_id"

        }

[How do i configure the compression]

            "store" : {

                "compress" : {

                    "stored" : true,

                    "tv" : true

                }

            }

[How do i configure the customized analyzer]

            "analysis" : {

                "analyzer" : {

                    "kr_analyzer" : {

                        "type" : "custom",

                        "tokenizer" : "kr_tokenizer",

                        "filter" : ["trim", "kr_filter", "kr_synonym"]

                    },

                    "kr_analyzer" : {

                        "type" : "custom",

                        "tokenizer" : "kr_tokenizer",

                        "filter" : ["trim", "kr_filter", "kr_synonym"]

                    }

                },

                "filter" : {

                    "kr_synonym" : {

                        "type" : "synonym",

                        "synonyms_path" : "analysis/synonym.txt"

                    }

                }

            }

        }


[How do i disable the _all field]
    You can add some parameter.

    like this : {"include_in_all" : false}

[Fully Configurations]

curl -XPUT 'http://localhost:9200/index0' -d '{

    "settings" : {

        "number_of_shards" : 50,

        "number_of_replicas" : 1,

        "index" : {

            "refresh_interval" : "60s",

            "term_index_interval" : "1",

            "store" : {

                "compress" : {

                    "stored" : true,

                    "tv" : true

                }

            },

            "analysis" : {

                "analyzer" : {

                    "kr_analyzer" : {

                        "type" : "custom",

                        "tokenizer" : "kr_tokenizer",

                        "filter" : ["trim", "kr_filter", "kr_synonym"]

                    },

                    "kr_analyzer" : {

                        "type" : "custom",

                        "tokenizer" : "kr_tokenizer",

                        "filter" : ["trim", "kr_filter", "kr_synonym"]

                    }

                },

                "filter" : {

                    "kr_synonym" : {

                        "type" : "synonym",

                        "synonyms_path" : "analysis/synonym.txt"

                    }

                }

            }

        },

        "routing" : {

            "required" : true,

            "path" : "indexType.user_uniq_id"

        }

    },

    "mappings" : {

        "indexType" : {

            "properties" : {

                "docid" : { "type" : "string", "store" : "yes", "index" : "not_analyzed", "include_in_all" : false },

                "rm_seq" : { "type" : "long", "store" : "yes", "index" : "no", "include_in_all" : false },

                "rm_join_seq" : { "type" : "long", "store" : "yes", "index" : "no", "include_in_all" : false },

                "rm_title" : { "type" : "string", "store" : "yes", "index" : "analyzed", "term_vector" : "yes", "analyzer" : "kr_analyzer", "include_in_all" : false },

                "user_uniq_id" : { "type" : "string", "store" : "yes", "index" : "not_analyzed", "include_in_all" : false },

                "mb_nm" : { "type" : "string", "store" : "yes", "index" : "analyzed", "term_vector" : "yes", "analyzer" : "kr_analyzer", "include_in_all" : false },

                "mb_count" : { "type" : "integer", "store" : "yes", "index" : "no", "include_in_all" : false },

                "rm_ymdt" : { "type" : "date", "format" : "yyyyMMddHHmmss", "store" : "yes", "index" : "not_analyzed", "include_in_all" : false },

                "data_size" : { "type" : "long", "store" : "yes", "index" : "no", "include_in_all" : false },

                "msgs" : {

                    "properties" : {

                        "msg_seq" : { "type" : "long", "store" : "no", "index" : "no", "include_in_all" : false },

                        "msg" : { "type" : "string", "store" : "yes", "index" : "analyzed", "term_vector" : "yes", "analyzer" : "kr_analyzer", "include_in_all" : false },

                        "send_user_uniq_id" : { "type" : "string", "store" : "yes", "index" : "not_analyzed", "include_in_all" : false },

                        "send_user_nick_nm" : { "type" : "string", "store" : "yes", "index" : "not_analyzed", "term_vector" : "yes", "analyzer" : "kr_analyzer", "include_in_all" : false },

                        "recv_ymdt" : { "type" : "date", "format" : "yyyyMMddHHmmss", "store" : "yes", "index" : "not_analyzed", "include_in_all" : false },

                        "cfn_yn" : { "type" : "string", "store" : "no", "index" : "no", "include_in_all" : false },

                        "send_yn" : { "type" : "string", "store" : "yes", "index" : "not_analyzed", "include_in_all" : false },

                        "msg_type" : { "type" : "integer", "store" : "yes", "index" : "not_analyzed", "include_in_all" : false }

                    }

                }

            }

        }

    }

}'


Original article is 
        http://helloworld.naver.com/helloworld/273788


NHN Business Platform 글로벌플래폼개발랩 이재익

elasticsearch는 Shay Banon이 Lucene을 바탕으로 개발한 분산 검색엔진입니다. 설치와 서버 확장이 매우 편리하기 때문에 개발하고 있는 시스템에 검색 기능이 필요하다면 elasticsearch를 적용하는 것을 권장하고 싶습니다. 분산 시스템이기 때문에 검색 대상 용량이 증가했을 때 대응하기가 무척 수월하다는 것이 장점입니다.

이 글에서는 참고 자료의 내용을 바탕으로 기본적인 elasticsearch의 설치와 사용법을 설명하고, 실제 서비스에 적용할 때 고려해야 할 사항을 정리했습니다.

elasticsearch의 특징

우선 관계형 데이터베이스에 익숙한 사람들을 위해 관계형 데이터베이스와 elasticsearch의 용어를 비교한 표를 참고 자료에서 인용했다.

표 1 관계형 데이터베이스와 elasticsearch 용어 비교

관계형 데이터베이스

elasticsearch

Database

Index

Table

Type

Row

Document

Column

Field

Schema

Mapping

Index

Everything is indexed

SQL

Query DSL

JSON 기반의 스키마 없는 저장소

elasticsearch는 검색엔진이지만, NoSQL처럼 사용할 수 있다. 데이터 모델을 JSON으로 사용하고 있어서, 요청과 응답을 모두 JSON 문서로 주고받고 소스 저장도 JSON 형태로 저장한다. 스키마를 미리 정의하지 않아도, JSON 문서를 넘겨주면 자동으로 인덱싱한다. 숫자나 날짜 등의 타입은 자동으로 매핑한다.

Multi-tenancy

elasticsearch는 multit-enancy를 지원한다. 하나의 elasticsearch 서버에 여러 인덱스를 저장하고, 여러 인덱스의 데이터를 하나의 쿼리로 검색할 수 있다. <예제 1>의 경우 날짜별로 인덱스를 분리해 로그를 저장하고 있고, 검색 시에는 검색 범위에 있는 날짜의 인덱스를 하나의 쿼리로 요청하고 있다.

예제 1 Multi-tenency 예제 쿼리

# log-2012-12-26 인덱스에 로그 저장
curl -XPUT http://localhost:9200/log-2012-12-26/hadoop/1 -d '{
"projectName" : "hadoop",
"logType": "hadoop-log",
"logSource": "namenode",
"logTime":"2012-12-26T14:12:12",
"host": host1",
"body": "org.apache.hadoop.hdfs.StateChange: DIR* NameSystem.completeFile"
}'

# log-2012-12-27 인덱스에 로그 저장
curl -XPUT http://localhost:9200/log-2012-12-27/hadoop/1 -d '{
"projectName" : "hadoop",
"logType": "hadoop-log",
"logSource": "namenode",
"logTime":"2012-12-27T02:02:02",
"host": "host2",
"body": "org.apache.hadoop.hdfs.server.namenode.FSNamesystem"
}'

# log-2012-12-26, log-2012-12-27 인덱스에 한번에 검색 요청
curl -XGET http://localhost:9200/ log-2012-12-26, log-2012-12-27/_search

확장성과 유연성

elasticsearch는 확장성과 유연성이 매우 뛰어나다. 플러그인을 이용해 기능을 확장할 수 있다. 예를 들어 Thrift 플러그인이나 Jetty 플러그인을 사용하면 전송 프로토콜을 변경할 수 있다. 필수 플러그인이라고 할 수 있는 BigDesk나 Head를 설치하면 elasticsearh 모니터링 기능을 사용할 수 있게 된다. <예제 2>에서 보는 것처럼 동적으로 복제본 개수를 조정할 수도 있다. 다만 샤드 수는 인덱스별로 고정돼 있어 수정이 불가능하므로 노드 수와 향후 서버 확장을 고려해 초기에 적절한 수를 할당해야 한다.

예제 2 설정 변경 쿼리

$ curl -XPUT http://localhost:9200/log-2012-12-27/ -d '{
"settings" : {
"number_of_shards" : 10,
"number_of_replicas" : 1
}
}'

분산 저장소

elasticsearch는 분산 검색엔진이다. 키에 따라 여러 샤드가 구성되는 방식으로 데이터를 분산한다. 인덱스는 각각의 샤드마다 구성된다. 각각의 샤드는 0개 이상의 복제본을 가진다. elasticsearch는 클러스터링을 지원하며 클러스터가 가동될 때 여러 노드 중 하나가 메타데이터 관리를 위한 마스터 노드로 선출된다. 마스터 노드가 다운되면 자동으로 클러스터 내의 다른 노드가 마스터가 된다. 노드 추가 또한 매우 간단하다. 같은 네트워크에 노드를 추가하는 경우 추가된 노드가 멀티캐스트를 이용해 자동으로 클러스터를 찾아 자신을 추가한다. 같은 네트워크를 이용하지 않을 경우 유니캐스트로 마스터 노드의 주소를 명시해 주어야 한다(참고 영상:http://youtu.be/l4ReamjCxHo).

설치하기

Quick Start

elasticsearch는 무설정 설치가 가능하다. <예제 3>에서 볼 수 있는 것 처럼 공식 홈페이지에서 파일을 내려 받아 압축을 해제하기만 하면 바로 실행해 볼 수 있다.

  1. 다운로드

$ wget http://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.20.4.tar.gz
$ tar xvzf elasticsearch-0.20.4.tar.gz

  1. 서버 실행

예제 3 설치 및 실행 명령

$ bin/elasticsearch -f

플러그인 설치

elasticsearch는 플러그인을 통해 쉽게 기능을 확장할 수 있다. 관리 기능을 추가하거나 Lucene의 Analyzer를 변경하고 기본 전송 모듈을 Netty에서 Jetty로 변경하는 것도 가능하다. <예제 4>는 플러그인을 설치하기 위한 명령어다. <예제 4>의 첫 번째와 두 번째 줄에서 보이는 'head'와 'bigdesk'는 elasticsearch 모니터링을 위한 필수 플러그인이므로 꼭 설치해서 기능을 확인해 보도록 하자. 설치 후 http://localhost:9200/plugin/head/http://localhost:9200/plugin/bigdesk/로 접속하면 웹 브라우저를 이용해 상태를 확인해 볼 수 있다.

예제 4 플러그인 설치

bin/plugin -install Aconex/elasticsearch -head
bin/plugin -install lukas-vlcek/bigdesk
bin/plugin -install elasticsearch/elasticsearch-transport-thrift/1.4.0
bin/plugin -url https://oss-es-plugins.s3.amazonaws.com/elasticsearch-jetty/elasticsearch-jetty-0.20.1.zip -install elasticsearch-jetty-0.20.1

주요 설정

간단한 기능 테스트에는 설정 변경이 필요 없으나, 성능 테스트를 하거나 실서비스에 적용할 때에는 기본 설정에 대한 몇 가지 변경이 필요하다. <예제 5>를 참고하면 초기 설정 파일(elasticsearch.yml)에서 변경해야 할 설정 내용이 무엇인지 알 수 있다.

예제 5 주요 설정(config/elasticsearch.yml)

# 클러스터를 식별하기 위한 이름이므로 유일성과 의미를 가진 이름을 사용하자
cluster.name: es-cluster

# 노드 이름은 자동으로 생성되지만 호스트명과 같이 클러스터 내에서 식별 가능한 이름을 활용하는 것이 좋다.
node.name: "es-node1"

# 기본값은 아래 두 값이 모두 true다. node.master는 노드가 마스터가 될 수 있지에 대한 설정이고, node.data는 데이터를 저장하는 노드인지에 대한 설정이다. 보통은 두 값을 true로 설정하면 되고, 클러스터의 규모가 큰 경우에는 3가지 종류의 노드를 구성하기 위해 이 값을 노드별로 조정해 설정한다. 자세한 사항은 토폴로지(topology) 설정에서 다시 설명하겠다.
node.master: true
node.data: true

# 샤드와 리플리카 수를 변경하는 설정이다. 아래 값은 기본값이다.
index.number_of_shards: 5
index.number_of_replicas: 1



#JVM의 스왑을 방지하려면 아래 설정 값을 true로 한다.
bootstrap.mlockall: true

# 클러스터 내의 각 노드의 상태 체크를 위한 타임아웃 값으로, 너무 작게 하면 노드가 클러스터에서 자주 이탈하는 문제가 발생할 수 있어 적절한 값을 설정한다. 기본값은 3초다.
discovery.zen.ping.timeout: 10s

# 기본값은 멀티캐스트를 사용하지만, 실환경에서는 다른 클러스터와 노드가 섞이는 위험이 발생할 수 있으므로 유니캐스트를 사용하고 두 번째 설정 값에 마스터가 될 수 있는 서버들의 목록을 나열하는 것이 좋다.
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: ["host1", "host2:port", "host3[portX-portY]"]

REST API 사용하기

elasticsearch는 <예제 6>과 같은 형식의 REST API를 제공한다. 인덱스 생성과 삭제, 매핑 생성과 삭제, 검색, 설정 변경 등 대부분의 기능을 REST API를 통해 제공한다. REST API 이외에도 Java, Python, Ruby 등의 언어별 클라이언트도 제공하고 있다.

예제 6 REST API 형식

http://host:port/(index)/(type)/(action|id)

<예제 7>의 경우 날짜별로 인덱스를 분리하고, 프로젝트별로 타입을 나누어 관리하고 있다. 2012년 12월 27일에 hadoop이라는 프로젝트로 들어온 로그를 문서 단위로 생성하는 과정을 REST API를 사용해 수행하는 예다.

예제 7 REST API 사용 예

#문서 생성
curl -XPUT http://localhost:9200/log-2012-12-27/hadoop/1
curl -XGET http://localhost:9200/log-2012-12-27/hadoop/1
curl -XDELETE http://localhost:9200/log-2012-12-27/hadoop/1

#검색
curl -XGET http://localhost:9200/log-2012-12-27/hadoop/_search
curl -XGET http://localhost:9200/log-2012-12-27/_search
curl -XGET http://localhost:9200/_search

#인덱스 상태 보기
curl -XGET http://localhost:9200/log-2012-12-27/_status

문서와 인덱스 생성

<예제 8>에서처럼 요청을 보내면 인덱스와 타입이 정의돼 있지 않더라도 elasticsearch는 자동으로 log-2012-12-27 인덱스와 hadoop 타입을 생성한다. 자동으로 생성하지 않고 명시적으로 생성하려면 설정 파일에서 action.auto_create_index와 index.mapper.dynamic의 설정 값을 false로 명시해야 한다.

예제 8 문서 생성

# 요청
curl -XPUT http://localhost:9200/log-2012-12-27/hadoop/1 -d '{
"projectName" : "hadoop",
"logType": "hadoop-log",
"logSource": "namenode",
"logTime":"2012-12-27T02:02:02",
"host": "host2 ",
"body": "org.apache.hadoop.hdfs.server.namenode.FSNamesystem"
}'

# 결과
{
"ok" : true,
"_index" : "log-2012-12-27",
"_type" : "hadoop",
"_id" : "1",
"_version" : 1
}

<예제 9>에서 보는 것처럼 타입을 문서에 포함해 요청할 수 있다.

예제 9 타입을 포함한 쿼리

curl -XPUT http://localhost:9200/log-2012-12-27/hadoop/1 -d '{
"hadoop" : {
"projectName" : "hadoop",
"logType": "hadoop-log",
"logSource": "namenode",
"logTime":"2012-12-27T02:02:02",
"host": "host2 ",
"body": "org.apache.hadoop.hdfs.server.namenode.FSNamesystem"
}
}'

<예제 10>과 같이 ID 값을 생략하면 자동으로 ID를 생성하고 문서를 만든다. 요청 시 PUT 대신 POST 방식을 사용한 것에 주의하자.

예제 10 ID 없는 문서 생성 쿼리

# 요청
curl -XPOST http://localhost:9200/log-2012-12-27/hadoop/ -d '{
"projectName" : "hadoop",
"logType": "hadoop-log",
"logSource": "namenode",
"logTime":"2012-12-27T02:02:02",
"host": "host2 ",
"body": "org.apache.hadoop.hdfs.server.namenode.FSNamesystem"
}'

# 결과
{
"ok" : true,
"_index" : "log-2012-12-27",
"_type" : "hadoop",
"_id" : "kgfrarduRk2bKhzrtR-zhQ",
"_version" : 1
}

문서 삭제

<예제 11>은 문서를 삭제하는 방법을 보여 주고 있다. DELETE 메서드를 사용해 log-2012-12-27 인덱스에서 타입이 hadoop이고 ID가 1인 문서를 삭제한다.

예제 11 문서 삭제 쿼리

# 요청
$ curl -XDELETE 'http://localhost:9200/log-2012-12-27/hadoop/1'

# 결과
{
"ok" : true,
"_index" : "log-2012-12-27",
"_type" : "hadoop",
"_id" : "1",
"found" : true
}

문서 가져오기

<예제 12>와 같이 GET 메서드를 사용하면 log-2012-12-27 인덱스에서 타입이 hadoop이고 ID가 1인 문서를 가져올 수 있다.

예제 12 문서를 가져오기 위한 쿼리

#요청
curl -XGET 'http://localhost:9200/log-2012-12-27/hadoop/1'

# 결과
{
"_index" : "log-2012-12-27",
"_type" : "hadoop",
"_id" : "1",
"_source" : {
"projectName" : "hadoop",
"logType": "hadoop-log",
"logSource": "namenode",
"logTime":"2012-12-27T02:02:02",
"host": "host2 ",
"body": "org.apache.hadoop.hdfs.server.namenode.FSNamesystem"
}
}

검색

검색 API를 호출하면 elasticsearch는 검색 API를 실행한 후 질의 내용과 일치하는 검색 결과를 반환한다. <예제 13>에서 검색 API를 사용하는 예제를 볼 수 있다.

예제 13 검색 API 사용 예제 쿼리

# 특정 인덱스의 모든 타입
$ curl -XGET 'http://localhost:9200/log-2012-12-27/_search?q=host:host2'

# 특정 인덱스의 특정 타입
$ curl -XGET 'http://localhost:9200/log-2012-12-27/hadoop,apache/_search?q=host:host2'

# 모든 인덱스의 특정 타입
$ $ curl - XGET 'http://localhost:9200/_all/hadoop/_search?q=host:host2'

# 모든 인덱스와 타입
$ curl -XGET 'http://localhost:9200/_search?q=host:host2'

URI 요청을 사용한 검색 API

URI를 사용하면 <표 2>의 파라미터와 쿼리 스트링(Query String)으로 간단하게 검색할 수 있다. 모든 검색 옵션을 제공하지는 않으므로 주로 테스트 용도로 간편하게 사용할 때 유용하다.

표 2 주요 파라미터들

이름

설명

q

쿼리 스트링

default_operator

기본으로 사용할 연산자(AND 혹은 OR). 기본값은 OR.

fields

결과로 가져올 필드. 기본값은 '_source' 필드.

sort

정렬 방법(예: fieldName:asc/fieldName:desc)

timeout

검색 수행 타임아웃 값. 기본값은 무제한.

size

결과 값의 개수. 기본값은 10.

예제 14 URI 요청을 사용한 검색 쿼리

# 요청
$ curl -XGET 'http://localhost:9200/log-2012-12-27/hadoop/_search?q=host:host2'

# 결과
{
"_shards":{
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits":{
"total" : 1,
"hits" : [
{
"_index" : "log-2012-12-27",
"_type" : "hadoop",
"_id" : "1",
"_source" : {
"projectName" : "hadoop",
"logType": "hadoop-log",
"logSource": "namenode",
"logTime":"2012-12-27T02:02:02",
"host": "host2",
"body": "org.apache.hadoop.hdfs.server.namenode.FSNamesystem"
}
}
]
}
}

요청 바디(Request Body)를 사용한 검색 API

HTTP 바디를 사용할 경우 Query DSL을 사용해서 검색한다. Query DSL은 내용이 방대하므로 공식 사이트의 가이드 문서(http://www.elasticsearch.org/guide/reference/query-dsl/)를 참고하도록 하자.

예제 15 Query DSL을 사용한 검색

# 요청
$ curl -XPOST 'http://localhost:9200/log-2012-12-27/hadoop/_search' -d '{
"query" : {
"term" : { "host" : "host2" }
}
}
'

# 결과
{
"_shards":{
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits":{
"total" : 1,
"hits" : [
{
"_index" : "log-2012-12-27",
"_type" : "hadoop",
"_id" : "1",
"_source" : {
"projectName" : "hadoop",
"logType": "hadoop-log",
"logSource": "namenode",
"logTime":"2012-12-27T02:02:02",
"host": "host2",
"body": "org.apache.hadoop.hdfs.server.namenode.FSNamesystem"
}
}
]
}
}

Mapping

Put Mapping API

특정 타입에 매핑을 추가하려면 <예제 16>과 같은 형태로 정의할 수 있다.

예제 16 매핑을 등록하기 위한 쿼리

$ curl -XPUT 'http://localhost:9200/log-2012-12-27/hadoop/_mapping' -d '
{
"hadoop" : {
"properties" : {
"projectName" : {"type" : "string", "index" : "not_analyzed"},
"logType" : {"type" : "string", "index" : "not_analyzed"},
"logSource" : {"type" : "string", "index" : "not_analyzed"},
"logTime" : {"type" : "date"},
"host" : {"type" : "string", "index" : "not_analyzed"},
"body" : {"type" : "string"},
}
}
}
'

Get Mapping API

정의한 매핑 정보를 얻기 위해서 <예제 17>과 같은 형태의 쿼리를 사용할 수 있다.

예제 17 매핑을 얻어오기 위한 쿼리

$ curl -XGET 'http://localhost:9200/log-2012-12-27/hadoop/_mapping'

Delete Mapping API

<예제 18>은 정의한 매핑을 삭제하는 예다.

예제 18 매핑을 삭제하기 위한 쿼리

$ curl -XDELETE 'http://localhost:9200/log-2012-12-27/hadoop'

성능 최적화 팁

메모리와 오픈 파일 수

검색할 데이터가 많아질수록 많은 메모리가 필요하다. elasticsearch를 운영하다 보면 메모리 사용으로 인한 문제를 많이 겪게 된다. elasticsearch 커뮤니티에서 권장하는 운영 방법에 따르면, elasticsearch 전용 서버를 운영할 때는 메모리 용량의 절반만 elasticsearch에 할당하고, 나머지 메모리 용량은 운영체제가 시스템 캐시 목적으로 사용할 수 있도록 하는 것이 좋다. 메모리 크기는 ES_HEAP_SIZE 환경 변수를 설정하거나 JVM의 -Xms와 -Xmx 값을 사용해서 설정할 수 있다.

예제 19 힙 크기를 지정해 실행

bin/ElasticSearch -Xmx=2G -Xms=2G

elasticsearch를 사용할 때는 OOME(Out Of Memory Error)가 발생하는 경우가 많다. 필드 캐시가 최대 힙 크기를 넘어서면서 발생하는데, index.cache.field.type 설정을 기본값인 resident 대신 soft로 설정하면 soft 레퍼런스를 활용하므로 캐시 영역에 대해 우선 가비지 컬렉션(Garbage Collection)을 실행해 문제를 해결할 수 있다.

예제 20 필드 캐시 타입 설정

index.cache.field.type: soft

데이터가 많아지면 인덱스 파일 개수 또한 증가하게 된다. elasticsearch가 사용하고 있는 Lucene에서 인덱스를 세그먼트 단위로 관리하기 때문이다. 경우에 따라 MAX_OPEN 파일 개수를 넘어서는 일도 발생한다. ulimit 명령으로 최대 오픈 파일 제한을 변경해야 한다. 권장되는 값은 32000~64000이지만, 시스템 규모나 데이터의 양에 따라 더 큰 값으로 설정해야 할 수도 있다.

인덱스 최적화

날짜별로 인덱스를 관리하면 <예제 21>에서 보는 것처럼 관리가 필요 없는 오래된 로그를 쉽고 빠르게 삭제할 수 있어서, 문서별로 TTL 값을 설정해 삭제하는 것 보다 시스템에 주는 오버헤드가 적다.

예제 21 인덱스 삭제

$ curl -XDELETE 'http://localhost:9200/log-2012-10-01/'

인덱스 최적화(Index Optimization)를 수행하면 세그먼트를 병합시킨다. 이러한 방식으로 검색 성능을 향상시킬 수 있다. 다만 시스템에 부담을 주므로 시스템 사용이 적은 시간대에 작업하도록 해야 한다.

예제 22 인덱스 최적화

$ curl -XPOST 'http://localhost:9200/log-2012-10-01/_optimize'

샤드와 복제본

샤드 개수는 초기에 설정한 다음에는 변경이 불가능하므로 현재 시스템의 노드 수와 추후 발생할 수 있는 노드 증가를 고려해 정해야 한다. 예를 들어 현재 5개의 노드가 있고 향후 10개까지 노드를 증가시킬 계획이라면, 초기부터 샤드 수를 10개로 지정하는 것이 좋다. 초기에 5개로 지정하면 이후 노드를 10개로 증가시켜도 샤드는 5개이므로 5개의 노드는 활용할 수 없게 된다. 물론 복제본 수를 1로 지정했다면 추가한 5개 노드를 복제 전용 노드로 활용할 수 있다.

샤드 수를 늘리면 질의가 샤드 개수만큼 분산되므로 많은 데이터 처리에 유리하게 되지만, 너무 크게 설정하면 오히려 트래픽이 증가해 성능이 떨어질 수 있으니 적절하게 설정해야 한다.

클러스터 토폴로지 구성

elasticsearch의 설정 파일에는 <예제 23>과 같은 내용을 볼 수 있다. <예제 23>을 보면 세 가지 종류의 노드(데이터 노드, 마스터 노드, 검색 밸런서 노드)가 있음을 알 수 있다.

  • 데이터 노드: 마스터 역할을 수행하지 않고, 데이터만 저장한다. 클라이언트로부터의 요청이 왔을 때 샤드에서 데이터를 검색하거나 인덱스를 생성한다.
  • 마스터 노드: 클러스터를 유지하기 위한 역할을 하고 인덱싱이나 검색 요청을 데이터 노드들에 요청한다.
  • 검색 로드 밸런서 노드: 검색 요청이 오면 노드들에 데이터를 요청 후 취합해 결과를 전달한다.

하나의 노드가 마스터와 데이터 노드 역할을 다 하도록 운영할 수도 있으나, 세 가지 형태의 노드를 각각 사용하면 데이터 노드의 부담을 줄일 수 있다. 또한 마스터 노드를 별도로 구성하면 클러스터의 안정성을 높일 수 있다. 게다가 마스터 노드와 검색 노드는 저사양의 서버 장비를 활용할 수 있도록 해 운영 비용 또한 줄일 수 있다.

예제 23 토폴로지 관련 설정 내용

# You can exploit these settings to design advanced cluster topologies.
#
# 1. You want this node to never become a master node, only to hold data.
# This will be the "workhorse" of your cluster.
#
# node.master: false
# node.data: true
#
# 2. You want this node to only serve as a master: to not store any data and
# to have free resources. This will be the "coordinator" of your cluster.
#
# node.master: true
# node.data: false
#
# 3. You want this node to be neither master nor data node, but
# to act as a "search load balancer" (fetching data from nodes,
# aggregating results, etc.)
#
# node.master: false
# node.data: false

라우팅 설정

인덱싱할 데이터가 많으면 샤드의 수를 늘리는 것이 전체적인 성능을 증가시킬 수 있지만, 샤드의 개수를 증가시키는 만큼 노드 간의 트래픽이 증가한다는 문제점이 있다. 예를 들어 샤드가 100개면 하나의 검색 요청이 왔을 때 100개의 샤드에 모두 요청을 보내 데이터를 취합하는 형식이기 때문에 전체 클러스터에 부담이 된다. 라우팅을 사용하면 특정 샤드에만 데이터가 저장되어 샤드 수가 아무리 커져도 1개의 샤드에만 요청을 보내게 되므로 트래픽을 극적으로 줄일 수 있다. <그림 2>와 <그림 3>, <그림 4>는 Rafal Kuc이 Berlin Buzzwords 2012에서 발표한 자료에서 인용한 것이다. 라우팅을 사용하지 않으면 <그림 2>와 같이 전체 샤드에 모두 요청을 보내지만 라우팅을 사용하면 <그림 3>과 같이 특정 샤드에만 요청을 보내는 것을 볼 수 있다. <그림 4>에서 인용한 자료에 따르면 200개의 샤드에서 라우팅 사용 전과 후의 성능을 비교 했을 때 반응 시간이 10배 이상 차이 나는 것을 볼 수 있다. 라우팅을 적용하면 적용하지 않은 경우외 비교해 스레드 개수가 10~20배 증가하지만 CPU 사용률은 훨씬 적은 것을 볼 수 있다. 하지만 경우에 따라 라우팅을 적용하지 않은 것이 성능이 더 좋을 때도 있다. 여러 샤드로부터 결과가 취합되어야 하는 검색 질의에 대해서는 여러 샤드에 요청이 전달되는 것이 성능상 유리할 수 있다.

c0209f80b05fe14bd157cbace9270378.png

그림 2 라우팅 사용 전

b4a8e465ca9e0c609eff1677e8a0e690.png

그림 3 라우팅 사용 후

8aafb0f6569fda4b8c31e98cad9123f2.png

그림 4 라우팅 사용 전/후의 성능 비교

마무리

elasticsearch는 유용한 기능뿐만 아니라 간단한 설치와 높은 확장성때문에 사용자를 빠르게 늘려가고 있다. 버전 숫자로만 본다면 최근(2013년 1월 기준)에 0.20.4 버전이 나온 정도지만 커뮤니티가 활발하기 때문에 빠르게 기능이 개선되고 있다. 또한 점점 더 많은 회사에서 elasticsearch를 자사의 서비스에 사용하고 있다. 최근에는 개발자인 Shay Banon을 포함한 커미터들이 모여 Elasticsearch.com을 만들어 컨설팅과 교육을 제공하고 있다. 이번 글에서는 기본적인 설치와 사용 방법, 성능 튜닝을 위한 내용을 정리했다. 많은 개발자들에게 도움이 되길 바란다.

참고 자료

31a9898c16d64bb9fd3a2a0043aeb1d3.JPG
NBP 글로벌플래폼개발랩 이재익
글로벌플랫폼개발랩에서 중국개발자들과 로그시스템을 개발하고 있다. 최근 elasticsearch, node.js, iOS 개발에 관심을 가지고 업무에 활용하고 있으며, 여가시간에는 무지함에서 벗어나기위해 독서를 하거나 여행을 하려고 노력 중이다.



: