고딩왕 코범석

ELK Stack - (1) Elastic Search 본문

Data/ELK

ELK Stack - (1) Elastic Search

고딩왕 코범석 2021. 6. 19. 22:28
반응형

안녕하세요! 이번 포스팅에서는 ELK 스택에서 Elastic Search의 기본 개념을 작성해보았습니다.처음이다 보니 틀린 내용들이 있을 수 있어, 가벼운 참고 및 비판적인 시선으로 포스팅을 봐주시면 감사하겠습니다..!

먼저 ELK 스택은 다음과 같습니다.

  • Elastic Search
  • Logstash
  • Kibana

Logstash가 Mysql, MongoDB, CSV 파일 등등 어떤 데이터든지 수집해서 Elastic Search에 넘겨주고 Kibana는 Elastic Search에 쌓인 데이터들을 보기 좋게 시각화해주는 역할을 합니다. Kibana가 Elastic Search의 빠른 검색 시스템을 기반으로 데이터들을 가져와 프론트엔드 코드 하나 없이 보기 좋게 보여줍니다.

image

Elastic Search 기본 개념 정리

왜 RDB보다 성능이 좋을까요?

image

image

그림 처럼 RDB는 어떤 데이터가 저장될 때, 데이터 자체를 모두 저장합니다. 하지만 Elastic Search는 특정 키워드가 어디에서 나타났는지 저장되기 떄문에 단순히 Search라는 관점에서 보았을 때, RDB보다 검색 성능이 훨씬 좋다고 할 수 있죠. 저는 쉽게 생각하면 Elastic Search는 해쉬 테이블 개념이라고 이해했고, RDB는 모든 데이터들을 풀스캔(인덱스나 이런 설정 없다고 가정)해서 찾으려는 데이터가 있는지 찾기 때문에 Elastic Search가 보다 정보를 가져오는 속도가 빠르다! 라고 이해했습니다.

image

image

image

ElasticSearch 사용하기

Create Index(Database)

image

다음과 같은 명령어로 인덱스를 조회해보고, 결과를 보았습니다.

curl -XGET http://localhost:9200/classes?pretty
{
  "error": {
    "root_cause": [
      {
        "type": "index_not_found_exception",
        "reason": "no such index [classes]",
        "resource.type": "index_or_alias",
        "resource.id": "classes",
        "index_uuid": "_na_",
        "index": "classes"
      }
    ],
    "type": "index_not_found_exception",
    "reason": "no such index [classes]",
    "resource.type": "index_or_alias",
    "resource.id": "classes",
    "index_uuid": "_na_",
    "index": "classes"
  },
  "status": 404
}

404에러가 발생하는데, 이는 classes라는 인덱스를 우리가 만들지 않았기 때문에 발생하는 에러입니다. 이제 직접 만들어보죠!

image

{
    "acknowledged":true,
    "shards_acknowledged":true,
    "index":"classes"
}

이렇게 인덱스가 생성된 것을 볼 수 있고, 다시 아까 했던 조회를 해보면

{
  "classes" : {
    "aliases" : { },
    "mappings" : { },
    "settings" : {
      "index" : {
        "routing" : {
          "allocation" : {
            "include" : {
              "_tier_preference" : "data_content"
            }
          }
        },
        "number_of_shards" : "1",
        "provided_name" : "classes",
        "creation_date" : "1624066751056",
        "number_of_replicas" : "1",
        "uuid" : "OBu3_zwNTnGhilWEZJ2DVg",
        "version" : {
          "created" : "7130299"
        }
      }
    }
  }
}

이렇게 결과가 나오는 것을 확인할 수 있어요!

Delete Index

다음은 인덱스를 지워보겠습니다.

curl -XDELETE http://localhost:9200/classes
{
  "acknowledged" : true
}

정상적으로 잘 지워졌다는 신호를 받았네요!

Create Type(Table), Document(Row)

이제 RDB에서 ROW의 개념인 Document를 만들어볼게요. 만약 인덱스가 존재하지 않더라도 인덱스명과 타입을 기재한다면 자동으로 만들어줍니다.

image

{
    "_index": "classes",
    "_type": "class",
    "_id": "1",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

이후 잘 저장되었는지 조회를 해보죠.

curl -XGET http://localhost:9200/classes/class/1/?pretty
{
    "_index": "classes",
    "_type": "class",
    "_id": "1",
    "_version": 1,
    "_seq_no": 0,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "title": "Algorithm",
        "professor": "John"
    }
}

