ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Elasticsearch 설치 및 사용법
    LogSystem 2016. 7. 14. 11:12

    Elastic Search

    설치

    $ docker pull elasticsearch # 이미지 다운로드
    $ docker run -d -p 9200:9200 --name elastic elasticsearch # 기본세팅으로 바로 실행
    $ docker run -d -p 9200:9200 --name elastic elasticsearch elasticsearch -Des.node.name="TestNode" # 옆과 같이 flag를 달 수 있음
    $ docker run -d -p 9200:9200 --name elastic -v "$PWD/config":/usr/share/elasticsearch/config elasticsearch # 수정한 config파일을 적용하여 컨테이너를 실행할 때
    $ docker run -d -p 9200:9200 --name elastic -v "$PWD/esdata":/usr/share/elasticsearch/data elasticsearch # 컨테이너 내 인덱스가 저장되는 폴더를 공유합니다.

    추가설치

    elasticsearch에서 'head'와 'bigdesk'를 plugin으로 설치할 시, 모니터링을 할 수 있으므로 필수 플러그인 입니다.

    $ docker exec -it elastic /bin/bash
    $ plugin --help
    NAME
        plugin - Manages plugins
    SYNOPSIS
        plugin <command>
    DESCRIPTION
        Manage plugins
    COMMANDS
        install    Install a plugin
        remove     Remove a plugin
        list       List installed plugins
    NOTES
        [*] For usage help on specific commands please type "plugin <command> -h"
    # 다음과 같은 설명을 확인 할 수 있음
     
    $ plugin install mobz/elasticsearch-head

    위와 같이 설치가 전부 성공했으면, http://ip주소/_plugin/head/로 확인이 가능합니다.

    config 설정

    간단한 기능 테스트에는 설정 변경이 필요 없으나, 성능 테스트를 하거나 실서비스에 적용할 때에는 기본 설정에 대한 몇 가지 변경이 필요합니다.

    # 클러스터를 식별하기 위한 이름이므로 유일성과 의미를 가진 이름을 사용
    cluster.name: es-cluster
    
    # 노드 이름은 자동으로 생성되지만 호스트명과 같이 클러스터 내에서 식별 가능한 이름을 활용하는 것을 권장
    node.name: "es-node1"
    
    # 기본값은 아래 두 값이 모두 true. node.master는 노드가 마스터가 될 수 있지에 대한 설정이고, node.data는 데이터를 저장하는 노드인지에 대한 설정. 보통은 두 값을 true로 설정하면 되고, 클러스터의 규모가 큰 경우에는 3가지 종류의 노드를 구성하기 위해 이 값을 노드별로 조정해 설정합니다.
    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]"]

    Clustering 하기

    elasticsearch는 각 노드끼리 동일한 네트워크 상에 존재하면 자동으로 찾아서 클러스터링을 맺는다라고 합니다. 그래서 서버 1대에 docker로 아무런 설정없이 3개의 node를 실행했지만 각기 다른 클러스터로 묶여서 다음과 같이 각 설정을 주었습니다. 노드의 갯수는 3개로 마스터노드 1개 데이터노드 2개로 구성해봤습니다.

    master node 

    cluster.name: jh-cluster # 같은 클러스터끼리 묶이고자 하면 이름이 동일해야함
    node.name: "jh-node1"
    node.master: true #해당 노드를 마스터로 임명
    node.data: false
    index.number_of_shards: 5 # shard는 default로 지정 
    index.number_of_replicas: 1
    network.host: 0.0.0.0
    transport.tcp.port: 9300
    transport.tcp.compress: true
    http.port: 9200
    http.enabled: true
    discovery.zen.minimum_master_nodes: 1
    discovery.zen.ping.timeout: 10s
    discovery.zen.ping.multicast.enabled: false
    discovery.zen.ping.unicast.hosts: ["ip주소:9301", "ip주소:9302"] # 클러스터링할 node의 정보

    위와 같이 elasticsearch.yml을 설정한 다음, 아래와 같이 실행합니다.

    $ docker run -d -p 9202:9200 -p 9302:9300 --name ela -v $PWD/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml elasticsearch

    data node

    cluster.name: jh-cluster
    node.name: "jh-node2"
    index.number_of_shards: 5
    index.number_of_replicas: 1
    network.host: 0.0.0.0
    transport.tcp.port: 9301
    transport.tcp.compress: true
    http.port: 9201
    http.enabled: true
    discovery.zen.minimum_master_nodes: 1
    discovery.zen.ping.timeout: 10s
    discovery.zen.ping.multicast.enabled: false
    discovery.zen.ping.unicast.hosts: ["ip주소:9300","ip주소:9302"]

    master node 구성과 똑같이 port와 node name, discovery.zen.ping.unicast.hosts를 맞게 변경합니다.

    다음 아래와 같이 컨테이너를 실행합니다.

    $ docker run -d -p 9202:9200 -p 9302:9300 --name ela -v $PWD/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml elasticsearch

    확인

    아래의 명령어로 클러스터링이 되었는지 확인을 할 수 있습니다.

    $ curl -XGET http://localhost:9200/_cluster/health?pretty=true
    {
      "cluster_name" : "jh-cluster",
      "status" : "green",
      "timed_out" : false,
      "number_of_nodes" : 3,
      "number_of_data_nodes" : 2,
      "active_primary_shards" : 0,
      "active_shards" : 0,
      "relocating_shards" : 0,
      "initializing_shards" : 0,
      "unassigned_shards" : 0,
      "delayed_unassigned_shards" : 0,
      "number_of_pending_tasks" : 0,
      "number_of_in_flight_fetch" : 0,
      "task_max_waiting_in_queue_millis" : 0,
      "active_shards_percent_as_number" : 100.0
    }

    노드들의 클러스터링이 약간 느리게 진행될 수도 있습니다.

    또한 master node의 컨테이너에서 mobz/elasticsearch-head를 설치해 웹페이지에서 확인할 수 있습니다.


    REST API 사용

    elasticsearch는 REST API를 제공합니다.

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

    다음은 날짜별로 인덱스를 분리하고, 프로젝트 별로 타입을 나누어 관리하고 있다. 2016년 07월 11일에 hadoop이라는 프로젝트로 들어온 로그를 문서 단위로 생성하는 과정을 REST API를 사용해 수행하는 예입니다.

    #문서 생성
    curl -XPUT http://localhost:9200/log-2016-07-11/hadoop/1  
    curl -XGET http://localhost:9200/log-2012-0712-27/hadoop/1  
    curl -XDELETE http://localhost:9200/log-2016-07-11/hadoop/1
    #검색
    curl -XGET http://localhost:9200/log-2016-07-11/hadoop/_search  
    curl -XGET http://localhost:9200/log-2016-07-11/_search  
    curl -XGET http://localhost:9200/_search
    #인덱스 상태 보기
    curl -XGET http://localhost:9200/log-2016-07-11/_status

    문서와 인덱스 생성

    아래와 같이 요청을 보내면 인덱스와 타입이 정의돼 있지 않더라도 elasticsearch는 자동으로 log-2016-07-11 인덱스와 hadoop 타입을 생성합니다. 자동으로 생성하지 않고 명시적으로 생성하려면 설정 파일에서 action.auto_create_index와 index.mapper.dynamic의 설정 값을 false로 명시해야 합니다.

    # 요청
    curl -XPUT http://localhost:9200/log-2016-07-11/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-2016-07-11",
        "_type" : "hadoop",
        "_id" : "1",
        "_version" : 1
    }
     
    # 다음과 같이 타입을 문서에 포함할 수 있음
    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"
        }
    }'
     
    #요청 (POST)에서 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
    }
     
    # 문서를 삭제할 때
    # 요청
    $ curl -XDELETE 'http://localhost:9200/log-2012-12-27/hadoop/1'
    
    # 결과
    {
        "ok" : true,
        "_index" : "log-2012-12-27",
        "_type" : "hadoop",
        "_id" : "1",
        "found" : true
    }

    문서 가져오기

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

    #요청
    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"
        }
    }

    검색

    # 특정 인덱스의 모든 타입
    $ 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'

    Mapping

    ## 특정 타입에 매핑을 추가
    $ 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"},
            }
        }
    }
    ' 
     
    ## 정의된 매핑 정보 얻기
    $ curl -XGET 'http://localhost:9200/log-2012-12-27/hadoop/_mapping'
    
    ## 정의된 매핑 삭제
    $ curl -XDELETE 'http://localhost:9200/log-2012-12-27/hadoop'

    성능 최적화

    메모리와 오픈 파일 수

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

    bin/ElasticSearch -Xmx=2G -Xms=2G # 힙크기를 수동으로 지정


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

    index.cache.field.type: soft  # 필드 캐시 타입 설정


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

    인덱스 최적화

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

    $ curl -XDELETE 'http://localhost:9200/log-2012-10-01/' # 인덱스 삭제


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

    $ curl -XPOST 'http://localhost:9200/log-2012-10-01/_optimize' # 인덱스 병합

    샤드와 복제본

    샤드 개수는 초기에 설정한 다음에는 변경이 불가능하므로 현재 시스템의 노드 수와 추후 발생할 수 있는 노드 증가를 고려해 정해야 합니다. 예를 들어 현재 5개의 노드가 향후 10개까지 증가할 계획이라면, 초기부터 샤드 수를 10개로 지정하는 것이 좋습니다. 초기에 샤드를 5개로 지정하면 이후 노드를 10개로 증가시켜도 샤드는 5개이므로 5개의 노드는 활용할 수 없게 됩니다. 물론 복제본 수를 1로 지정했다면 추가한 5개 노드를 복제 전용 노드로 활용할 수는 있습니다. 샤드 수를 늘리면 질의가 샤드 개수만큼 분산되므로 많은 데이터 처리에 유리하게 되지만, 너무 크게 설정하면 오히려 트래픽이 증가해 성능이 떨어질 수 있으니 적절하게 설정해야 합니다.

    클러스터 토폴로지 구성

    elasticsearch의 설정 파일에는 세 가지 종류의 노드(데이터 노드, 마스터 노드, 검색 밸런서 노드)가 있습니다.

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

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

    # 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개의 샤드(데이터가 저장이 되어 있는 샤드)에만 요청을 보내게 되므로 트래픽을 극적으로 줄일 수 있습니다. 라우팅을 사용하지 않으면 <그림 1>과 같이 전체 샤드에 모두 요청을 보내지만 라우팅을 사용하면 <그림 2>와 같이 특정 샤드에만 요청을 보내는 것을 볼 수 있습니다. <그림 3>에 따르면 200개의 샤드에서 라우팅 사용 전과 후의 성능을 비교 했을 때 반응 시간이 10배 이상 차이 나는 것을 볼 수 있습니다. 라우팅을 적용하면 적용하지 않은 경우와 비교해 스레드 개수가 10~20배 증가하지만 CPU 사용률은 훨씬 적은 것을 볼 수 있습니다. 하지만 경우에 따라 라우팅을 적용하지 않은 것이 성능이 더 좋을 때도 있습니다. 여러 샤드로부터 결과가 취합되어야 하는 검색 질의에 대해서는 여러 샤드에 요청이 전달되는 것이 성능 상 유리할 수 있습니다.


    <그림1> 라우팅 사용 이전


    <그림2> 라우팅 사용 후


    <그림3> 라우팅 사용 이전/이후 비교


    댓글