본문 바로가기

DevOps/Elastic Stack

[Elasticsearch] Cardinality 집계, 유니크한 값의 수 - 컴도리돌이

728x90
728x90

SQL에서 데이터를 집계할 때, GROUP BY 구문을 자주 사용하여 특정 필드에 대해 유니크한 값의 수나 그룹별 집계를 수행합니다. 예를 들어, 특정 칼럼의 유니크한 값을 계산하거나, 고유한 사용자 수를 구하는 등의 작업을 쉽게 처리할 수 있습니다. 하지만 Elasticsearch에서 이러한 작업을 어떻게 처리할까요? Elasticsearch는 분산형 시스템으로, 데이터를 빠르게 검색하고 집계할 수 있는 강력한 기능을 제공하지만, SQL과는 다른 방식으로 데이터를 처리합니다. 

 

Elasticsearch에서 Cardinality 집계는 특정 필드의 유니크한 값의 수를 계산하는 집계 방식입니다. 예를 들어, 사용자가 방문한 사이트의 고유한 페이지 수를 계산하거나, 상품의 고유한 카테고리 수를 구하는 등 유용하게 사용됩니다.


Cardinality 집계 사용 예시

Elasticsearch에서 Cardinality 집계를 사용하기 위해서는 다음과 같은 방식으로 집계를 설정합니다. 주로 특정 필드에서 유니크한 값의 개수를 구할 때 사용합니다. 다음은 임의로 재구성한 데이터입니다.

[
  { "user_id": 1, "product_id": "A" },
  { "user_id": 2, "product_id": "B" },
  { "user_id": 3, "product_id": "A" },
  { "user_id": 4, "product_id": "C" },
  { "user_id": 5, "product_id": "A" }
]

 

위 데이터에서, 우리는 product_id 필드의 유니크한 개수를 구하고 싶습니다. SQL로는 COUNT(DISTINCT product_id)와 같은 쿼리를 사용할 수 있습니다. 이제 Elasticsearch에서는 이를 어떻게 처리할까요? 🤔

{
  "size": 0,
  "aggs": {
    "unique_product_count": {
      "cardinality": {
        "field": "product_id.keyword"
      }
    }
  }
}

 

이 쿼리는 product_id.keyword 필드에 대해 Cardinality 집계를 수행하여, 유니크한 상품 ID의 개수를 계산합니다. size: 0을 설정하여 결과로 문서가 반환되지 않도록 하고, 오직 집계 결과만 반환됩니다.

{
  "aggregations": {
    "unique_product_count": {
      "value": 3
    }
  }
}

 

이 결과는 product_id 필드에서 유니크한 값이 3개(A, B, C) 임을 알려줍니다. SQL의 COUNT(DISTINCT product_id)와 동일한 결과를 반환하지만, Elasticsearch는 빠르고 효율적으로 이 값을 계산합니다.


HLL 알고리즘의 특징과 precision_threshold의 영향

HyperLogLog(HLL) 알고리즘은 Elasticsearch에서 Cardinality 집계를 수행할 때 유니크 값의 개수를 근사적으로 계산하는 데 사용됩니다. 이 알고리즘은 정확도와 성능을 조절할 수 있는 유연성을 제공하며, precision_threshold 값을 설정하여 정확도를 높일 수 있지만, 이로 인해 메모리 사용량이 증가하는 특성을 가집니다.

{
  "size": 0,
  "aggs": {
    "unique_product_count": {
      "cardinality": {
        "field": "product_id.keyword",
        "precision_threshold": 10000
      }
    }
  }
}

 

위와 같이 precision_threshold 값을 설정하면, 정확도가 높아지지만 성능은 약간 희생될 수 있습니다. 실제 사용 시에는 성능과 정확도 사이에서 적절한 균형을 맞추는 것이 중요합니다.

정확도(precision) 설정

precision_threshold 값은 정확도를 제어하는 중요한 매개변수입니다. 이 값은 0부터 40,000까지 설정할 수 있으며, 기본적으로 Elasticsearch는 성능을 우선시하므로 정확도는 약간 떨어지지만, 속도와 메모리 효율성을 고려한 결과를 제공합니다. precision_threshold 값을 높이면 정확도가 높아지지만 메모리 사용량도 비례해서 증가합니다.

