욱'S 노트

Elasticsearch - Exploring Your Data 본문

Programming/Elasticsearch

Elasticsearch - Exploring Your Data

devsun 2015. 5. 27. 17:36

이제 우리는 기초적인 내용을 잠깐 보았다. 이제 좀더 실제적인 데이터셋과 동작을 해보자. 고객 은행 계좌 정보를 표현한 샘플을 준비하였다. 각 도큐먼트는 다음과 같은 스키마를 가진다.


{

    "account_number": 0,

    "balance": 16623,

    "firstname": "Bradshaw",

    "lastname": "Mckenzie",

    "age": 29,

    "gender": "F",

    "address": "244 Columbus Place",

    "employer": "Euron",

    "email": "bradshawmckenzie@euron.com",

    "city": "Hobucken",

    "state": "CO"

}


이 데이터는 www.json-generator.com/로 부터 생성한 데이터이다. 이 사이트는 랜덤하게 실제 데이터를 무시하되 의미있는 데이터를 생성해준다.


Loading the Sample Dataset


샘플 데이터셋을 다운로드 받고 디렉토리에 압축을 해제한 다음 아래의 명령으로 데이터를 로딩해보자.


curl -XPOST 'localhost:9200/bank/account/_bulk?pretty' --data-binary @generated.json


그리고 인덱스에 로딩된 데이터를 살펴보자.


curl 'localhost:9200/_cat/indices?v'

health index pri rep docs.count docs.deleted store.size pri.store.size

yellow bank    5   1       1000            0    424.4kb        424.4kb


1000개의 도큐먼트가 bank 인덱스에 성공적을 벌크 로딩 되었다는 것을 의미한다.


The Search API


이제 몇가지 간단한 검색을 시작해보자. 검색을 수행하기 위한 두가지 간단한 방법은 다음과 같다. 하나는 REST URI를 통해 검색 파라미터를 전송하는 방법이고, 다른 방법은 REST 요청 본문에 검색 파라미터를 전송하는 방식이다. request 본문에 검색 파라미터를 전달하는 방식이 더 표현하기 쉽고 더 읽기도 쉬울 것이다. 그러나 우리는 이번 튜토리얼에서는 URI를 통해 파라키터를 전송할 것이다.


검색을 위한 REST API는 _search 엔드포인트를 통해 접근가능하다. 이번 예제는 bank 인덱스의 모든 도큐먼트를 반환하는 것이다.


curl 'localhost:9200/bank/_search?q=*&pretty'


첫번째 검색 요청을 분석해보자. 우리는 bank 인덱스를 조회할 것이고, q=* 라고 파라미터를 지정하였는데 이 의미는 인덱스에 모든 도큐먼트를 매칭하겠다는 의미이다.


결과는 다음과 같다. (중략)


curl 'localhost:9200/bank/_search?q=*&pretty'