저는 classes 라는 INDEXclass 라는 TYPE을 선언하였고 제목은 알고리즘, 교수는 존 이라는 JSON 형식의 ROW를 저장했습니다. 이 때 저장된 구조는 밑의 그림과 같아요.

image

그럼 row를 저장할 때 마다 JSON 형식을 입력하는 것은 매우 번거로운 일입니다. json 파일로 row를 저장해보겠습니다. 밑에 처럼 json 파일을 만들어볼까요?

{
    "title" : "Math",
    "professor" : "beomseok",
    "major" : "computer",
    "unit" : 3,
    "student_count" : 100
}

그후 curl 명령어로 elastic search에 전송해봅시다. 저는 oneclass.json 이라는 파일 명으로 저장을 했고, 이 파일이 위치한 곳에서 직접 명령어를 날렸습니다.

 curl -XPOST http://localhost:9200/classes/class/1/ -H "Content-Type: application/json" -d @oneclass.json

다시 조회해보면

{
    "_index": "classes",
    "_type": "class",
    "_id": "1",
    "_version": 2,
    "_seq_no": 1,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "title": "Math",
        "professor": "beomseok",
        "major": "computer",
        "unit": 3,
        "student_count": 100
    }
}

이렇게 잘 조회되는 것을 확인할 수 있습니다!

Update Document

이번에는 수정을 해보겠습니다! 위에서 삽입한 json(제목, 교수, 전공, 학점, 학생수) 에서 학년 필드를 추가해볼까요?

curl -XPOST http://localhost:9200/classes/class/1/_update?pretty -d '
{"doc" : {"grade" : 3}}' -H "Content-Type: application/json"

다시 조회를 해보면!

{
    "_index": "classes",
    "_type": "class",
    "_id": "1",
    "_version": 3,
    "_seq_no": 2,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "title": "Math",
        "professor": "beomseok",
        "major": "computer",
        "unit": 3,
        "student_count": 100,
        "grade": 3
    }
}

방금 추가한 grade : 3이 보이네요!

수정하는 또 다른 방식인 스크립트 방식(?)으로 수정이 가능한데, 이는 좀 더 프로그래밍 적으로? 수정이 가능합니다.

curl -XPOST http://localhost:9200/classes/class/1/_update -d '
{"script" : "ctx._source.unit += 5"}' -H "Content-Type: application/json"

기존의 학점에 5를 추가했고, 확인해보면

{
    "_index": "classes",
    "_type": "class",
    "_id": "1",
    "_version": 4,
    "_seq_no": 3,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "title": "Math",
        "professor": "beomseok",
        "major": "computer",
        "unit": 8,
        "student_count": 100,
        "grade": 3
    }
}

기존의 3학점에 5를 더해 8학점으로 변경된 것이 보이실 거에요!

Bulk Insert

이번에는 여러개의 데이터를 한번에 넣어볼까요?

