ElasticSearch Getting Started - 3

2017. 6. 28. 16:09DB&NoSQL/Elasticsearch,ELK


Exploring Your data


Sample Dataset

지금까지 기초에 대해서 살짝 봤고, 실질적인 데이터셋을 가지고 작업해봅시다.
가상의 고객  은행 계좌 정보의 JSON documents 예제를 준비했습니다. 
각 document는 아래의 스카마를 따르게 됩니다.

{
    "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 -H "Content-Type: application/json" -XPOST 'localhost:9200/bank/account/_bulk?pretty&refresh' --data-binary "@accounts.json"
curl 'localhost:9200/_cat/indices?v'

*x-pack 설치 후에는 curl --user elastic:changeme  를 추가 해야 함

응답을 보면

health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   bank  l7sSYV2cQXmu6_4rJWVIww   5   1       1000            0    128.6kb        128.6kb

정상적으로 1000개의 documents들이 인덱스 되어져서 벌크되었다 란 뜻이지요.


The Search API

간단한 검색을 시작해봅시다.
검색을 하기 위해서는 두개의 기본 방법이 있습니다.
하나는 REST request URI 을 통해 검색 파라미터를 보내는 방법입니다.
또 다른 하는 REST request body를 통해서 검색어를 보내는 방법입니다.

request body  메소드는 좀 더 많은 표현이 가능하게 하고, 읽기 쉬운 JSON 포멧상에서 여러분의 검색어를 정의할수 있습니다.

request URL 메소드의 예제를 하도록 할건데, 하지만 튜토리얼의 나머지를 위해, 여기서는 request body 메소드를 사용하도록 하겠습니다.

검색을 위한 REST API는 _search 엔드포인트로 부터 접근가능합니다.
이 예제는 bank 인덱스에서 모든 documents를 리턴합니다.

GET /bank/_search?q=*&sort=account_number:asc&pretty

curl -XGET 'localhost:9200/bank/_search?q=*&sort=account_number:asc&pretty&pretty'
뭐 이렇게 해도 됩죠!

우선 잘게잘게 검색 호출을 분석해보도록 합시다.(dissert)
bank 인덱스에서 (_search 엔드포인트)로 검색하고 있고, q=* 파라미터는 인덱스에 모든 documents를 매칭하기 위해 Elasticsearch 에게 지시합니다.
sort=account_number:asc 파라미터는  account_number 필드을 기준으로 asc(ascending) 올림차순하란 뜻이구요.
pretty 파라미터는, 다시 말하자면, pretty-printed JSON 결과로 리턴하라고, Elasticsearch에 지시하는 것이구요.

결과는...(부분만 보여드립쬬)

{
  "took" : 63,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1000,
    "max_score" : null,
    "hits" : [ {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "0",
      "sort": [0],
      "_score" : null,
      "_source" : {"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"}
    }, {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "1",
      "sort": [1],
      "_score" : null,
      "_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"}
    }, ...
    ]
  }
}

응답을 보면 아래와 같은 부분으로 볼수 있습니다.

- took : 검색을 실행한 소요 시간 (miliseconds)
- timed_out : 검색이 타임아웃 났는지 여부를 알려줌
- _shards : 얼마나 많은 shard에서 검색되었는지와 더불어 검색이 성공/실패된 shard의 개수
- hits : 검색 결과
- hits.total : 검색어와 매칭된 document의 전체 개수
- hits.hits - 검색 결과의 실제 배열( 첫번째 10개의 document가 기본값)
- hits.sort - 결과의 정렬키(점수(score에 의한 정렬이라면 missing)
- hits._score 와 max_score :  지금은 해당 필드에 대해서는 무시 합니다.

여기에 동일한 실제 검색이 있습니다. 위에서 이야기한 request body method를 사용한 것이죠

  
GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ]
}

여기서 다른 점은 URI에 q=*를 건네는 대신, _search API에 JSON 스타일의 쿼리 request body를  POST 한것입니다. 
다음 섹션에서는 JSON query에 대해서 논의하도록 할 것입니다.

한번 여러분이 검색 결과를 되돌려 받는 것을 이해하는 것이 중요한데,
elasticsearch는 완벽하게 request를 가지고 끝내고, 여러분의 결과안에 open cursor들 또는
어떠한 서버사이드 리소스들의 종류를 남기지 않습니다.

