Exact Count Bitmap

Exact Count Bitmap (정확 카운트 비트맵)

druid-exact-count-bitmap 확장은 Roaring Bitmap을 사용해 LONG 타입 열의 정확한 카디널리티(count)를 계산하는 기능을 제공해요. HyperLogLog 같은 근사 카디널리티 집계기와 달리, 이 집계기는 정확한 고유값 개수를 돌려줘요.

출처: 문서

본문

이 확장은 Roaring Bitmap을 사용해 LONG 타입 열에 대한 정확한 카디널리티 계산 기능을 제공해요. HyperLogLog와 같은 근사 카디널리티 집계기와 달리, 이 집계기는 정확한 distinct count를 제공해요.

Installation

이 Apache Druid 확장을 사용하려면 extensions load list에 druid-exact-count-bitmap을 포함해 주세요.

druid-exact-count-bitmap

Comparison with Similar Aggregations

Distinct Count 집계기는 Exact Count 집계기와 비슷하게 동작해요. 그래서 이 두 집계기의 동작 차이를 이해하는 것이 중요해요.

Exact Count Distinct Count
전제 조건이 필요 없어요 (예: hash partition, segment granularity 설정). 집계를 수행하려면 전제 조건이 필요해요.
64비트 숫자 열(BIGINT)에서만 동작해요. dimension 열에서 동작해요 (String, Complex Type 등 포함).

How it Works

이 확장은 내부 데이터 구조로 Roaring64NavigableMap을 사용해서 64비트 정수의 정확한 카디널리티를 효율적으로 저장하고 계산해요. 서로 다른 목적을 가진 두 타입의 집계기를 제공해요.

Build Aggregator (Bitmap64ExactCountBuild)

BUILD 집계기는 원시 LONG 값에서 직접 카디널리티를 계산하고 싶을 때 사용해요.

  • 수집(ingestion) 시점이나 원시 데이터를 질의할 때 사용해요.
  • LONG 타입 열에만 사용해야 해요.

예시:

{
  "type": "Bitmap64ExactCountBuild",
  "name": "unique_values",
  "fieldName": "id"
}

Merge Aggregator (Bitmap64ExactCountMerge)

MERGE 집계기는 미리 계산된(pre-computed) 비트맵을 다룰 때 사용해요.

  • 미리 집계된 데이터(BUILD로 이전에 집계된 열)를 질의할 때 사용돼요.
  • 비트 연산(bitwise operations)으로 여러 비트맵을 결합해요.
  • BUILD로 집계된 열이나 이전 MERGE로 집계된 열에 사용해야 해요.

Bitmap64ExactCountMerge 집계기는 timeseries 타입 질의에 사용하길 권장해요. topN과 groupBy 질의에서도 동작하긴 해요.

예시:

{
  "type": "Bitmap64ExactCountMerge",
  "name": "total_unique_values",
  "fieldName": "unique_values" // Must be a pre-computed bitmap
}

Typical Workflow

수집 시점에 BUILD로 초기 비트맵을 만들어요.

{
  "type": "index",
  "spec": {
    "dataSchema": {
      "metricsSpec": [
        {
          "type": "Bitmap64ExactCountBuild",
          "name": "unique_users",
          "fieldName": "user_id"
        }
      ]
    }
  }
}

집계된 데이터를 질의할 때는 MERGE로 비트맵을 결합해요.

{
  "queryType": "timeseries",
  "aggregations": [
    {
      "type": "Bitmap64ExactCountMerge",
      "name": "total_unique_users",
      "fieldName": "unique_users"
    }
  ]
}

Usage

SQL Query

SQL 질의에서 BITMAP64_EXACT_COUNT 함수를 사용할 수 있어요.

SELECT BITMAP64_EXACT_COUNT(column_name)
FROM datasource
WHERE ...
GROUP BY ...

Post-Aggregator

추가 처리를 위해 post-aggregator도 사용할 수 있어요.

{
  "type": "bitmap64ExactCount",
  "name": "<output_name>",
  "fieldName": "<aggregator_name>"
}

Considerations

  • 메모리 사용량: Roaring Bitmap은 효율적이지만, 정확한 고유값을 저장하는 것은 일반적으로 HyperLogLog 같은 근사 알고리즘보다 더 많은 메모리를 소비해요.
  • 입력 타입: 이 집계기는 LONG(64비트 정수) 열에서만 동작해요. String이나 다른 데이터 타입은 사용 전에 long으로 변환해야 해요.
  • Build vs Merge: 원시 숫자 데이터에는 항상 BUILD, 미리 집계된 데이터에는 MERGE를 사용해요. 미리 집계된 데이터에 BUILD를, 원시 데이터에 MERGE를 사용하면 올바르게 동작하지 않아요.

Example Use Cases

User Analytics: 시간 경과에 따른 고유 사용자 수

-- First ingest with BUILD aggregator
-- Then query with:
SELECT
  TIME_FLOOR(__time, 'PT1H') AS hour,
  BITMAP64_EXACT_COUNT(unique_users) as distinct_users
FROM user_metrics
GROUP BY 1

High-Precision Metrics: 정확한 count가 필요할 때

{
  "type": "groupBy",
  "dimensions": [
    "country"
  ],
  "aggregations": [
    {
      "type": "Bitmap64ExactCountMerge",
      "name": "exact_user_count",
      "fieldName": "unique_users"
    }
  ]
}

Walkthrough Using Wikipedia datasource

Batch Ingestion Task Spec