{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "1" } }
{"title" : "Machine Learning","Professor" : "Minsuk Heo","major" : "Computer Science","semester" : ["spring", "fall"],"student_count" : 100,"unit" : 3,"rating" : 5, "submit_date" : "2016-01-02", "school_location" : {"lat" : 36.00, "lon" : -120.00}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "2" } }
{"title" : "Network","Professor" : "Minsuk Heo","major" : "Computer Science","semester" : ["fall"],"student_count" : 50,"unit" : 3,"rating" : 4, "submit_date" : "2016-02-02", "school_location" : {"lat" : 36.00, "lon" : -120.00}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "3" } }
{"title" : "Operating System","Professor" : "Minsuk Heo","major" : "Computer Science","semester" : ["spring"],"student_count" : 50,"unit" : 3,"rating" : 4, "submit_date" : "2016-03-02", "school_location" : {"lat" : 36.00, "lon" : -120.00}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "5" } }
{"title" : "Machine Learning","Professor" : "Tim Cook","major" : "Computer Science","semester" : ["spring"],"student_count" : 40,"unit" : 3,"rating" : 2, "submit_date" : "2016-04-02", "school_location" : {"lat" : 39.00, "lon" : -112.00}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "6" } }
{"title" : "Network","Professor" : "Tim Cook","major" : "Computer Science","semester" : ["summer"],"student_count" : 30,"unit" : 3,"rating" : 2, "submit_date" : "2016-02-02", "school_location" : {"lat" : 36.00, "lon" : -120.00}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "7" } }
{"title" : "Operating System","Professor" : "Jeniffer Anderson","major" : "Computer Science","semester" : ["winter"],"student_count" : 30,"unit" : 3,"rating" : 1, "submit_date" : "2016-11-02", "school_location" : {"lat" : 39.97, "lon" : -89.78}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "8" } }
{"title" : "Algorithm","Professor" : "Tim Cook","major" : "Computer Science","semester" : ["fall"],"student_count" : 80,"unit" : 3,"rating" : 2, "submit_date" : "2016-10-22", "school_location" : {"lat" : 39.97, "lon" : -89.78}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "9" } }
{"title" : "Data Structure","Professor" : "Tim Cook","major" : "Computer Science","semester" : ["winter"],"student_count" : 50,"unit" : 3,"rating" : 2, "submit_date" : "2016-07-22", "school_location" : {"lat" : 39.97, "lon" : -89.78}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "10" } }
{"title" : "Computer Graphic","Professor" : "Jeniffer Anderson","major" : "Computer Science","semester" : ["spring"],"student_count" : 60,"unit" : 2,"rating" : 3, "submit_date" : "2016-11-12", "school_location" : {"lat" : 39.97, "lon" : -89.78}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "11" } }
{"title" : "Music Fundamental","Professor" : "Jay Z","major" : "Music","semester" : ["fall"],"student_count" : 100,"unit" : 3,"rating" : 5, "submit_date" : "2016-05-22", "school_location" : {"lat" : 42.51, "lon" : -74.83}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "12" } }
{"title" : "Vocal Techniques","Professor" : "Beyonce","major" : "Music","semester" : ["fall"],"student_count" : 30,"unit" : 3,"rating" : 5, "submit_date" : "2016-11-22", "school_location" : {"lat" : 42.51, "lon" : -74.83}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "13" } }
{"title" : "Guitar Techiniques","Professor" : "Eric Clapton","major" : "Music","semester" : ["spring", "fall"],"student_count" : 20,"unit" : 2,"rating" : 4, "submit_date" : "2016-03-12", "school_location" : {"lat" : 42.51, "lon" : -74.83}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "14" } }
{"title" : "Finance","Professor" : "Bill Gates","major" : "Accounting","semester" : ["winter"],"student_count" : 50,"unit" : 3,"rating" : 2, "submit_date" : "2016-01-12", "school_location" : {"lat" : 42.51, "lon" : -74.83}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "15" } }
{"title" : "Marketing","Professor" : "Bill Gates","major" : "Accounting","semester" : ["spring"],"student_count" : 60,"unit" : 2,"rating" : 3, "submit_date" : "2016-01-22", "school_location" : {"lat" : 42.51, "lon" : -74.83}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "16" } }
{"title" : "Accounting Information Systems","Professor" : "Tom Cruise","major" : "Accounting","semester" : ["fall"],"student_count" : 100,"unit" : 2,"rating" : 1, "submit_date" : "2016-11-12", "school_location" : {"lat" : 42.51, "lon" : -74.83}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "17" } }
{"title" : "Individual Taxation","Professor" : "Tom Cruise","major" : "Accounting","semester" : ["fall"],"student_count" : 30,"unit" : 1,"rating" : 2, "submit_date" : "2016-08-02", "school_location" : {"lat" : 42.32, "lon" : -94.74}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "18" } }
{"title" : "Auditing","Professor" : "Victoria Park","major" : "Accounting","semester" : ["spring", "fall"],"student_count" : 20,"unit" : 2,"rating" : 3, "submit_date" : "2016-09-13", "school_location" : {"lat" : 42.32, "lon" : -94.74}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "19" } }
{"title" : "Cell Biology","Professor" : "Anjella Kim","major" : "Medical","semester" : ["fall"],"student_count" : 40,"unit" : 5,"rating" : 5, "submit_date" : "2016-02-22", "school_location" : {"lat" : 42.32, "lon" : -94.74}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "20" } }
{"title" : "Physiology","Professor" : "Jack Berk","major" : "Medical","semester" : ["summer"],"student_count" : 30,"unit" : 5,"rating" : 4, "submit_date" : "2016-11-12", "school_location" : {"lat" : 32.69, "lon" : -99.44}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "21" } }
{"title" : "Neuroscience","Professor" : "Jihee Yang","major" : "Medical","semester" : ["spring", "fall"],"student_count" : 20,"unit" : 5,"rating" : 4, "submit_date" : "2016-06-03", "school_location" : {"lat" : 32.69, "lon" : -99.44}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "22" } }
{"title" : "Immunology","Professor" : "Meredith Lee","major" : "Medical","semester" : ["winter"],"student_count" : 30,"unit" : 3,"rating" : 2, "submit_date" : "2016-06-21", "school_location" : {"lat" : 32.69, "lon" : -99.44}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "23" } }
{"title" : "Genetics","Professor" : "David Pollack","major" : "Medical","semester" : ["spring"],"student_count" : 20,"unit" : 3,"rating" : 3, "submit_date" : "2016-06-30", "school_location" : {"lat" : 28.22, "lon" : -81.87}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "24" } }
{"title" : "Biochemistry","Professor" : "John Miller","major" : "Medical","semester" : ["fall"],"student_count" : 30,"unit" : 3,"rating" : 4, "submit_date" : "2016-01-11", "school_location" : {"lat" : 28.22, "lon" : -81.87}}
{ "index" : { "_index" : "classes", "_type" : "class", "_id" : "25" } }
{"title" : "Anatomy","Professor" : "Tom Johnson","major" : "Medical","semester" : ["fall"],"student_count" : 30,"unit" : 5,"rating" : 3, "submit_date" : "2016-11-12", "school_location" : {"lat" : 28.22, "lon" : -81.87}}