{

  "took" : 63,

  "timed_out" : false,

  "_shards" : {

    "total" : 5,

    "successful" : 5,

    "failed" : 0

  },

  "hits" : {

    "total" : 1000,

    "max_score" : 1.0,

    "hits" : [ {

      "_index" : "bank",

      "_type" : "account",

      "_id" : "1",

      "_score" : 1.0, "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}

    }, {

      "_index" : "bank",

      "_type" : "account",

      "_id" : "6",

      "_score" : 1.0, "_source" : {"account_number":6,"balance":5686,"firstname":"Hattie","lastname":"Bond","age":36,"gender":"M","address":"671 Bristol Street","employer":"Netagy","email":"hattiebond@netagy.com","city":"Dante","state":"TN"}

    }, {

      "_index" : "bank",

      "_type" : "account",


응답으로 부터 다음과 같은 부분을 발견할 수 있다.


took – 엘라스틱 서치가 검색을 수행하는데 걸린 시간 (milliseconds)

timed_out – 검색에 타임아웃이 발생했는지 안했는지 여부

_shards – 얼마나 많은 샤드에서 검색했는지와 성공/실패 샤드 수

hits – 검색 결과

hits.total – 검색 조건에 매칭된 도큐먼트 수

hits.hits – 검색 결과 실제 배열 (디폴트로 처음 10개의 도큐먼트만 보인다.)

_score and max_score - 이번에는 무시하자.


여기 똑같은 검색을 요청 본문에서  처리해보자.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": { "match_all": {} }

}'


URI에 q=*를 전달하는 대신에 검색을 위해 JSON 스타일의 요청 본문을 전송하였다. 다음 섹션에서 JSON 쿼리에 대해서 논의해보도록 하자.


결과는 같을 것이다.


이해해야할 중요한 점은 한번 검색 결과가 전달 되면 엘라스틱서치는 더 이상 어떤 종류의 서버 사이드 리소스나 결과에 대한 커서를 열지 않는다는 것이다. 이것은 SQL기반의 다른 플랫폼에서는 완전한 제약조건이며, 이로서 전체 쿼리 결과에 따른 패치를 계속 서버로 와서 수행할 수 있게 되는 것이다. 이럴 경우 스테이트풀한 서버 사이드 커서를 이용한다.


Introducing the Query Language


엘라스틱 서치는 JSON 스타일에 DSL을 쿼리를 수행할 수 있게 제공한다. 쿼리 언어는 꽤 이해하기 쉽다. 


지난 예제로 돌아가서 쿼리를 수행해보자.


{

  "query": { "match_all": {} }

}


위의 내용를 해석해보면 쿼리 부분은 우리에서 쿼리 정의가 어떻게 되었는지 알려주고 match_all 파트는 단순히 우리가 수행하고 싶은 쿼리의 타입이다. match_all 쿼리는 단순히 특정 인덱스에 있는 모든 도큐먼트를 검색한다.


쿼리 파라미터에 추가적으로 검색 결과에 영향을 주는 다른 파라미터를 정의할 수 있다. 예를 들어 다음은 모든 매치되는 문서중 단 하나의 결과만 반환해 달라는 의미이다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": { "match_all": {} },

  "size": 1

}'


사이즈를 명시하지 않으면 기본적으로 10이다.


다음 예제는 11 부터 20번째 문서를 리턴해달라는 것이다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": { "match_all": {} },

  "from": 10,

  "size": 10

}'


첫번째 도큐먼트는 0이고 from 파라미터는 몇번째 도큐먼트부터 인지를 지정하고 size는 얼마나 많은 도큐먼트를 from 파라미터로부터 반환할 것인가를 의미한다. 이러한 기능을 검색결과의 페이징을 구현할 때 유용한다. 만약 from을 명시하지 않으면 기본값은 0이다.


이번 예제는 account 잔액에 따라 내림차순 정렬을 한 다음 상위 10개의 도큐먼트만 반환하는 예제이다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": { "match_all": {} },

  "sort": { "balance": { "order": "desc" } }

}'


Executing Searches


몇가지 기본적인 검색 파라미터에 대해서 살펴 보았다. 이제 쿼리 DSL에 대해서 조금더 살펴보자. 첫번째는 반환되는 도큐먼트 필드에 대해서 살펴보겠다. 기본적으로 모든 검색의 결과로 풀 JSON 도큐먼트가 반환된다. 이것은 source로 표현된다. 만약 전체 소스 도큐먼트를 리턴받기를 원하지 않는다면 source내의 특정 필드를 지정할 수 있다.


소스내의 account_number와 balance만 반환받는 예제이다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": { "match_all": {} },

  "_source": ["account_number", "balance"]

}'


만약 SQL 지식이 있다면 위의 예제는 SELECT FROM의 필드리스트와 유사하다는 것을 알 수 있을 것이다.


이제 쿼리 부분으로 돌아가자. 이전에 match_all은 모든 도큐먼트를 매칭할때 사용된다고 했다. 이제 match라고 불리는 새로운 쿼리를 소개하겠다. match 검색 쿼리의 가장 기본이라고 생각하면 되겠다.


이번 예제는 account number 가 20인것을 리턴한다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": { "match": { "account_number": 20 } }

}'


이번 예제는 mill이라는 단어를 포함한 주소를 가진 모든 계좌를 반환한다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": { "match": { "address": "mill" } }

}'


이번 예제는 주소가 mill 또는 lane을 포함하는 모든 계좌를 반환한다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": { "match": { "address": "mill lane" } }

}'


이번 예제는 mill lane 이라는 구문이 포함된 계좌를 반환한다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": { "match_phrase": { "address": "mill lane" } }

}'


이제 불린 쿼리를 소개하겠다. 불린 쿼리는 불린 로직을 이용해 작은 쿼리들을 큰 쿼리로 변환 기능을 제공한다.


다음 예제는 두개의 매치 쿼리를 이용해 mill 과 lane이 포함된 주소를 가진 모든 계좌를 번환한다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": {

    "bool": {

      "must": [

        { "match": { "address": "mill" } },

        { "match": { "address": "lane" } }

      ]

    }

  }

}'