{
  "type": "index",
  "spec": {
    "dataSchema": {
      "dataSource": "wikipedia_metrics",
      "timestampSpec": {
        "column": "__time",
        "format": "auto"
      },
      "dimensionsSpec": {
        "dimensions": [
          "channel",
          "namespace",
          "page",
          "user",
          "cityName",
          "countryName",
          "regionName",
          "isRobot",
          "isUnpatrolled",
          "isNew",
          "isAnonymous"
        ]
      },
      "metricsSpec": [
        {
          "type": "Bitmap64ExactCountBuild",
          "name": "unique_added_values",
          "fieldName": "added"
        },
        {
          "type": "Bitmap64ExactCountBuild",
          "name": "unique_delta_values",
          "fieldName": "delta"
        },
        {
          "type": "Bitmap64ExactCountBuild",
          "name": "unique_comment_lengths",
          "fieldName": "commentLength"
        },
        {
          "name": "count",
          "type": "count"
        },
        {
          "name": "sum_added",
          "type": "longSum",
          "fieldName": "added"
        },
        {
          "name": "sum_delta",
          "type": "longSum",
          "fieldName": "delta"
        }
      ],
      "granularitySpec": {
        "type": "uniform",
        "segmentGranularity": "DAY",
        "queryGranularity": "HOUR",
        "rollup": true,
        "intervals": [
          "2016-06-27/2016-06-28"
        ]
      }
    },
    "ioConfig": {
      "type": "index",
      "inputSource": {
        "type": "druid",
        "dataSource": "wikipedia",
        "interval": "2016-06-27/2016-06-28"
      },
      "inputFormat": {
        "type": "tsv",
        "findColumnsFromHeader": true
      }
    },
    "tuningConfig": {
      "type": "index",
      "maxRowsPerSegment": 5000000,
      "maxRowsInMemory": 25000
    }
  }
}

원시 bytes가 있는 datasource 질의

{
  "queryType": "timeseries",
  "dataSource": {
    "type": "table",
    "name": "wikipedia_metrics"
  },
  "intervals": {
    "type": "intervals",
    "intervals": [
      "-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z"
    ]
  },
  "granularity": {
    "type": "all"
  },
  "aggregations": [
    {
      "type": "Bitmap64ExactCountBuild",
      "name": "a0",
      "fieldName": "unique_added_values"
    }
  ]
}

미리 집계된 비트맵이 있는 datasource 질의

{
  "queryType": "timeseries",
  "dataSource": {
    "type": "table",
    "name": "wikipedia_metrics"
  },
  "intervals": {
    "type": "intervals",
    "intervals": [
      "-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z"
    ]
  },
  "granularity": {
    "type": "all"
  },
  "aggregations": [
    {
      "type": "Bitmap64ExactCountMerge",
      "name": "a0",
      "fieldName": "unique_added_values"
    }
  ]
}

Other Examples

Kafka ingestion task spec

{
  "type": "kafka",
  "spec": {
    "dataSchema": {
      "dataSource": "ticker_event_bitmap64_exact_count_rollup",
      "timestampSpec": {
        "column": "timestamp",
        "format": "millis",
        "missingValue": null
      },
      "dimensionsSpec": {
        "dimensions": [
          {
            "type": "string",
            "name": "key"
          }
        ],
        "dimensionExclusions": []
      },
      "metricsSpec": [
        {
          "type": "Bitmap64ExactCountBuild",
          "name": "count",
          "fieldName": "value"
        }
      ],
      "granularitySpec": {
        "type": "uniform",
        "segmentGranularity": "HOUR",
        "queryGranularity": "HOUR",
        "rollup": true,
        "intervals": null
      },
      "transformSpec": {
        "filter": null,
        "transforms": []
      }
    },
    "ioConfig": {
      "topic": "ticker_event",
      "inputFormat": {
        "type": "json",
        "flattenSpec": {
          "useFieldDiscovery": true,
          "fields": []
        },
        "featureSpec": {}
      },
      "replicas": 1,
      "taskCount": 1,
      "taskDuration": "PT3600S",
      "consumerProperties": {
        "bootstrap.servers": "localhost:9092"
      },
      "pollTimeout": 100,
      "startDelay": "PT5S",
      "period": "PT30S",
      "useEarliestOffset": false,
      "completionTimeout": "PT1800S",
      "lateMessageRejectionPeriod": null,
      "earlyMessageRejectionPeriod": null,
      "lateMessageRejectionStartDateTime": null,
      "stream": "ticker_event",
      "useEarliestSequenceNumber": false,
      "type": "kafka"
    }
  }
}

Post-aggregator 질의

{
  "queryType": "timeseries",
  "dataSource": {
    "type": "table",
    "name": "ticker_event_bitmap64_exact_count_rollup"
  },
  "intervals": {
    "type": "intervals",
    "intervals": [
      "2020-09-13T06:35:35.000Z/146140482-04-24T15:36:27.903Z"
    ]
  },
  "descending": false,
  "virtualColumns": [],
  "filter": null,
  "granularity": {
    "type": "all"
  },
  "aggregations": [
    {
      "type": "count",
      "name": "cnt"
    },
    {
      "type": "Bitmap64ExactCountMerge",
      "name": "a0",
      "fieldName": "count"
    }
  ],
  "postAggregations": [
    {
      "type": "arithmetic",
      "fn": "/",
      "fields": [
        {
          "type": "bitmap64ExactCount",
          "name": "a0",
          "fieldName": "a0"
        },
        {
          "type": "fieldAccess",
          "name": "cnt",
          "fieldName": "cnt"
        }
      ],
      "name": "rollup_rate"
    }
  ],
  "limit": 2147483647
}

더 알아보기 (Learn more)