너무 많은데 하나만 가져와보면 다음 형식과 같습니다.

{ 
  "index" : { 
    "_index" : "classes", 
    "_type" : "class", 
    "_id" : "1" 
  } 
}
{
  "title" : "Machine Learning",
  "Professor" : "Minsuk Heo",
  "major" : "Computer Science",
  "semester" : ["spring", "fall"],
  "student_count" : 100,
  "unit" : 3,
  "rating" : 5, 
  "submit_date" : "2016-01-02", 
  "school_location" : {
    "lat" : 36.00, 
    "lon" : -120.00
  }
}

제가 이해한 것은 classes라는 인덱스에 class 타입으로, id는 1번으로 설정하고 밑의 데이터를 집어 넣겠다는 의미로 이해했습니다. 그럼 벌크 연산을 직접 실행해보죠! 아무 폴더에 json 파일을 저장하고, 다음과 같이 명령어를 실행해봅시다. 저는 dummy.json 파일로 저장해두었어요.

curl -XPOST http://localhost:9200/_bulk?pretty --data-binary @dummy.json -H "Content-Type: application/json"

1번 데이터는 앞에서 많이 사용했기 때문에 10번 데이터 하나만 확인을 해보겠습니다.

// 조회한 데이터
{
    "_index": "classes",
    "_type": "class",
    "_id": "10",
    "_version": 1,
    "_seq_no": 12,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "title": "Computer Graphic",
        "Professor": "Jeniffer Anderson",
        "major": "Computer Science",
        "semester": [
            "spring"
        ],
        "student_count": 60,
        "unit": 2,
        "rating": 3,
        "submit_date": "2016-11-12",
        "school_location": {
            "lat": 39.97,
            "lon": -89.78
        }
    }
}

// dummy.json에서 10번 id
{
  "index": {
    "_index": "classes",
    "_type": "class",
    "_id": "10"
  }
}
{
  "title": "Computer Graphic",
  "Professor": "Jeniffer Anderson",
  "major": "Computer Science",
  "semester": [
    "spring"
  ],
  "student_count": 60,
  "unit": 2,
  "rating": 3,
  "submit_date": "2016-11-12",
  "school_location": {
    "lat": 39.97,
    "lon": -89.78
  }
}

똑같이 삽입된 것을 확인할 수 있어요!

Mapping(Schema)

지금까지 Mapping이라는 개념 없이 데이터를 넣고 조회하고 수정, 삭제를 해보았습니다. 실제 mapping 없이 Elastic Search에서 CRUD를 하면 Kibana가 시각화 할 때, 아주 많은 문제가 발생합니다!

다음의 json을 확인해보면서 mapping 작업을 거치지 않을 경우 어떤 문제가 발생할지 보겠습니다.

