COUNT DISTINCT 用ビットマップ
ビットマップを使用して、異なる値の数を計算する。
ビットマップは、配列内の異なる値の数を計算するための便利なツールです。この方法は、従来の Count Distinct と比較して、ストレージスペースを節約し、計算を高速化できます。配列 A が [0, n) の範囲の値を持つと仮定します。(n+7)/8 バイトのビットマップを使用することで、配列内の異なる要素の数を計算できます。これを行うには、すべてのビットを 0 に初期化し、要素の値をビットの添字として設定し、すべてのビットを 1 に設定します。ビットマップ内の 1 の数が、配列内の異なる要素の数です。
従来の Count Distinct
データベースのMPPアーキテクチャは、Count Distinctを使用する際にも詳細なデータを保持できます。しかし、Count Distinct 機能はクエリ処理中に複数のデータシャッフルを必要とし、これによりリソースが多く消費され、データ量が増加するにつれてパフォーマンスが線形に低下します。
以下のシナリオでは、テーブル (dt, page, user_id) の詳細データに基づいて UV を計算します。
| dt | page | user_id |
|---|---|---|
| 20191206 | game | 101 |
| 20191206 | shopping | 102 |
| 20191206 | game | 101 |
| 20191206 | shopping | 101 |
| 20191206 | game | 101 |
| 20191206 | shopping | 101 |
データは以下の図に従って計算される。まず、page と user_id 列でデータをグループ化し、次に処理された結果をカウントします。

図は、2 つの BE ノードで計算された 6 行のデータの概略を示しています。
大量のデータを扱う際に複数のシャッフル操作が必要な場合、計算リソースが大幅に増加する可能性があります。これによりクエリが遅くなります。しかし、ビットマップ技術を使用することで、この問題に対処し、クエリパフォーマンスを向上させることができます。
page でグループ化して uv をカウントします:
select page, count(distinct user_id) as uv from table group by page;
| page | uv |
|---|---|
| game | 1 |
| shopping | 2 |
ビットマップを使用した Count Distinct の利点
COUNT(DISTINCT expr) と比較して、ビットマップを使用することで次のような利点があります:
- ストレージスペースの削減: INT32 データの異なる値の数をビットマップで計算する場合、必要なストレージスペースは
COUNT(DISTINCT expr)の 1/32 に過ぎません。圧縮された Roaring Bitmap は計算処理に使用され、従来のビットマップと比較してさらにストレージ使用量を削減する。 - 高速な計算: ビットマップはビット演算を使用するため、
COUNT(DISTINCT expr)と比較して計算が高速です。異なる値の数の計算は並列処理が可能であり、これによりクエリのパフォーマンスがさらに向上する。
Roaring Bitmap の実装については、specific paper and implementation を参照してください。
使用上の注意
- ビットマップインデックスとビットマップ Count Distinct はどちらもビットマップ技術を使用しますが、それらを導入する目的と解決する問題は完全に異なります。前者は低基数の列をフィルタリングするために使用され、後者はデータ行の値列における異なる要素の数を計算するために使用されます。
- テーブルの種類 (集計テーブル、重複キーテーブル、主キーテーブル、ユニークキーテーブル) にかかわらず、値列を
BITMAPとして定義できます。ただし、テーブルの sort key はBITMAPタイプにすることはできません。 - テーブルを作成する際に、値列を
BITMAPとして定義し、集計関数をBITMAP_UNIONとすることができます。 TINYINT、SMALLINT、INT、およびBIGINTのデータ型に対してのみ、Roaring ビットマップを使用して異なる値の数を計算できます。他のデータ型の場合は、グローバル辞書を構築 する必要があります。