elasticsearch로 로그 검색 시스템 만들기 개발자Tip


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 개발에 관심을 가지고 업무에 활용하고 있으며, 여가시간에는 무지함에서 벗어나기위해 독서를 하거나 여행을 하려고 노력 중이다.


출처 - http://helloworld.naver.com/helloworld/273788







필요성

웹 사이트나 애플리케이션을 구축한 후 검색 기능을 추가하는데, 이때 이 작업이 쉽지만은 않다는 것을 깨닫게된다. 검색 기능을 추가할때 충족되었으면 하는 요구사항은 다음과 같다.

- 검색 솔루션이 빠르기를 원한다.

- 설치가 쉬어야 한다.

- 검색 스키마가 없어야 한다.

- HTTP를 통해 전달하는 데이터로 색인이 가능해야 한다.

- 검색 서버가 중단이 없기를 바란다.(가용성)

- 처음에는 한 대에서 출발하더라도 후에 수백대로 확장가능해야 한다.(확장성)

- 실시간 검색을 지원해야 한다.

- 멀티 태넌시(multi-tenancy)를 지원해야 한다.

- 클라우드 환경에 적합해야 한다.

이런 요구사항을 충족할 목적으로 elasticsearch가 만들어졌다. elasticsearch는 다음의 특성을 가진다.

- 오픈소스

- 분산 검색 시스템

- Restful 지원

- 아파치 루씬 기반

스키마가 없는 문서 기반 검색 엔진

일반적으로 검색 엔진의 데이타 모델은 스키마가 없는 문서 기반 데이터베이스에 기원을 둔다. nosql로의 움직임에서 알 수 있듯이, 이러한 모델은 애플리케이션을 구축하는데 매우 효과적임이 증명되었다. 

elasticsearch가 사용하는 모델은 JSON이다. JSON은 근래에 데이터를 표현하는 표준으로 부상하고 있다. JSON은 elasticsearch에서 다음과 같이 사용된다.

$ curl -XPUT http://localhost:9200/twitter/user/kimchy -d '{

    "name" : "Shay Banon"

}'


$ curl -XPUT http://localhost:9200/twitter/tweet/1 -d '{

    "user": "kimchy",

    "post_date": "2009-11-15T13:12:00",

    "message": "Trying out elasticsearch, so far so good?"

}'


$ curl -XPUT http://localhost:9200/twitter/tweet/2 -d '{

    "user": "kimchy",

    "post_date": "2009-11-15T14:12:12",

    "message": "You know, for Search"

}'


스키마가 없는 문서 기반 검색은 일반적인 검색 엔진의 특성이며, JSON 기반 다큐먼트는 elasticsearh의 특성이다. 내부적으로 JSON 형태로 저장되는 것은 아니며 이 포맷으로 데이터 전달이 이루어진다. 

스키마 매핑

elasticsearch는 데이터베이스에서 사용하는 강력한 개념의 스키마는 없다. 특정 유형의 JSON 다큐먼트를 elasticsearch에 전달하면 자동으로 인덱싱한다. 숫자나 날짜 같은 타입도 스키마를 미리 정의하지 않더라도 자동으로 파악되어 적절이 처리된다. 

검색 엔진은 내부적으로 꽤 복잡하다. 다큐먼트 필드는 스코링에 영향을 미치는 레벨 정보를 가지며, 어떤 필드가 분석(analyze)되어야 하는지가 정의되어야 한다. 또 토큰화를 위해 분석기(analyzer)가 선택되고 사용된다. 이밖에도 고려해야할 사항들이 많다. 입력 데이터인 JSON 다큐먼트는 위의 사항들이 고려되어 인덱싱되어야 하는데 이를 매핑이라고 한다. elasticsearch는 사용자에게 이 매핑에 대한 통제권을 인덱스와 타입별로 제공한다. 여기에 대한 자세한 설명은 매핑 부분을 참조한다.

데이터 가져오기(Get)

타입 레벨의 구분자(Unique Identifier)를 사용해서 데이터를 인덱싱한다. 데이터를 가져오는 방법은 매우 간단한데, 인덱스 이름과 타입 그리고 아이디만 있으면 된다. 이 세가지를 인자로 가져온 데이터는 원본 데이터를 인덱싱하는데 사용했던 JSON 다큐먼트이다. 실제 내부적으로 사용되는 분산 키/값 시스템은 외부에 노출되지 않는다.

$ curl -XPUT http://localhost:9200/twitter/tweet/2 -d '{ "user": "kimchy", "post_date": "2009-11-15T14:12:12", "message": "You know, for Search" }' $ curl -XGET http://localhost:9200/twitter/tweet/2

검색(Search)

내부적으로는 분산 검색 엔진이기에 복잡하지만, 외부로 드러나는 검색 사용법은 단순하다. 두 가지 사용법이 있는데 첫째는 루씬 기반의 쿼리 스트링이며, 둘째는 JSON 기반 검색 쿼리 DSL이다. 

검색은 단순히 이런 쿼리만 사용되는 것은 아니며, 패싯(facet)이나 하이라이팅(highlighting) 그리고 커스텀 스크립트등이 추가적으로 사용될 수 있다.

