データセットの行数が数億に達すると、COUNT(DISTINCT ...) の実行速度が低下します。すべてのノードから得られた相違値が、最終的なマージ処理のために単一の最上位ノードにシャッフルされるためです。このボトルネックは、データ量およびビットマップサイズの増加に伴って悪化します。ApsaraDB for SelectDB では、直交ビットマップを採用することでこの課題を解決します。すなわち、データをバケットに分割し、各ノードが自身のインターセクションまたはユニオンを独立して計算できるようにすることで、最上位ノードへ送信されるのは縮小された結果のみとなります。
直交ビットマップの仕組み
標準的な COUNT(DISTINCT ...) は、2 段階で実行されます:
スキャン (ノード 1) スキャン (ノード 2)
+---------+---------+ +---------+---------+
| user_id | tag | | user_id | tag |
+---------+---------+ +---------+---------+
| 1 | A | | 3 | A |
| 2 | B | | 4 | C |
+---------+---------+ +---------+---------+
| |
+----------+ +-----------+
| |
最上位ノード
全ノードからのすべての相違値を受信し、
マージを実行(ボトルネック:I/O + 単一ノードでの処理)ビットマップのサイズが 1 GB を超えると、この最終マージステップがネットワーク I/O および単一ノードにおける処理負荷という 2 つの観点からボトルネックとなります。
ApsaraDB for SelectDB では、hid(ハッシュ ID)列を活用してこのボトルネックを回避します。テーブル作成時に、ユーザー ID に対してハッシュアルゴリズムを適用し、バケットに割り当てます。異なるバケット内のビットマップ値は 直交(重複なし)であるため、各バケットが独自の結果を独立して計算できます:
ステージ 1(分散処理):各バケットがインターセクション/ユニオンをローカルで計算
ステージ 2(マージ):最上位ノードが各バケットの結果を統合(小規模データのみ)これにより、単一ノードによるボトルネックが解消され、数十億行規模へのスケーリングが可能になります。
制限事項
開始前に、以下の制約事項をご確認ください:
集約キー(Aggregate Key)モデルが必要: ビットマップ列は、集約キー(Aggregate Key)モデルを使用したテーブル内に配置する必要があります。また、集約関数として
BITMAP_UNIONを指定する必要があります。パーティションテーブルはサポート対象外: 直交ビットマップ関数は、パーティションテーブルでは使用できません。パーティション境界がバケット間の直交性保証を損なうため、結果が信頼できなくなります。
`hid` 列のカーディナリティ要件:
hid列のカーディナリティは、バケット数の少なくとも 5 倍以上である必要があります。これにより、ハッシュバケット化後のデータ分散が均等になることが保証されます。
ビットマップ重複排除の設定
ステップ 1:テーブルの作成
集約キー(Aggregate Key)モデルを用いてテーブルを作成し、ビットマップ型の Value フィールドとハッシュバケット化用の hid 列を定義します。
CREATE TABLE `user_tag_bitmap` (
`tag` BIGINT(20) NULL COMMENT "ユーザー タグ",
`hid` SMALLINT(6) NULL COMMENT "バケット ID",
`user_id` BITMAP BITMAP_UNION NULL COMMENT "ユーザー ビットマップ"
) ENGINE=OLAP
AGGREGATE KEY(`tag`, `hid`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`hid`) BUCKETS 3;hid 列は、user_id の範囲をグループに分割し、各グループを単一のバケットに割り当てます。これにより、バケット間のビットマップ値が直交となることが保証されます。
hid 列のカーディナリティは、バケット数の少なくとも 5 倍以上に設定してください。たとえば、バケット数が 3 の場合、hid 列には最低でも 15 種類の相違値が必要です。
ステップ 2:データのインポート
LOAD LABEL 文を用いてデータをロードします。ceil(tmp_user_id/500) 式により各行の hid 値を算出し、to_bitmap(tmp_user_id) 関数により整数値をビットマップエントリに変換します。
LOAD LABEL user_tag_bitmap_test
(
DATA INFILE('hdfs://abc')
INTO TABLE user_tag_bitmap
COLUMNS TERMINATED BY ','
(tmp_tag, tmp_user_id)
SET (
tag = tmp_tag,
hid = ceil(tmp_user_id/500),
user_id = to_bitmap(tmp_user_id)
)
)除数 500 は固定値ではありません。予想されるユーザー ID の範囲および目標とする hid の相違値数に応じて調整してください。
ソースデータの形式は、カンマ区切りの 2 列(タグ、ユーザー ID)です:
11111111,1
11111112,2
11111113,3
11111114,4インポート処理中、SelectDB は [1, 5000000) の範囲にあるユーザー ID に同一の hid 値を割り当て、同一バケットに配置します。これにより、バケット間のビットマップ値は引き続き直交状態を維持し、インターセクションおよびユニオン操作の計算コストが削減されます。
ステップ 3:直交ビットマップ関数を用いたクエリ実行
データをインポートした後、次のセクションで説明する直交ビットマップ関数を使用します。標準ビットマップ関数の一覧については、「SQL リファレンス」をご参照ください。
直交ビットマップ関数
以下の 5 つの関数は、直交シナリオにおけるビットマップクエリをサポートします。カウントを返す関数は、対応する COUNT(DISTINCT ...) パターンの直接的な代替となりますが、分散処理方式で実行されます。
| 関数 | 戻り値 | 使用タイミング |
|---|---|---|
bitmap_orthogonal_intersect | ビットマップ | タグ間のインターセクション ビットマップを計算する場合 |
orthogonal_bitmap_intersect_count | カウント | 複数のタグに共通して存在するユーザー数をカウントする場合 |
orthogonal_bitmap_union_count | カウント | タグ全体にわたる固有ユーザー総数をカウントする場合 |
orthogonal_bitmap_expr_calculate | ビットマップ | 複雑な集合演算式(AND、OR、NOT)を評価する場合 |
orthogonal_bitmap_expr_calculate_count | カウント | 複雑な集合演算式の結果をカウントする場合 |
直交ビットマップ関数は、パーティションテーブルでは使用できません。パーティション同士は互いに直交であるため、パーティション間のデータは直交性が保証されず、結果が信頼できなくなります。
bitmap_orthogonal_intersect
指定されたフィルター値にわたるビットマップ値のインターセクションを計算し、その結果をビットマップとして返します。
構文
bitmap_orthogonal_intersect(bitmap_column, column_to_filter, filter_values)パラメーター
| パラメーター | 説明 |
|---|---|
bitmap_column | 集約対象のビットマップ列。 |
column_to_filter | フィルター処理に使用するディメンション列。 |
filter_values | ディメンション列をフィルターするための可変長の値リスト。 |
例
SELECT BITMAP_COUNT(bitmap_orthogonal_intersect(user_id, tag, 13080800, 11110200))
FROM user_tag_bitmap
WHERE tag IN (13080800, 11110200);以下と同等です:
-- 指定されたすべてのタグに出現するユーザー数を返す
SELECT COUNT(DISTINCT user_id)
FROM user_tag_bitmap
WHERE tag IN (13080800, 11110200)
AND <user_id がすべてのタグに出現>;実行時、ステージ 1 では指定されたタグで行をフィルターし、各バケット内で該当タグのビットマップインターセクションを計算します。ステージ 2 では、各バケットのインターセクションビットマップを反復的にマージして最終結果を得ます。
orthogonal_bitmap_intersect_count
ビットマップ値のインターセクションを計算し、そのカウントを直接返します。intersect_count とセマンティクスは一致しますが、実行はバケット間で分散されます。構文は `intersect_count` 関数と同じですが、実装が異なります。構文は `bitmap_union_count` 関数と同じですが、実装が異なります。
構文
orthogonal_bitmap_intersect_count(bitmap_column, column_to_filter, filter_values)パラメーター
| パラメーター | 説明 |
|---|---|
bitmap_column | 集約対象のビットマップ列。 |
column_to_filter | フィルター処理に使用するディメンション列。 |
filter_values | ディメンション列をフィルターするための可変長の値リスト。 |
例
SELECT orthogonal_bitmap_intersect_count(user_id, tag, 1150000, 1150001, 390006)
FROM user_tag_bitmap
WHERE tag IN (1150000, 1150001, 390006);実行時、ステージ 1 では指定されたタグでフィルターし、該当タグのビットマップインターセクションを計算した後、各バケットごとにそのカウントを算出します。ステージ 2 では、各バケットのカウントを合計して最終的な合計値を得ます。
orthogonal_bitmap_union_count
ビットマップ値のユニオンを計算し、そのカウントを返します。BITMAP_UNION_COUNT とセマンティクスは一致しますが、実行はバケット間で分散されます。
構文
orthogonal_bitmap_union_count(bitmap_column)パラメーター
| パラメーター | 説明 |
|---|---|
bitmap_column | ユニオンおよびカウント対象のビットマップ列。 |
例
SELECT orthogonal_bitmap_union_count(user_id)
FROM user_tag_bitmap
WHERE tag IN (1150000, 1150001, 390006);以下と同等です:
SELECT COUNT(DISTINCT user_id)
FROM user_tag_bitmap
WHERE tag IN (1150000, 1150001, 390006);実行時、ステージ 1 では各バケットごとにビットマップユニオンを計算し、そのカウントを算出します。ステージ 2 では、各バケットのカウントを合計して最終的な合計値を得ます。
orthogonal_bitmap_expr_calculate
ビットマップ列にわたる集合演算式を評価し、その結果をビットマップとして返します。カウントではなく、ビットマップ自体を後続処理で利用する必要がある場合に使用します。
構文
orthogonal_bitmap_expr_calculate(bitmap_column, filter_column, input_string)パラメーター
| パラメーター | 説明 |
|---|---|
bitmap_column | 集約対象のビットマップ列。 |
filter_column | フィルター処理に使用するディメンション列(計算のキー列)。 |
input_string | キー列の値に対するセット式文字列。 |
input_string では、以下の演算子がサポートされます:
| 演算子 | 操作 |
|---|---|
& | インターセクション |
| | ユニオン |
- | 差分 |
^ | 排他的 XOR |
\ | エスケープ文字 |
例
SELECT orthogonal_bitmap_expr_calculate(
user_id,
tag,
'(833736|999777)&(1308083|231207)&(1000|20000-30000)'
)
FROM user_tag_bitmap
WHERE tag IN (833736, 999777, 130808, 231207, 1000, 20000, 30000);実行時、ステージ 1 では input_string を解析してタグフィルターを特定し、データをフィルターした上で、各バケット内のフィルター済み行にビットマップ演算式を適用します。ステージ 2 では、各バケットのビットマップ結果をユニオンし、最終的なビットマップを返します。
orthogonal_bitmap_expr_calculate_count
ビットマップ列にわたる集合演算式を評価し、そのカウントを返します。構文およびパラメーターは orthogonal_bitmap_expr_calculate と同一です。
構文
orthogonal_bitmap_expr_calculate_count(bitmap_column, filter_column, input_string)パラメーター
| パラメーター | 説明 |
|---|---|
bitmap_column | 集約対象のビットマップ列。 |
filter_column | フィルター処理に使用するディメンション列(計算のキー列)。 |
input_string | キー列の値に対する集合演算式文字列。演算子 &、|、-、^、\ をサポートします。 |
例
SELECT orthogonal_bitmap_expr_calculate_count(
user_id,
tag,
'(833736|999777)&(1308083|231207)&(1000|20000-30000)'
)
FROM user_tag_bitmap
WHERE tag IN (833736, 999777, 130808, 231207, 1000, 20000, 30000);実行時、ステージ 1 では input_string を解析し、データをフィルターした上でビットマップ演算式を適用し、各バケットごとにそのカウントを算出します。ステージ 2 では、各バケットのビットマップ結果をユニオンし、最終的なカウントを返します。
次のステップ
SQL リファレンス — ビットマップ関数セクションにあるビットマップ関数の完全な一覧