{
    "class" : {
        "properties" : {
            "title" : {
                "type" : "string"
            },
            "professor" : {
                "type" : "string"
            },
            "major" : {
                "type" : "string"
            },
            "semester" : {
                "type" : "string"
            },
            "student_count" : {
                "type" : "integer"
            },
            "unit" : {
                "type" : "integer"
            },
            "rating" : {
                "type" : "integer"
            },
            "submit_date" : {
                "type" : "date",
                "format" : "yyyy-MM-dd"
            },
            "school_location" : {
                "type" : "geo_point"
            }
        }
    }
}

RDB에서 컬럼 타입을 지정하는 것과 비슷하다고 볼 수 있겠네요! 만약에 이 작업을 거치지 않았다면 Kibana가 날짜별, 위치별 자료들을 시각화 할 때, 상당히 애먹겠죠? 왜냐하면 Kibana는 mapping 작업을 거치지 않았다면, 이 데이터가 무슨 타입인지 알 수 없기 때문에 시각화 하기 굉장히 힘들어집니다.

mapping 작업을 하기 전에 인덱스를 하나 만들어봅시다. 먼저, 기존에 실습했던 classes 인덱스를 지우고 다시 만들어 볼게요!

curl -XDELETE http://localhost:9200/classes

curl -XPUT http://localhost:9200/classes

잘 만들었는지 조회해보겠습니다.

{
    "classes": {
        "aliases": {},
        "mappings": {},
        "settings": {
            "index": {
                "routing": {
                    "allocation": {
                        "include": {
                            "_tier_preference": "data_content"
                        }
                    }
                },
                "number_of_shards": "1",
                "provided_name": "classes",
                "creation_date": "1624074457188",
                "number_of_replicas": "1",
                "uuid": "theI-_rrQBC02C-xejFcSg",
                "version": {
                    "created": "7130299"
                }
            }
        }
    }
}

여기서 살펴봐야 할 것은 mappings라는 곳입니다. 우리는 현재 인덱스만 생성했지, mapping 작업을 따로 하지 않았기 때문에 아무것도 없다고 나와있습니다.

이제 본격적으로 mapping을 해볼게요! 우선 이 파일명을 classesRating_mapping.json 이라고 저장할게요!

// classesRating_mapping.json 
{
    "class" : {
        "properties" : {
            "title" : {
                "type" : "text"
            },
            "professor" : {
                "type" : "text"
            },
            "major" : {
                "type" : "text"
            },
            "semester" : {
                "type" : "text"
            },
            "student_count" : {
                "type" : "integer"
            },
            "unit" : {
                "type" : "integer"
            },
            "rating" : {
                "type" : "integer"
            },
            "submit_date" : {
                "type" : "date",
                "format" : "yyyy-MM-dd"
            },
            "school_location" : {
                "type" : "geo_point"
            }
        }
    }
}

그 다음 이 json 파일을 class index에 mapping 등록시켜볼게요!

curl -XPUT http://localhost:9200/classes/class/_mapping -d @classesRating_mapping.json -H "Content-Type: application/json"

문제가 발생했습니다..

image

원인은 여기서 찾아볼 수 있었습니다. 저는 도커로 엘라스틱서치를 띄웠고 버전이 7.13.2 버전이어서 발생했던 에러였네요.

그럼 다시 요청을 보내볼게요!

curl -H 'Content-Type:application/json' -XPUT 'http://localhost:9200/classes/class/_mapping?include_type_name=true&pretty' -d @classesRating_mapping.json

다시 잘 mapping 되었는지 확인해볼게요!

{
    "classes": {
        "aliases": {},
        "mappings": {
            "properties": {
                "major": {
                    "type": "text"
                },
                "professor": {
                    "type": "text"
                },
                "rating": {
                    "type": "integer"
                },
                "school_location": {
                    "type": "geo_point"
                },
                "semester": {
                    "type": "text"
                },
                "student_count": {
                    "type": "integer"
                },
                "submit_date": {
                    "type": "date",
                    "format": "yyyy-MM-dd"
                },
                "title": {
                    "type": "text"
                },
                "unit": {
                    "type": "integer"
                }
            }
        },
        "settings": {
            "index": {
                "routing": {
                    "allocation": {
                        "include": {
                            "_tier_preference": "data_content"
                        }
                    }
                },
                "number_of_shards": "1",
                "provided_name": "classes",
                "creation_date": "1624074457188",
                "number_of_replicas": "1",
                "uuid": "theI-_rrQBC02C-xejFcSg",
                "version": {
                    "created": "7130299"
                }
            }
        }
    }
}

mappings에 정상적으로 잘 들어갔습니다!

Search