이것은  적나라한(stark) 대조를 보입니다.
많은 다른 플랫폼들, 예를 들면 SQL와 같이  여러분이 최초에 여러분의 쿼리 결과들의 앞단의 일부분만 획득하고 
서버사이드 커서 상태(stateful)의 종류들을 사용하는 결과들의 나머지를 가져와야 한다면,  그때 끊임없이 서버에  되돌아가야만 합니다. 

뭐 그렇다는 것이네요.

Introducing the Query Language

Elasticsearch는 JSON 스타일 도메인 특정 언어를 제공합니다.
이건 여러분이 query들을 실행하는데 사용됩니다.
query 언어는 매우 포괄적(comprehensive)하고 한번 봤을때 겁먹을수 있지만,
이걸 실제 배우는데 있어서 가장 좋은 방법은 몇가지 기본 예제로 시작하는 것입니다.

마지막 예제로 돌아가서, 우리는 아래와 같은 쿼리르 실행 했습니다.

GET /bank/_search
{
  "query": { "match_all": {} }
}

분석해보면, query 부분은 우리 쿼리 정의가 무엇인지를 알려주고 match_all 파트는 간단히 동작하기 원하는 query 타입이다.
match_all 쿼리는 간단하게 명시된 인덱스상에 모든 document를 검색하는 것입니다.

추가적으로 query 파라미터는, 우리는 검색결과에 영향을 주는 다른 파라미터도 전달할수 있습니다.
위의 예제에서, 우리는 soert를 전달해고, 여기서는 size를 전달합니다.

GET /bank/_search
{
  "query": { "match_all": {} },
  "size": 1
}

size가 명시되지 않았다면, 기본값은 10입니다.
이 예제는 match_all를 하고 11 에서 20의 document를 리턴합니다.

GET /bank/_search
{
  "query": { "match_all": {} },
  "from": 10,
  "size": 10
}

from 파라미터는 어느 document 인덱스에서 시작하는지를 명시하고 size 파라미터는 from 파라미터에서 사작으로
document 를 얼마나 많이 리턴하는냐를 명시합니다.
검색 결과의 페이징을 구현할때 굉장히 유용합니다.
from이 명시되지 않았을때 기본값은 뭐 알다시피 0 입니다.

이 예제는 match_all 하고 account balance로 내림차순 정렬 하고 상위 10개의 document를 리턴합니다.

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": { "balance": { "order": "desc" } }
}

Executing Searches

기본적인 검색 파라미터들을 봤으니, Query DSL안으로 좀 더 깊에 파고 들어가봅시다.
우선 리턴된 document 필드들을 봅시다.
기본으로, 전체 JSON document은 모든 검색의 일부분만 리턴합니다. 
이건 소스(검색 hits안에 _source 필드)를 참조합니다. 
만약 완전한  소스 document를 리턴받는것을 원치 않으면, 리턴될 소스로 부터 몇개의 필드만 요청할수 있는 능력을 갖고 있습니다.

이 예제는 검색으로 부터 account_number 와 balance(_source 안쪽에), 두개의 필드만 리턴되는 것을 보여줍니다.

GET /bank/_search
{
  "query": { "match_all": {} },
  "_source": ["account_number", "balance"]
}

위의 예제는 _source 필드를 간단하게 줄여주는 것을 기억하세요.
_source 라는 필드만 리턴하는데, 하지만 그 안에서, account_number 와 balance만 포함되었습니다.

SQL를 좀 하면, SQL SELECT FROM 필드 리스트에 대한 컨셉과 비슷할것이다.

쿼리 파트로 옮겨와서, 이전에 match_all 쿼리가 모든 document들을 어떻게 매치하는데 사용되지는지를 봤습니다.
검색 쿼리의 기본 필드로써 생각할수 있는, match 쿼리라고 불리는 새로운 쿼리에 대해서 소개하도록 합시다.
(i.e 검색은 특정필드 또는 필드셋에 대해 완료한다.)

이 예제는 20이라고 넘버링된 계정을 리턴합니다.

GET /bank/_search
{
  "query": { "match": { "account_number": 20 } }
}

이 예제는 주소에 "mill" 이라는 용어가 포함된 모든 계정을 리턴합니다.