위의 예에서 must 절은 모든 쿼리의 결과를 만족하는 도큐먼트만을 제공해달라는 의미이다.


반대로 mill이나 lane이 주소에 포함된 모든 계좌를 리턴받기 위해선 아래와 같이 명령을 수행하면 된다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": {

    "bool": {

      "should": [

        { "match": { "address": "mill" } },

        { "match": { "address": "lane" } }

      ]

    }

  }

}'


위의 예제에서 should 절은 리스트의 쿼리 중 어떤 것이라고 참이되면 매칭된 것으로 간주한다는 의미이다.


이번 예제는 mill 이나 lane이 모두 포함되지 않는 모든 계좌를 반환한다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": {

    "bool": {

      "must_not": [

        { "match": { "address": "mill" } },

        { "match": { "address": "lane" } }

      ]

    }

  }

}'


위의 예제에서 must_not 절은 쿼리 리스트 중 참이 되면 매칭의 결과로 고려하지 않겠다는 의미이다.


must, should, must_not은 불린 쿼리내에 동시에 적용할 수 있다. 


이번 예제는 나이가 40인 사람들의 계좌를 리턴하는데 ID 주에 살지 않는 사람을 리턴해달라는 의미이다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": {

    "bool": {

      "must": [

        { "match": { "age": "40" } }

      ],

      "must_not": [

        { "match": { "state": "ID" } }

      ]

    }

  }

}'


Executing Filters


이전 섹션에서 우리는 도큐먼트 score라고 불리는 상세 정보를 스킵했었다. 스코어는 숫자이고 우리가 명시한 검색 쿼리에 도큐먼트가 얼마나 잘 맞는지를 나타내는 상대적인 수치이다. 더 높은 스코어는 상대적으로 더 신뢰할 수 있는 도큐먼트이고 낮은 스코어는 약간 덜 신뢰할 수 있는 도큐먼트라고 생각할 수 있다. 


엘라스틱 서치는 모든 쿼리는 스코어 계산을 수행한다. 어떤 경우 우리는 스코어가 필요없을 수 있는데 엘라스틱 서치는 필터라는 형태의 또다른 쿼리 기능을 제공한다.  필터는 쿼리에서 제외하는 컨셉이며, 더 빠른 수행속도를 위한 두가지 주요한 특징을 가지고 있다. 


필터는 쿼리보다 더 빠르게 수행하기 위해 스코어를 계산하지 않는다.

필터는 반복적인 쿼리가 훨씬 빨리 수행될 수 있데 메모리에 캐쉬될 수 있다. 


필터를 이해하기 위해선 첫번째 필터 쿼리를 소개하겠다. 예제는 range 필터이며 range값에 따라 도큐먼트를 필터링 한다. 일반적으로 숫자나 날짜를 필터링하기 위해 사용된다.


이번 예제는 잔액 20000에서 30000 사이인 모든 계좌를 리턴하는 필터 쿼리이다. 


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "query": {

    "filtered": {

      "query": { "match_all": {} },

      "filter": {

        "range": {

          "balance": {

            "gte": 20000,

            "lte": 30000

          }

        }

      }

    }

  }

}'


위를 분석해 보면 필터 쿼리는 match_all 쿼리를 포함하고 range filter가 적용되어 있다. 위의 경우 range 필터는 range안에 들어온 결과가 의미가 있으므로, 더 이상 도큐먼트는 다른 것들과 연관성을 고려할 필요가 없다.


일반적으로 필터를 사용할 지 쿼리를 사용할 지 여부는 연관 스코어가 필요한지 아닌지 여부이다. 스코어가 중요하지 않다면 필터를 쓰고, 그렇지 않다면 쿼리를 사용해라. 만약 SQL 지식이 있다면 쿼리와 필터는 SELECT WHERE 절과 비슷하다.


게다가 match_all, match, bool, filtered 그리고 range query외에 다양한 query/filter 타입이 존재한다. 이번에는 기초만 이해하기 위해서 자세한 설명을 생략한다.


Executing Aggregations


데이터에 대한 통계를 그룹짓고 추출하기 위해 집합 기능을 제공한다. 집합연산에 대해 이해하기 쉬운 간단한 방식은 SQL의 GROUP BY와 다른 집합연산들과 어느 정도 비슷하다고 생각하면 된다. 엘라스틱 서치에서는 서치를 수행하고 동시에 집합연산의 결과를 한 reponse에 받을 수 있는 기능을 제공한다. 이는 매우 강력하고 효율적이다. 한번에 오퍼레이션을 수행하기 때문에 네트워크 라운드트립을 피할 수 있다.