정확도를 높이면 더 많은 비트로 계산을 하게 되어, 유니크 값의 정확한 근사값을 제공할 수 있습니다. 하지만, 이로 인해 메모리 사용량이 늘어나게 됩니다. 예를 들어, 정확도를 높일수록 Elasticsearch는 8바이트의 메모리를 추가로 사용하게 되며, 이는 precision_threshold에 비례합니다.

메모리 사용량 = precision_threshold * 8 bytes

 

따라서 높은 정확도 설정은 더 많은 메모리와 시간을 소모할 수 있기 때문에, 성능과 메모리 사용 간의 균형을 잘 맞추는 것이 중요합니다. 😅

Cardinality가 낮은 집합일수록 정확도가 높다

Cardinality가 낮은 집합에서는 유니크한 값들이 적기 때문에, 정확도가 자연스럽게 높아집니다. 예를 들어, 고유한 값들이 적은 컬럼에서는 높은 precision_threshold 값을 설정하지 않아도 충분히 높은 정확도를 얻을 수 있습니다. 반면, Cardinality가 높은 집합에서는 정확도를 높이려면 precision_threshold 값을 더 크게 설정해야 하며, 이로 인해 메모리 사용량이 급격하게 증가할 수 있습니다.

성능 향상을 위한 방안

  1. Hashing 필드 정의: Cardinality 집계를 수행할 필드를 hash 방식으로 정의하면 성능을 향상할 수 있습니다. 이는 특히 Cardinality가 큰 필드String 필드에서 효과적입니다. 예를 들어, 고유한 값들이 많은 텍스트 필드를 처리할 때 해시 값을 사용하면, 계산량을 줄이고 성능을 개선할 수 있습니다. 

다음은 precision_threshold 값에 따른 에러율을 시각적으로 보여주는 그래프입니다.

 

 

이 그래프에서는 precision_threshold 값을 높일수록 에러율이 감소하는 경향을 보여줍니다. 정확도가 높아지면, 에러율이 낮아지지만 메모리 사용량이 비례하여 증가함을 알 수 있습니다. 이 점을 유의하며 적절한 precision_threshold 값을 설정해야 합니다.

 

Cardinality Aggregation | Elasticsearch Guide [1.6] | Elastic

Pre-computing hashes is usually only useful on very large and/or high-cardinality fields as it saves CPU and memory. However, on numeric fields, hashing is very fast and storing the original values requires as much or less memory than storing the hashes. T

www.elastic.co

 

Elasticsearch의 Cardinality 집계에서 정확도를 높이기 위해 precision_threshold 값을 조정할 수 있습니다. 하지만 높은 정확도를 설정할수록 메모리 사용량이 증가하고 성능에 영향을 미칠 수 있습니다. Cardinality가 낮은 집합에서는 높은 정확도를 설정하지 않아도 충분히 좋은 결과를 얻을 수 있으며, hashing을 사용하여 성능을 더욱 향상할 수 있습니다. precision_threshold 값을 조정하면서 성능과 정확도 간의 균형을 맞추는 것이 핵심입니다.


자바 코드로 Cardinality 집계 사용하기

 SearchRequest searchRequest = new SearchRequest.Builder()
                    .index("products_index")
                    .size(0)
                    .aggregations("products",
                            a -> a.cardinality(
                                    t -> t.field("product_id.keyword")
                            )
                    )
                    .build();

 

위 코드는 Elasticsearch에서 product_id.keyword 필드에 대해 Cardinality 집계를 실행하고, 유니크한 상품의 개수를 출력합니다.

 

대규모 데이터셋에서 SQL의 COUNT(DISTINCT)와 동일한 작업을 빠르게 처리할 수 있으며, HyperLogLog 알고리즘을 사용하여 성능과 정확도 간의 균형을 맞추는 방식으로 최적화됩니다. 성능이 중요한 대규모 데이터 환경에서 유용하게 활용할 수 있습니다.


728x90
728x90