GET /bank/_search
{
  "query": { "match": { "address": "mill" } }
}

이예제는 주소에 "mill" 또는 "lane" 용어가 포함된 모든 계정을 리턴합니다.

GET /bank/_search
{
  "query": { "match": { "address": "mill lane" } }
}

이 예제는  주소에 "mill lane" 절이 포함된 모든 계정를 리턴하는  match(match_phrase)의 다른 형태(variant)것입니다. 

GET /bank/_search
{
  "query": { "match_phrase": { "address": "mill lane" } }
}

이번엔 bool(ean) Query 에 대해서 소개 하겠습니다. 
bool 쿼리는 boolean 로직을 사용하여 작은 쿼리를  보다 큰 쿼리 안으로 넣어 구성하도록 합니다.

이 예제는 두개의 match 쿼리들을 구성하고, 주소에 "mill" 과 "lane"이 포함된 모든 계정을 리턴합니다.

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

위의 예제에서, bool must  절은 match가 고려된 document들에 대해 true 여야 하는 모든 쿼리들을 명시합니다. 

반대로, 아래 예제는 두개의 match 쿼리를 구성하고 주소에 "mill" 이나 "lane"이 포함된 모든 계정을 리턴합니다.

GET /bank/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

위의 예제에서 bool should 절은 매칭되는 문서에 대해서 참인 어떤 쪽이든지 포함되는 쿼리들의 리스트를 명시합니다.

이 예제는 두개의 match 쿼리들을 구성하고, "mill" 또는 "lane" 이 주소에 없는 모든 계정을 리턴합니다.

GET /bank/_search
{
  "query": {
    "bool": {
      "must_not": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

위의 예제에서는 bool must_not 절은 매칭되는 문서들에 대해서 참이여야 하는 것이없는 쿼리들의 리스트를 명시합니다. 

우리는 bool 쿼리 안에 must,should,must_not 절을 상황에 맞게 조합할수 있습니다.
더나아가, 우리는 복잡한 멀티 레벨의 boolean 로직을 흉내내기 위한(mimic) 이런 bool 절 쿼리 안에 bool 쿼리를 조합할수 있습니다.

이 예제는 40살인 하지만 ID(aho)에 살고 있지 않은 사람들의 모든 계정을 리턴합니다. 

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}

Executing Filters

앞장에서, document score(검색 결과에 _score 필드)라고 불리는 부분을 건너 뛰었는데.
score는 숫자값,얼마나 잘 document가 우리가 명시한 검색 쿼리와 매치하는지에 대한 상대적인 측정법입니다. 
좀 더 높은 score 는, document의 관련성이 높고(more relevant), 좀 더 낮은 score는 document의 관련성이 떨어집니다.(less relevant)

그러나 항상 쿼리가 score들을 생산할 필요는 없습니다. 특별히, 쿼리들이 단지 "filtering" document 셋에서만 사용됩니다.
Elasticsearch는 이런 상황을 감지하고, 쓸모없는 score를 계산을 하지 않기 위해 자동으로 query 실행을 활용합니다. 

앞서 소개한 bool query는 score를 계산하는 방법이 변경되는 것 없이,
다른 절에 의해 매칭 될 document를 제한하기 위한 쿼리를 사용하도록 filter 절을 지원합니다. 
예제로, range query를 소개합니다.
이건 range 값에 의해 document들을 필터링 하도록 합니다.
이건 일반적으로 숫자 또는 날짜 필터링을 위해 사용됩니다.

이 예제에서는 balance가 20000에서 30000사이에 포함하는 모든 계정을 리턴하는 bool query 를 사용합니다. 
다른말로,  20000과 같거나 큰거 그리고 30000과 같거나 작은 balance를 갖는 계정을 찾는 것 원하죠

GET /bank/_search
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}

하나씩 뜯어 보면, bool 쿼리는 match_all 쿼리(the query part)와 range 쿼리(the filter part)을 포함하고 있습니다.
원하신다면 해당 쿼리와 필터에 어떤 다른 쿼리들을 대신할수 있습니다.
위의 경우에, document들이 "동등하게",예로, 어떠한 document도 다른 document 보다 더 적합하지 않은
모두 매칭되는 범위안에 포함한 후  range쿼리는 완벽한 센스를 만듭니다. 

match_all, match, bool,range 쿼리들에 더해, 사용할수 있고, 여기서 사용하지 않은 많은 다른 쿼리 타입들이 있습니다.
어떻게  동작하는지에 대한 기본 이해는 되었으니, 다른 것을 적용하는 것은 더이상 어렵지 않을것입니다.
다른 쿼리에 대해서는 이런 저런 실험을 많이 해보도록 하세요

Executing Aggregations

집합(Aggregation)들은 그룹핑하는 능력과 여러분의 데이터로 부터 통계를 추출하는 능력을 제공합니다.
집합에 대해 가장 생각하는데 가장 쉬운 방법은 SQL GROUP BY 와 SQL aggregate function들과 대충 비슷하다는 것입니다.
Elasticsearch에서는 hits를 리턴하는 검색들을 실행하는 능력을 갖고,
동시에 하나의 응답안에 모든 hits로 부터 분리된 집합된 결과(aggregated results)들을 리턴합니다.

이건 매우 파워풀하고 유용합니다.의미상으로 여러분은  쿼리들과 다수의 집합체들을 동작할수 있고, 
간결하고(concise) 단순화된 API를 사용하여, 네트워크 라운드트립을 피해 한방에,
쿼리들과  다수의 집합체들에 대한 결과를 얻을수 있습니다.

시작하기 위해서, state로 모든 계정을 그룹핑하고 그때 count로 내림차순 정렬한 top 10개 states를 리턴합니다.

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      }
    }
  }
}