이제 데이터를 검색해보겠습니다. 먼저 두개의 document로 구성된 simple_basketball.json을 만들어 보겠습니다.

{ "index" : { "_index" : "basketball", "_type" : "record", "_id" : "1" } }
{"team" : "Chicago Bulls","name" : "Michael Jordan", "points" : 30,"rebounds" : 3,"assists" : 4, "submit_date" : "1996-10-11"}
{ "index" : { "_index" : "basketball", "_type" : "record", "_id" : "2" } }
{"team" : "Chicago Bulls","name" : "Michael Jordan","points" : 20,"rebounds" : 5,"assists" : 8, "submit_date" : "1996-10-11"}

그 다음, 벌크로 데이터를 삽입해볼게요

curl -H "Content-Type: application/json" -XPOST localhost:9200/_bulk --data-binary @simple_basketball.json

이제 search를 해보겠습니다!

curl -XGET http://localhost:9200/basketball/record/_search?pretty

그럼 다음과 같은 결과가 나오게 됩니다.

{
    "took": 424,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "basketball",
                "_type": "record",
                "_id": "1",
                "_score": 1.0,
                "_source": {
                    "team": "Chicago Bulls",
                    "name": "Michael Jordan",
                    "points": 30,
                    "rebounds": 3,
                    "assists": 4,
                    "submit_date": "1996-10-11"
                }
            },
            {
                "_index": "basketball",
                "_type": "record",
                "_id": "2",
                "_score": 1.0,
                "_source": {
                    "team": "Chicago Bulls",
                    "name": "Michael Jordan",
                    "points": 20,
                    "rebounds": 5,
                    "assists": 8,
                    "submit_date": "1996-10-11"
                }
            }
        ]
    }
}

hits.hits에 실제 저장된 데이터를 볼 수 있어요!

이제 검색 조건을 설정해볼게요! points 값이 30인 선수를 찾아보겠습니다.

curl -XGET http://localhost:9200/basketball/record/_search?q=points:30&pretty

그럼 다음과 같이 30포인트를 가진 선수만 나오는 것을 볼 수 있어요.

{
    "took": 97,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "basketball",
                "_type": "record",
                "_id": "1",
                "_score": 1.0,
                "_source": {
                    "team": "Chicago Bulls",
                    "name": "Michael Jordan",
                    "points": 30,
                    "rebounds": 3,
                    "assists": 4,
                    "submit_date": "1996-10-11"
                }
            }
        ]
    }
}

Request Body로 직접 조회하는 방법도 있습니다! 다양한 방법을 지원하지만, 지금은 개념을 다루는 포스팅이다보니 대표로 하나만 해보겠습니다.

curl -H "Content-Type: application/json" -XGET http://localhost:9200/basketball/record/_search -d '
{
    "query" : {
        "term" : {"points" : 30}
    }
}'

아까와 같은 결과로 조회가 되는 것을 확인할 수 있을겁니다!

Aggregation

Aggregation은 조합을 통해 어떠한 값을 도출하는 방법이며, 그 중 Metric Aggregation은 산술을 통한 방법이다. 예를 들면, 평균이나 최대, 최소 와 같이 산술을 통해 값들을 도출하는 방법을 의미합니다.

Aggregation의 형식은 밑의 그림과 같습니다!

image

먼저, Aggregation을 정의하고 파일들을 저장해보겠습니다.

// 평균 포인트, avg_points_aggs.json
{
    "size" : 0,
    "aggs" : {
        "avg_score" : {
            "avg" : {
                "field" : "points"
            }
        }
    }
}

// 최대 포인트, max_points_aggs.json
{
    "size" : 0,
    "aggs" : {
        "max_score" : {
            "max" : {
                "field" : "points"
            }
        }
    }
}

// 최소 포인트, min_points_aggs.json
{
    "size" : 0,
    "aggs" : {
        "min_score" : {
            "min" : {
                "field" : "points"
            }
        }
    }
}

// 합계 포인트, sum_points_aggs.json
{
    "size" : 0,
    "aggs" : {
        "sum_score" : {
            "sum" : {
                "field" : "points"
            }
        }
    }
}

// stats (위의 네가지 모두 다), stats_points_aggs.json
{
    "size" : 0,
    "aggs" : {
        "stats_score" : {
            "stats" : {
                "field" : "points"
            }
        }
    }
}

이제 각 aggregation으로 명령들을 실행해보겠습니다.

