중복 제거 (DISTINCT Count) 집계 (cardinality vs scripted_metric)

이현우·2023년 11월 1일
0

엘라스틱서치

목록 보기
1/2

현재 사내 트래픽 데이터 추출 서비스에서 빠른 집계와 검색을 위해 사용자의 로그를 하나하나 문서화하여 ElasticSearch를 사용하고 있습니다.

ElasticSearch를 사용하여 집계된 데이터를 전송해주는 백엔드 API 까지 개발에 들어간 상태인데, 여기서 문제점이 생겼습니다.

데이터에서 중복 제거 된 수치를 집계하여 보여줘야 하는데, ElasticSearch에서 흔히 사용하는 cardinality는 정확한 집계를 보여주지 않습니다.

저는 정확한 데이터를 보내줘야 하기 때문에 성능이 떨어지는 단점이 있지만 scripted_metric 사용해 보고자 합니다.

이를 위해 제가 사용한 집계 쿼리만 따로 보면 다음과 같습니다.

"aggs": {
  "distinct count": {
    "scripted_metric": {
      "init_script": "state.docs=new HashSet()",
      "map_script": """
        state.docs.add(doc['field'].value)
      """,
      "combine_script": "return state.docs;",
      "reduce_script": """
        int count = 0;
        for (s in states) {
          count += s.size();
        }
        return count;
      """
    }
  }
}

쿼리문을 분석하면 다음과 같습니다.

  • distinct count라고 쓰인 부분은 집계 이름이니 원하는 이름을 쓰면 됩니다.
  • init_script 부분에 state.docs에 자동으로 중복 제거를 해주는 HashSet을 선언했습니다.
  • state.docs.add(doc['field'].value) 에서 'field' 부분에 중복 제거를 원하는 필드명을 넣으면 됩니다.
  • reduce_script에선 반복문을 통해 HashSet의 size를 더하여 return하는 형식으로 마무리 하였습니다.

주의 할 점으로 검색한 인덱스 별 중복 제거의 합 혹은 검색한 모든 인덱스에서의 중복 제거에 따라 방식이 달라집니다.
states는 현재 인덱스 별 중복 제거된 HashSet의 집합이라고 볼 수 있습니다.

  • 예를 들어 여러 문서를 가진 a, b, c라는 인덱스가 있고, 포함된 문서에 sample_field라는 필드가 있을 때, 위의 쿼리문은 (a에서 중복 제거한 sample_field의 count + b에서 중복 제거한 sample_field의 count + c에서 중복 제거한 sample_field의 count)가 됩니다.
  • 저의 경우 인덱스가 일자별 정리이고 독립적으로 보기 때문에 일자별로 중복 제거한 카운트를 더하는 형식이 필요했습니다.
  • 만약 a, b, c 전체에서 중복 제거한 카운트를 원할 경우 새로운 HashSet에 요소를 담은 뒤 size()를 반환하는 방법 등 추가 로직이 필요합니다.

또한 해당 필드의 정보를 뽑고 싶다면, 아래와 같이 새로운 HashSet(모든 인덱스 상관없이 중복 제거를 원할 경우) 혹은 List(인덱스 별로 중복제거를 원할 경우)에 넣어서 반환하시면 됩니다.

"aggs": {
  "distinct count": {
    "scripted_metric": {
      "init_script": "state.docs=new HashSet()",
      "map_script": """
        state.docs.add(doc['field'].value)
      """,
      "combine_script": "return state.docs;",
      "reduce_script": """
        def all_docs = new HashSet();
        for (s in states) {
          all_docs.addAll(s);
        }
        return all_docs;
      """
    }
  }
}
profile
GitHub - https://github.com/jenu8628

0개의 댓글