SQL에서는, 위의 집합은 아래와 비슷하겠습니다.

SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC

응답은  아래와 같습니다.(일부분만)

{
  "took": 29,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits" : {
    "total" : 1000,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_state" : {
      "doc_count_error_upper_bound": 20,
      "sum_other_doc_count": 770,
      "buckets" : [ {
        "key" : "ID",
        "doc_count" : 27
      }, {
        "key" : "TX",
        "doc_count" : 27
      }, {
        "key" : "AL",
        "doc_count" : 25
      }, {
        "key" : "MD",
        "doc_count" : 25
      }, {
        "key" : "TN",
        "doc_count" : 23
      }, {
        "key" : "MA",
        "doc_count" : 21
      }, {
        "key" : "NC",
        "doc_count" : 21
      }, {
        "key" : "ND",
        "doc_count" : 21
      }, {
        "key" : "ME",
        "doc_count" : 20
      }, {
        "key" : "MO",
        "doc_count" : 20
      } ]
    }
  }
}

ID(Idaho) 에 27개의 계정, TX(Texas)에 27개의 계정, AL(Alabama)에 25개의 계정, 등등이 있는 것을 볼수 있습니다. 
우리는 size=0 이라고 세팅했습니다.
hists 검색을 보여주지 않으려고, 이유는 응답에 대한 집합 결과만 보길 원하기 때문이죠.

앞서서 집합을 빌딩하는 것은, 이 예제는 평균 계정 balance를 state에 의해 계산합니다.
(다시 ,count로 내림차순 정렬한  top 10개 state)

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

average_balance 집합이 group_by_state 집합안에 포함되어 있는지에 대해 알아두도록.
이건 모든 집합에 일반적인 패턴입니다.
집합을 집합안에 맘대로 배치할수 있는데, 여러분이 여러분 데이터로 부터 요구하는 피봇된 정리된것을 추출하기 위해서다.

앞선 집합상에 빌드하는 것은, 내림차순 정렬에 평균 balance를 정렬해보는 것입니다.

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword",
        "order": {
          "average_balance": "desc"
        }
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

이 예제는 연령대(나이가 20-29 , 30-39, 40-49), 성별로 그룹핑하는 방법에 대해서 설명합니다.
연령대, 성별 마다 평균 계정 balance를 최종적으로 얻을 수 있습니다.

GET /bank/_search
{
  "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.keyword"
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }
  }
}

여기서 다루진 않았지만 많이 응용해서 여러가지를 접목해 볼수 있겠습니다.


Conclusion
Elasticsaerch는 단순하고 복잡한 제품이다.
무엇인지에 대한 기본들, 어떻게 그 안을 볼지, REST API들을 가지고 어떻게 그것을 가지고 일할지, 등등 배웠습니다.
잘 합시다.