curl -H "Content-Type: application/json" http://localhost:9200/_search?pretty --data-binary @avg_points_aggs.json

curl -H "Content-Type: application/json" http://localhost:9200/_search?pretty --data-binary @max_points_aggs.json

curl -H "Content-Type: application/json" http://localhost:9200/_search?pretty --data-binary @min_points_aggs.json

curl -H "Content-Type: application/json" http://localhost:9200/_search?pretty --data-binary @sum_points_aggs.json

curl -H "Content-Type: application/json" http://localhost:9200/_search?pretty --data-binary @stats_points_aggs.json

image

image

image

image

image

응답 중에서 확인해야할 부분만 캡쳐했습니다. 정상적으로 잘 동작하는 것을 확인할 수 있습니다.

이번에는 Bucket Aggregation을 알아보겠습니다. Bucket Aggregation은 SQL에서 Group By의 개념과 유사하다고 보면 될 것 같습니다.

우선 앞에서 실습했던 basketball 인덱스에 다음의 데이터를 넣겠습니다. 파일명은 twoteam_basketball.json로 지정할게요. 그 다음, 앞에서 설명드렸던 벌크 insert로 해당 데이터들을 넣어줍시다.

{ "index" : { "_index" : "basketball", "_type" : "record", "_id" : "1" } }
{"team" : "Chicago","name" : "Michael Jordan", "points" : 30,"rebounds" : 3,"assists" : 4, "blocks" : 3, "submit_date" : "1996-10-11"}
{ "index" : { "_index" : "basketball", "_type" : "record", "_id" : "2" } }
{"team" : "Chicago","name" : "Michael Jordan","points" : 20,"rebounds" : 5,"assists" : 8, "blocks" : 4, "submit_date" : "1996-10-13"}
{ "index" : { "_index" : "basketball", "_type" : "record", "_id" : "3" } }
{"team" : "LA","name" : "Kobe Bryant","points" : 30,"rebounds" : 2,"assists" : 8, "blocks" : 5, "submit_date" : "2014-10-13"}
{ "index" : { "_index" : "basketball", "_type" : "record", "_id" : "4" } }
{"team" : "LA","name" : "Kobe Bryant","points" : 40,"rebounds" : 4,"assists" : 8, "blocks" : 6, "submit_date" : "2014-11-13"}
curl -H "Content-Type: application/json" -XPOST localhost:9200/_bulk --data-binary @twoteam_basketball.json

그 다음, terms_aggs.json을 밑에 처럼 작성합시다. 그루핑시킬 조건을 작성한 것이라 이해하면 될 것 같아요. 밑의 json은 팀을 그루핑시키겠다는 의미겠네요!

{
    "size" : 0,
    "aggs" : {
        "players" : {
            "terms" : {
                "field" : "team"
            }
        }
    }
}

이제 curl을 통해 bucket aggregation 명령들을 날려보겠습니다.

curl -H "Content-Type: application/json" -XGET http://localhost:9200/_search?pretty --data-binary @terms_aggs.json

image

위와 같이 팀별로 그루핑 되어 시카고가 두팀, la도 두 팀이 나오는 결과를 확인할 수 있습니다!

조금 더 심화해서 생각해보자.

image

위의 자료를 바탕으로 활용해볼 수 있는 것은 팀별로 성적을 분석할 수 있다는 것이다. 예를 들면, 팀의 평균 득점, 리바운드 수, 어시스트, 블락 수 등등이 있겠죠?

{
    "size" : 0,
    "aggs" : {
        "team_stats" : {
            "terms" : {
                "field" : "team"
            },
            "aggs" : {
                "stats_score" : {
                    "stats" : {
                        "field" : "points"
                    }
                }
            }
        }
    }
}

위의 경우처럼 aggregation을 중첩해서 사용할 수 있는데, 팀별로 그루핑한 다음, metric aggregation을 사용해 점수별로 통계를 내겠다는 의미로 생각하면 될 것 같습니다. 저 json을 stats_by_team.json이라고 파일을 작성한 다음 실행해보면

curl -H "Content-Type: application/json" -XGET http://localhost:9200/_search?pretty --data-binary @stats_by_team.json

image

이처럼 팀별로 그루핑된 다음 점수의 최소, 최대, 평균, 합산의 결과를 볼 수 있습니다!

이번 포스팅은 여기서 마무리하겠습니다. 피드백은 항상 환영해요!

참고

반응형