데이터를 인덱싱하고 검색해보자.

$ curl -XPUT http://localhost:9200/twitter/tweet/2 -d '{ "user": "kimchy", "post_date": "2009-11-15T14:12:12", "message": "You know, for Search" }' $ curl -XGET http://localhost:9200/twitter/tweet/_search?q=user:kimchy $ curl -XGET http://localhost:9200/twitter/tweet/_search -d '{ "query" : { "term" : { "user": "kimchy" } } }' $ curl -XGET http://localhost:9200/twitter/_search?pretty=true -d '{ "query" : { "range" : { "post_date" : { "from" : "2009-11-15T13:00:00", "to" : "2009-11-15T14:30:00" } } } }'

멀티 태넌시(Multi Tenancy)

내부적으로는 분산 검색 엔진이기에 복잡하지만, 외부로 드러나는 검색 사용법은 단순하다. 두 가지 사용법이 있는데 첫째는 루씬 기반의 쿼리 스트링이며, 둘째는 JSON 기반 검색 쿼리 DSL이다. 

하나의 시스템이 여러 개의 elasticsearch 인덱스를 사용하는 일이 발생할 수도 있다. 이 경우 한 번의 쿼리로 여러 개의 물리적 인덱스를 검색한다. 예를 들면 로그 파일들을 인덱싱하는데 한 주에 하나의 elasticsearch 인덱스를 생성하거나 혹은 심지어 다른 설정을 가진 다른 인덱스들을 한 번의 쿼리로 검색할 수 있다.

elasticsearch에서는 복수 개의 인덱스를 통합하여 검색이 가능하다. 다음은 멀티 태넌시에 대한 이해를 돕는 샘플 명령이다.

$ curl -XPUT http://localhost:9200/kimchy


$ curl -XPUT http://localhost:9200/elasticsearch


$ curl -XPUT http://localhost:9200/elasticsearch/tweet/1 -d '{

    "post_date": "2009-11-15T14:12:12",

    "message": "Zug Zug",

    "tag": "warcraft"

}'


$ curl -XPUT http://localhost:9200/kimchy/tweet/1 -d '{

    "post_date": "2009-11-15T14:12:12",

    "message": "Whatyouwant?",

    "tag": "warcraft"

}'


$ curl -XGET http://localhost:9200/kimchy,elasticsearch/tweet/_search?q=tag:warcraft


$ curl -XGET http://localhost:9200/_all/tweet/_search?q=tag:warcraft

설정

특별한 설정 작업이 없이도 시스템을 시작할 수 있어야 하며, 그러면서도 필요하다면 애플리케이션을 세세하게 통제할 수 있어야 한다. elasticsearch는 이런 개념하에 구현되었다. 대부분의 동작을 설정이나 플러그인에 의해 변경할 수 있다. 또한 전체 마스터 인덱스 설정을 가지면서도 이를 재정의할 수 있는 개별 인덱스 설정을 가질 수 있다. 예를 들면 한 인덱스는 메모리 저장공간을 사용하면서 1 복사본(replica), 10 샤드(shard)를 가지면서 다른 인덱스는 파일 저장공간을 사용하면서 1 샤드, 10 복사본을 가질 수 있다. 인덱스 생성 시 YAML이나 JSON 포맷을 사용해 인덱스 수준의 설정 정보를 통제할 수 있다. 다음은 elasticsearch라는 인덱스를 샤드 2와 복사본 3으로 설정한다.

$ curl -XPUT http://localhost:9200/elasticsearch/ -d '{ "settings" : { "number_of_shards" : 2, "number_of_replicas" : 3 } }'

분산

elasticsearch의 주요 특징 중에 하나는 분산 환경을 기반으로 한다는 점이다. 인덱스는 여러 개의 샤드로 분할되며 하나의 샤드는 복사본을 가질 수 있다. 클러스터 내의 개별 데이터 노드는 하나 이상의 샤드를 가지며, 올바른 샤드로 요청을 전달하는 코디네이터로서의 역할도 수행한다. 리밸런싱과 라우팅은 자동으로 이루어진다.

1. 2 샤드와 1복사본(2 shard, 1 replica)로 테스트 인덱스를 생성한다. 하나의 노드에 주 샤드와 복사본이 같이 있는 것은 의미가 없기 때문에 복사본은 생성되지 않는다.

2. 새로운 노드가 기동되면서 복사본이 생성이 된다.

3. 새로운 노드가 기동되면서 복사본이 새로운 노드로 리밸런싱된다.

4. 새로운 노드가 기동되면서 주 샤드가 새로운 노드로 리밸런싱된다.



출처 - http://elasticsearch.tistory.com/








'DB > Common' 카테고리의 다른 글

SQL - Session, Connection  (0) 2019.02.16
sql - sql by wiki  (0) 2015.12.14
search - Elasticsearch  (0) 2014.04.24
db - jdbc vs jpa  (0) 2013.09.14
db - ACID  (0) 2013.06.22
Sharding & Query Off Loading  (0) 2013.04.29
Posted by linuxism