본문 바로가기

데이터베이스/ELK

[ELK] Elasticsearch 단어 사용 횟수 통계 (버킷 집계 - terms)

0. 들어가며

커뮤니티 프로젝트에서 사용된 단어를 형태소 분석기를 통해 토큰으로 분리한 단어의 사용 빈도 데이터를 추출해야한다.

ES에서 _search API를 이용해서 단어의 통계를 구하려한다.

 

이때 필요한 지식이 버킷 집계이다. 

 

 

1. 버킷 집계

매트릭 집계가 특정 필드를 기준으로 통계값을 계사하려는 목적이라면, 버킷 집계는 특정 기준에 맞춰서 조큐먼트를 그룹핑하는 역할을 한다.

버킷은 도큐먼트가 분할되는 단위로 나뉜 각 그룹을 의미한다.

위 사진처럼 도큐먼트들을 버킷 집계를 통하며 각각의 버킷 집합으로 만든다.

 

버킷 집계 종류

버킷 집계 설명
histogram 숫자 타입 필드를 일정 간격으로 분류한다.
date_histogram 날짜/시간 타입 필드를 일정 날짜/시간 간격으로 분류한다.
range 숫자 타입 필드를 사용자가 지정하는 범위 간격으로 분류한다.
date_rage 날짜/시간 타입 필드를 사용자가 지정하는 날짜/시간 간격으로 분류한다.
terms 필드에 많이 나타나는 용어(값)들을 기준으로 분류한다.
singificant_terms terms 버킷과 유사하나, 모든 값을 대상으로 하지 않고 인덱스 내 전체 문서 대비 현재 검색 조건에서 통계적으로 유의미한 값들을 기준으로 분류한다.
filters 각 그룹에 포함시킬 문서의 조건을 직접 지정한다. 이때 조건은 일반적으로 검색에 사용되는 쿼리와 동일하다.

버킷 집계의 종류가 많지만, 프로젝트에서 당장 필요한 기능은 terms 용어 집계이기에 terms 집계에 대해 설명할 것이다.

 

 

2. terms 집계 (용어 집계)

용어 집계는 필드의 유니크한 값을 기준으로 버킷을 나눌 때 사용한다. 즉, 필드에 많이 나타나는 용어들을 기준으로 분류하는 것이다.

위와 같이 daty_of_week 필드를 유니크하게 집계한 예시이다.

key에는 daty_of_week필드 내의 {월, 화, 수, 목, 금, 토, 일}이 저장되어 있을 것이고, doc_count에는 해당 요일이 얼마나 사용되었는가를 나타낸다고 이해하면 쉬울 것이다.

여기서 size는 6으로 설정해두었는데, TOP6까지 집계 결과를 반환한다는 의미이다. 굳이 안넣어도 됨;;

 

 

3. _search API를 이용한 버킷 집계

먼저 인덱스를 생성해주자.

message라는 key에 title과 content를 하위 key로 두었다. 또한, 한글 형태소 분석기 nori를 적용했다.

PUT elk-test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "nori": {
          "tokenizer": "nori_tokenizer"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "message.title": {
        "type": "text",
        "analyzer": "nori"
      },
      "message.content": {
        "type": "text",
        "analyzer": "nori"
      }
    }
  }
}

 

POST를 이용해서 새로운 도큐먼트를 생성한다.

POST elk-test/_doc/
{
  "message": {
    "Article_id": "a4847333-c581-409c-b9a8-asdasd",
    "title": "동해물과 백두산이",
    "content": "애국가입니다.",
    "Board_id": "532aa49c-c677-4fe6-8a93-asdasdasd",
    "Message": "Article Create"
  }
}

 

세팅은 끝났으니, _search API를 이용해서 버킷 집계를 한다.

GET elk-test/_search
{
  "aggs": {
    "term_aggs": {
      "terms": {
        "field": "message.title"
      }
    }
  }
}

term_aggs라는 이름으로 버킷 집합을 만들고 message에서 title 또는 content을 필드로 설정해서 집계를 구한다.

 

아마 이상태로 하면 에러가 뜰 것이다.

Fielddata is disabled on text fields by default.
Set fielddata=true on [your_field_name] in order to load fielddata in memory by

위와 비슷한 에러가 뜬다...

 

에러가 뜨는 이유는 간단한데, 기본적으로 ES의 검색은 '어떤 문서가 이 키워드를 포함하는지가 궁금'하므로 역인덱스된 정보를 통해 빠른 검색이 가능하게 해준다. 즉, 도큐먼트가 생성될 때 term으로 나누어서 역인덱스 구조를 가지게 된다.

 

그러나 sort, aggregation, accessing field value와 같은 패턴은 '이 문서에서 이 field value값이 무엇인지'가 관심이므로 역인덱스 정보가 아닌 document를 key로, field 정보를 담은 데이터 구조가 필요하다. 즉, 역인덱스가 아닌 일반 인덱스 구조여야 가능하다는 것이다.

결론은 집계하고자 하는 text 필드를 aggregation할 때, fielddata를 true로 설정하면 위의 에러는 해결된다.

 

그러면 다시 인덱스를 생성하자.

delete 명령어를 사용해서 만들어 두었던 인덱스를 삭제하고, 다시 생성한다.

PUT elk-test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "nori": {
          "tokenizer": "nori_tokenizer"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "message.title": {
        "type": "text",
        "analyzer": "nori",
        "fielddata": true
      },
      "message.content": {
        "type": "text",
        "analyzer": "nori",
        "fielddata": true
      }
    }
  }
}

 

인덱스를 생성했으면, 똑같이 도큐먼트를 생성하고 집계 명령어를 실행시키면

{
  "took" : 12,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "elk-test",
        "_type" : "_doc",
        "_id" : "_5QDNYUBSViGEzXdYw6P",
        "_score" : 1.0,
        "_source" : {
          "message" : {
            "Article_id" : "a4847333-c581-409c-b9a8-asdasd",
            "title" : "동해물과 백두산이",
            "content" : "애국가입니다.",
            "Board_id" : "532aa49c-c677-4fe6-8a93-asdasdasd",
            "Message" : "Article Create"
          }
        }
      }
    ]
  },
  "aggregations" : {
    "term_aggs" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "ᄇ니다",
          "doc_count" : 1
        },
        {
          "key" : "가",
          "doc_count" : 1
        },
        {
          "key" : "애국",
          "doc_count" : 1
        },
        {
          "key" : "이",
          "doc_count" : 1
        }
      ]
    }
  }
}

이런식으로 형태소 분석기 nori를 통해 분리된 term 용어의 횟수가 반환된다.

 

물론 해당 용어가 몇개의 도큐먼트에서 사용되었는지에 대한 횟수이긴 하지만... 이거라도 어디야;;

정보를 더 수집해보고 한 도큐먼트 안에서도 동일한 단어가 여러번 사용되었을 때 카운트가 되도록 해봐야겠다.

 

 

 

 

 

 

[참고]

https://flambeeyoga.tistory.com/entry/%EC%97%98%EB%9D%BC%EC%8A%A4%ED%8B%B1%EC%84%9C%EC%B9%98elasticsearch-%EC%A7%91%EA%B3%84

https://wonyong-jang.github.io/elk/2021/07/06/ELK-Elastic-Search-fielddata.html