모든 계좌의 주를 그룹핑하는 예제를 수행해보자. 카운트 별로 내림차순되어 상위 10개의 주가 반환된다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "size": 0,

  "aggs": {

    "group_by_state": {

      "terms": {

        "field": "state"

      }

    }

  }

}'


위의 집합연산의 컨셉은 SQL의 아래 쿼리와 비슷하다.


SELECT COUNT(*) from bank GROUP BY state ORDER BY COUNT(*) DESC


결과는 다음과 같다.(partially shown):


  "hits" : {

    "total" : 1000,

    "max_score" : 0.0,

    "hits" : [ ]

  },

  "aggregations" : {

    "group_by_state" : {

      "buckets" : [ {

        "key" : "al",

        "doc_count" : 21

      }, {

        "key" : "tx",

        "doc_count" : 17

      }, {

        "key" : "id",

        "doc_count" : 15

      }, {

        "key" : "ma",

        "doc_count" : 15

      }, {

        "key" : "md",

        "doc_count" : 15

      }, {

        "key" : "pa",

        "doc_count" : 15

      }, {

        "key" : "dc",

        "doc_count" : 14

      }, {

        "key" : "me",

        "doc_count" : 14

      }, {

        "key" : "mo",

        "doc_count" : 14

      }, {

        "key" : "nd",

        "doc_count" : 14

      } ]

    }

  }

}


AL주에 있는 21개 계좌, TX주에 17개 계좌등이 있다는 것을 확인할 수 있다.


size=0의 의미는 서치 결과를 보여주지 말라는 의미이다. 단지 집합의 결과만 보겠다는 의미이다.


이번 예제는 주별로 평균 계좌 잔액을 계산하는 예제이다. (이번에도 카운트별로 내림차순 정렬되어 상위 10개의 상태만이 나타난다.):


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "size": 0,

  "aggs": {

    "group_by_state": {

      "terms": {

        "field": "state"

      },

      "aggs": {

        "average_balance": {

          "avg": {

            "field": "balance"

          }

        }

      }

    }

  }

}'


어떻게 group_by_state 집한 연산내에 average_balance 집한연산이 수행되었느지를 주의 깊게 살펴 보자. 이것은 모든 집합연산에 공통적인 패턴이다. 당신은 임의의 집합연산내에 다른 집합연산을 내포할 수 있다.


이제 average balance에 따라 내림차순으로 정렬해보자.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "size": 0,

  "aggs": {

    "group_by_state": {

      "terms": {

        "field": "state",

        "order": {

          "average_balance": "desc"

        }

      },

      "aggs": {

        "average_balance": {

          "avg": {

            "field": "balance"

          }

        }

      }

    }

  }

}'


이번 예제는 나이대에 따라 그룹핑을 한 다음 성별에 따라 그룹핑을 하고 마지막으로 각 그룹의 balance를 평균을 내보겠다.


curl -XPOST 'localhost:9200/bank/_search?pretty' -d '

{

  "size": 0,

  "aggs": {

    "group_by_age": {

      "range": {

        "field": "age",

        "ranges": [

          {

            "from": 20,

            "to": 30

          },

          {

            "from": 30,

            "to": 40

          },

          {

            "from": 40,

            "to": 50

          }

        ]

      },

      "aggs": {

        "group_by_gender": {

          "terms": {

            "field": "gender"

          },

          "aggs": {

            "average_balance": {

              "avg": {

                "field": "balance"

              }

            }

          }

        }

      }

    }

  }

}'


더 많은 집합연산 기능을 제공하지만 여기서는 더 이상 다루지 않겠다. 더 많은 연산이 필요하면 aggregation 가이드를 참고하기 바란다. 


Conclusion


엘라스틱 서치는 단순하면서도 복잡한 프로덕트이다. 우리는 어느 정도 기초에 대해서 배웠다. 이 튜토리얼이 엘라스틱 서치를 이해하는데 많은 도움이 되었기를 바란다.

'Programming > Elasticsearch' 카테고리의 다른 글

Elasticsearch - Mapping  (0) 2015.05.28
Elasticsearch - API Conventions  (0) 2015.05.28
Elasticsearch - Modifying Your Data  (0) 2015.05.27
Elasticsearch - Exploring Cluster  (0) 2015.05.22
Elasticsearch - 시작하기  (1) 2015.05.20
Comments