さまざまなビジネスシナリオで ApsaraDB for SelectDB を使用する場合、ApsaraDB for SelectDB でサポートされているデータモデルを理解する必要があります。これは、ビジネスのソリューションを設計するのに役立ちます。このトピックでは、最適なストレージソリューションを設計するのに役立つ、ApsaraDB for SelectDB でサポートされているデータモデルについて説明します。
背景情報
ApsaraDB for SelectDB では、データは論理的にテーブルで記述されます。テーブルは行と列で構成されます。行はテーブル内のデータの行を示します。列は、行のフィールドを記述するために使用されます。
列は、キー列と値列の 2 つのタイプに分けることができます。ビジネスの観点からは、キー列はディメンション列に対応し、値列はメトリック列に対応します。キー列と値列は、CREATE TABLE ステートメントで指定されます。CREATE TABLE ステートメントのキーワード DUPLICATE KEY、AGGREGATE KEY、および UNIQUE KEY の後の列はキー列で、その他の列は値列です。
上記のキーワードは、ApsaraDB for SelectDB でサポートされている次の 3 つのデータモデルに対応しています。
集約キー モデル
ユニークキーモデル
複製キー モデル
集約キーモデル
ApsaraDB for SelectDB は、テーブルの作成に選択したデータモデルに基づいて、テーブルのキー列に同じデータを含む行を処理します。集約キーモデルは、キー列に同じデータを含むすべての行を集約します。これらの行の値列のデータは、CREATE TABLE ステートメントで各値列に指定された集約タイプに基づいて事前に集約されます。集約後、データの 1 行のみが保持されます。
このように、集約キーモデルは行を事前に集約して、スキャンされるデータ量とクエリに必要なコンピューティングリソースの量を削減します。このモデルは、レポートベースの統計分析シナリオに適しています。ただし、このモデルは、COUNT(*) 関数を含むクエリである COUNT(*) クエリには最適ではありません。さらに、各値列に集約タイプが指定されています。他のタイプの集約クエリを実行する場合は、クエリステートメントのセマンティックな正確性を考慮する必要があります。
次の例では、集約キーモデルについて説明し、集約キーモデルの使用方法を示します。
例 1: 集約するデータのインポート
example_tbl1 テーブルのスキーマを次の表に示します。
列名 | タイプ | 集約タイプ | コメント |
user_id | LARGEINT | N/A | ユーザー ID。 |
date | DATE | N/A | データがテーブルに書き込まれた日付。 |
city | VARCHAR(20) | N/A | ユーザーが住んでいる都市。 |
age | SMALLINT | N/A | ユーザーの年齢。 |
sex | TINYINT | N/A | ユーザーの性別。 |
last_visit_date | DATETIME | REPLACE | ユーザーが最後にアクセスした日時。 |
cost | BIGINT | SUM | ユーザーが費やした金額。 |
max_dwell_time | INT | MAX | ユーザーの最大滞在時間。 |
min_dwell_time | INT | MIN | ユーザーの最小滞在時間。 |
次のサンプルコードは、example_tbl1 テーブルを作成する方法の例を示しています。パーティションおよび分散情報は省略されています。
CREATE TABLE IF NOT EXISTS test.example_tbl1
(
`user_id` LARGEINT NOT NULL COMMENT "ユーザー ID", /* The user ID */
`date` DATE NOT NULL COMMENT "データがテーブルに書き込まれた日付", /* The date on which data is written to the table */
`city` VARCHAR(20) COMMENT "ユーザーが住んでいる都市", /* The city in which the user resides */
`age` SMALLINT COMMENT "ユーザーの年齢", /* The age of the user */
`sex` TINYINT COMMENT "ユーザーの性別", /* The gender of the user */
`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "ユーザーが最後にアクセスした日時", /* The last time when the user paid a visit */
`cost` BIGINT SUM DEFAULT "0" COMMENT "ユーザーが費やした金額", /* The amount of money that the user spends */
`max_dwell_time` INT MAX DEFAULT "0" COMMENT "ユーザーの最大滞在時間", /* The maximum dwell time of the user */
`min_dwell_time` INT MIN DEFAULT "99999" COMMENT "ユーザーの最小滞在時間" /* The minimum dwell time of the user */
)
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;上記のテーブルは、ユーザー情報とアクセス情報を記録する典型的なファクトテーブルです。スターモデルでは、ユーザー情報はディメンションテーブルに格納され、アクセス情報はファクトテーブルに格納されます。この例では、説明を簡単にするために、ユーザー情報とアクセス情報は同じテーブルに格納されています。
テーブルの列は、集約タイプが指定されているかどうかに基づいて、キー列と値列に分けられ、ディメンション列とメトリック列に対応します。 キー列には、user_id、date、age など、集約タイプが指定されていない列が含まれます。値列には、集約タイプが指定されている列が含まれます。 データがテーブルにインポートされると、キー列に同じデータを含むすべての行が集約され、これらの行の値列のデータは、各値列に指定された集約タイプに基づいて集約されます。 集約後、データの 1 行のみが保持されます。
集約タイプを次の表に示します。
集約タイプ | 説明 |
SUM | 複数行の値の合計を計算します。このタイプは数値に適用できます。 |
MIN | 最小値を計算します。このタイプは数値に適用できます。 |
MAX | 最大値を計算します。このタイプは数値に適用できます。 |
REPLACE | 以前の値を新しくインポートされた値に置き換えます。ディメンション列に同じデータを含む行の場合、メトリック列の値は、値がインポートされた順序に基づいて、新しくインポートされた値に置き換えられます。 |
REPLACE_IF_NOT_NULL | null 値以外の値を新しくインポートされた値に置き換えます。REPLACE タイプとは異なり、このタイプは null 値を置き換えません。このタイプを使用する場合は、フィールドのデフォルト値として空の文字列ではなく null 値を指定する必要があります。フィールドのデフォルト値として空の文字列を指定すると、このタイプは空の文字列を別の文字列に置き換えます。 |
HLL_UNION | HLL アルゴリズムを使用して、HyperLogLog (HLL) タイプの列を集約します。 |
BITMAP_UNION | BITMAP タイプの列を集約します。これは、ビットマップの和集合集約を実行します。 |
次の表に示すデータを example_tbl1 テーブルに書き込みます。
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
10000 | 2017-10-01 | Beijing | 20 | 0 | 2017-10-01 06:00:00 | 20 | 10 | 10 |
10000 | 2017-10-01 | Beijing | 20 | 0 | 2017-10-01 07:00:00 | 15 | 2 | 2 |
10001 | 2017-10-01 | Beijing | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | Shanghai | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | Guangzhou | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | Shenzhen | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | Shenzhen | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
データをインポートするための SQL ステートメントを実行する方法の例を次のサンプルコードに示します。
INSERT INTO example_db.example_tbl_agg1 VALUES
(10000,"2017-10-01","Beijing",20,0,"2017-10-01 06:00:00",20,10,10),
(10000,"2017-10-01","Beijing",20,0,"2017-10-01 07:00:00",15,2,2),
(10001,"2017-10-01","Beijing",30,1,"2017-10-01 17:05:45",2,22,22),
(10002,"2017-10-02","Shanghai",20,1,"2017-10-02 12:59:12",200,5,5),
(10003,"2017-10-02","Guangzhou",32,0,"2017-10-02 11:20:00",30,11,11),
(10004,"2017-10-01","Shenzhen",35,0,"2017-10-01 10:00:15",100,3,3),
(10004,"2017-10-03","Shenzhen",35,0,"2017-10-03 10:20:22",11,6,6);ApsaraDB for SelectDB インスタンスにデータが書き込まれた後のデータの格納方法を次の表に示します。
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
10000 | 2017-10-01 | Beijing | 20 | 0 | 2017-10-01 07:00:00 | 35 | 10 | 2 |
10001 | 2017-10-01 | Beijing | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | Shanghai | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | Guangzhou | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | Shenzhen | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | Shenzhen | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
上記の表では、ID が 10000 のユーザーについて、集約されたデータの 1 行のみが保持されます。他のユーザーのデータは生データと一致しています。ID が 10000 のユーザーのデータの集約方法を以下に分析します。
最初の 5 つの列は変更されません。6 番目の列 last_visit_date からデータが変更されます。
2017-10-01 07:00:00:last_visit_date列の集約タイプは REPLACE です。したがって、2017-10-01 06:00:00は2017-10-01 07:00:00に置き換えられます。説明REPLACE タイプを使用して同じバッチでインポートされたデータを集約する場合、値が置き換えられる順序は固定されていません。この例では、格納されるデータは
2017-10-01 06:00:00になる可能性があります。ただし、データが異なるバッチでインポートされる場合、新しいバッチでインポートされたデータは、以前のバッチでインポートされたデータを置き換えます。35:cost列の集約タイプは SUM です。したがって、更新された値 35 は 20 + 15 の結果です。10:max_dwell_time列の集約タイプは MAX です。したがって、10 と 2 の間の最大値として 10 が格納されます。2:min_dwell_time列の集約タイプは MIN です。したがって、10 と 2 の間の最小値として 2 が格納されます。
集約後、集約されたデータのみが ApsaraDB for SelectDB に格納されます。詳細な生データは使用できなくなります。
例 2: インポートされたデータと既存のデータの集約
次のサンプルコードは、example_tbl2 テーブルの作成方法の例を示しています。パーティションおよび分散情報は省略されています。
CREATE TABLE IF NOT EXISTS test.example_tbl2
(
`user_id` LARGEINT NOT NULL COMMENT "ユーザー ID", /* The user ID */
`date` DATE NOT NULL COMMENT "データがテーブルに書き込まれた日付", /* The date on which data is written to the table */
`city` VARCHAR(20) COMMENT "ユーザーが住んでいる都市", /* The city in which the user resides */
`age` SMALLINT COMMENT "ユーザーの年齢", /* The age of the user */
`sex` TINYINT COMMENT "ユーザーの性別", /* The gender of the user */
`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "ユーザーが最後にアクセスした日時", /* The last time when the user paid a visit */
`cost` BIGINT SUM DEFAULT "0" COMMENT "ユーザーが費やした金額", /* The amount of money that the user spends */
`max_dwell_time` INT MAX DEFAULT "0" COMMENT "ユーザーの最大滞在時間", /* The maximum dwell time of the user */
`min_dwell_time` INT MIN DEFAULT "99999" COMMENT "ユーザーの最小滞在時間" /* The minimum dwell time of the user */
)
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;次の表に示すデータを example_tbl2 テーブルに書き込みます。
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
10000 | 2017-10-01 | Beijing | 20 | 0 | 2017-10-01 07:00:00 | 35 | 10 | 2 |
10001 | 2017-10-01 | Beijing | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | {
"error": {
"code": 429,
"message": "Resource exhausted. Please try again later. Please refer to https://cloud.google.com/vertex-ai/generative-ai/docs/error-code-429 for more details.",
"status": "RESOURCE_EXHAUSTED"
}
}
2017-10-02 | Shanghai | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | Guangzhou | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | Shenzhen | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | Shenzhen | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
データをインポートするための SQL ステートメントを実行する方法の例を次のサンプルコードに示します。
INSERT INTO test.example_tbl2 VALUES
(10000,"2017-10-01","Beijing",20,0,"2017-10-01 06:00:00",20,10,10),
(10000,"2017-10-01","Beijing",20,0,"2017-10-01 07:00:00",15,2,2),
(10001,"2017-10-01","Beijing",30,1,"2017-10-01 17:05:45",2,22,22),
(10002,"2017-10-02","Shanghai",20,1,"2017-10-02 12:59:12",200,5,5),
(10003,"2017-10-02","Guangzhou",32,0,"2017-10-02 11:20:00",30,11,11),
(10004,"2017-10-01","Shenzhen",35,0,"2017-10-01 10:00:15",100,3,3),
(10004,"2017-10-03","Shenzhen",35,0,"2017-10-03 10:20:22",11,6,6);次に、次の表に示すデータを example_tbl2 テーブルに書き込みます。
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
10004 | 2017-10-03 | Shenzhen | 35 | 0 | 2017-10-03 11:22:00 | 44 | 19 | 19 |
10005 | 2017-10-03 | Changsha | 29 | 1 | 2017-10-03 18:11:02 | 3 | 1 | 1 |
データをインポートするための SQL ステートメントを実行する方法の例を次のサンプルコードに示します。
INSERT INTO test.example_tbl2 VALUES
(10004,"2017-10-03","Shenzhen",35,0,"2017-10-03 11:22:00",44,19,19),
(10005,"2017-10-03","Changsha",29,1,"2017-10-03 18:11:02",3,1,1);ApsaraDB for SelectDB インスタンスにデータが書き込まれた後のデータの格納方法を次の表に示します。
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
10000 | 2017-10-01 | Beijing | 20 | 0 | 2017-10-01 07:00:00 | 35 | 10 | 2 |
10001 | 2017-10-01 | Beijing | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | Shanghai | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | Guangzhou | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | Shenzhen | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | Shenzhen | 35 | 0 | 2017-10-03 11:22:00 | 55 | 19 | 6 |
10005 | 2017-10-03 | Changsha | 29 | 1 | 2017-10-03 18:11:02 | 3 | 1 | 1 |
上記の表では、ID が 10004 のユーザーの既存のデータが、新しくインポートされたデータと集約されています。ID が 10005 のユーザーのデータはテーブルにインポートされます。
ApsaraDB for SelectDB では、データは次の 3 つの段階で集約されます。
インポートされたデータの各バッチの抽出、変換、ロード (ETL) ステージ。このステージでは、インポートされたデータの各バッチが内部的に集約されます。
コンピューティングクラスタのデータ圧縮ステージ。このステージでは、コンピューティングクラスタは異なるバッチでインポートされたデータを集約します。
データクエリステージ。各クエリに含まれるデータが集約されます。
さまざまなステージで、データはさまざまな度合いで集約されます。たとえば、データのバッチがインポートされた場合、既存のデータと集約されない場合があります。ただし、クエリできるデータは集約されます。データが集約される度合いは透過的です。集約ステージを知る必要はなく、クエリされたデータが完全に集約されていると想定するだけで済みます。
例 3: 詳細データの保持
example_tbl3 テーブルのスキーマを次の表に示します。
列名 | タイプ | 集約タイプ | コメント |
user_id | LARGEINT | N/A | ユーザー ID。 |
date | DATE | N/A | データがテーブルに書き込まれた日付。 |
timestamp | DATETIME | N/A | データがテーブルに書き込まれた時刻 (秒単位)。 |
city | VARCHAR(20) | N/A | ユーザーが住んでいる都市。 |
age | SMALLINT | N/A | ユーザーの年齢。 |
sex | TINYINT | N/A | ユーザーの性別。 |
last_visit_date | DATETIME | REPLACE | ユーザーが最後にアクセスした日時。 |
cost | BIGINT | SUM | ユーザーが費やした金額。 |
max_dwell_time | INT | MAX | ユーザーの最大滞在時間。 |
min_dwell_time | INT | MIN | ユーザーの最小滞在時間。 |
テーブルに書き込まれた時刻 (秒単位) を記録するために、timestamp 列が追加されます。さらに、AGGREGATE KEY キーワードを使用して、次の集約キー列を指定します。AGGREGATE KEY(user_id, date, timestamp, city, age, sex).
次のサンプルコードは、example_tbl3 テーブルの作成例を示しています。パーティションおよび分散情報は省略されています。
CREATE TABLE IF NOT EXISTS test.example_tbl3
(
`user_id` LARGEINT NOT NULL COMMENT "ユーザー ID", /* The user ID */
`date` DATE NOT NULL COMMENT "データがテーブルに書き込まれた日付", /* The date on which data is written to the table */
`timestamp` DATETIME NOT NULL COMMENT "データがテーブルに書き込まれた時刻 (秒単位)", /* The time when data is written to the table, which is accurate to seconds */
`city` VARCHAR(20) COMMENT "ユーザーが住んでいる都市", /* The city in which the user resides */
`age` SMALLINT COMMENT "ユーザーの年齢", /* The age of the user */
`sex` TINYINT COMMENT "ユーザーの性別", /* The gender of the user */
`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "ユーザーが最後にアクセスした日時", /* The last time when the user paid a visit */
`cost` BIGINT SUM DEFAULT "0" COMMENT "ユーザーが費やした金額", /* The amount of money that the user spends */
`max_dwell_time` INT MAX DEFAULT "0" COMMENT "ユーザーの最大滞在時間", /* The maximum dwell time of the user */
`min_dwell_time` INT MIN DEFAULT "99999" COMMENT "ユーザーの最小滞在時間" /* The minimum dwell time of the user */
)
AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;次の表に示すデータを example_tbl3 テーブルに書き込みます。
user_id | date | timestamp | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
10000 | 2017-10-01 | 2017-10-01 08:00:05 | Beijing | 20 | 0 | 2017-10-01 06:00:00 | 20 | 10 | 10 |
10000 | 2017-10-01 | 2017-10-01 09:00:05 | Beijing | 20 | 0 | 2017-10-01 07:00:00 | 15 | 2 | 2 |
10001 | 2017-10-01 | 2017-10-01 18:12:10 | Beijing | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | 2017-10-02 13:10:00 | Shanghai | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | 2017-10-02 13:15:00 | Guangzhou | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | 2017-10-01 12:12:48 | Shenzhen | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | 2017-10-03 12:38:20 | Shenzhen | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
データをインポートするための SQL ステートメントを実行する方法の例を次のサンプルコードに示します。
INSERT INTO test.example_tbl3 VALUES
(10000,"2017-10-01","2017-10-01 08:00:05","Beijing",20,0,"2017-10-01 06:00:00",20,10,10),
(10000,"2017-10-01","2017-10-01 09:00:05","Beijing",20,0,"2017-10-01 07:00:00",15,2,2),
(10001,"2017-10-01","2017-10-01 18:12:10","Beijing",30,1,"2017-10-01 17:05:45",2,22,22),
(10002,"2017-10-02","2017-10-02 13:10:00","Shanghai",20,1,"2017-10-02 12:59:12",200,5,5),
(10003,"2017-10-02","2017-10-02 13:15:00","Guangzhou",32,0,"2017-10-02 11:20:00",30,11,11),
(10004,"2017-10-01","2017-10-01 12:12:48","Shenzhen",35,0,"2017-10-01 10:00:15",100,3,3),
(10004,"2017-10-03","2017-10-03 12:38:20","Shenzhen",35,0,"2017-10-03 10:20:22",11,6,6);ApsaraDB for SelectDB インスタンスにデータが書き込まれた後のデータの格納方法を次の表に示します。
user_id | date | timestamp | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
10000 | 2017-10-01 | 2017-10-01 08:00:05 | China (Beijing) | 20 | 0 | 2017-10-01 06:00:00 | 20 | 10 | 10 |
10000 | 2017-10-01 | 2017-10-01 09:00:05 | Beijing | 20 | 0 | 2017-10-01 07:00:00 | 15 | 2 | 2 |
10001 | 2017-10-01 | 2017-10-01 18:12:10 | Beijing | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | 2017-10-02 13:10:00 | China (Shanghai) | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | 2017-10-02 13:15:00 | Guangzhou | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | 2017-10-01 12:12:48 | Shenzhen | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | 2017-10-03 12:38:20 | Shenzhen | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
格納されたデータは、インポートされたデータとまったく同じです。データは集約されません。このデータバッチに timestamp 列が追加された後、行にはキー列に同じデータが含まれていません。この場合、集約キーモデルはデータを集約せず、完全な詳細データを保持します。
ユニークキーモデル
データの複数のディメンションを分析する場合、キーの一意性を確保する必要があります。たとえば、主キーに一意性制約を構成します。この問題を解決するために、ApsaraDB for SelectDB はユニークキーモデルをサポートしています。以前のバージョンでは、ユニークキーモデルは本質的に集約キーモデルの特殊なケースであり、テーブルスキーマを提示するための簡略化された方法でした。集約キーモデルは、Merge on Read (MoR) 実装方式に基づいて実装されており、一部の集約クエリには適していません。ApsaraDB for SelectDB V3.0 では、ユニークキーモデルは Merge on Write (MoW) 実装方式を提供しており、データ書き込み中に重複を削除することで最適なクエリパフォーマンスを実現します。
ユニークキーモデルは、指定されたキー列に同じデータを含む複数の行を上書きし、最後にインポートされた行のみを保持します。これにより、リレーショナルデータベースの一意性制約に似た一意性制約が提供されます。
ユニークキーモデルは、一意性制約を必要とするシナリオで主キーの一意性を確保します。このモデルは、注文などのリレーショナルデータの分析に使用できます。集約クエリの高パフォーマンス要件がある場合は、ApsaraDB for SelectDB の最新バージョンで提供されている MoW 実装方式を使用することをお勧めします。ただし、ユニークキーモデルは、クエリで ROLLUP などの事前集約を利用できません。
ユニークキーモデルを使用する場合は、MoW 実装方式を使用することをお勧めします。
次の例では、2 つの異なる実装方式について説明します。
MoW
ユニークキーモデルの MoW 実装方式は、集約キーモデルとはまったく異なります。MoW 実装方式のクエリパフォーマンスは、次のセクションで説明する複製キーモデルのパフォーマンスに近くなります。主キー制約が必要なシナリオでは、ユニークキーモデルの MoW 実装方式は、特に集約クエリや大量のデータをフィルタリングするためにインデックスを必要とするクエリにおいて、集約キーモデルよりも優れたクエリパフォーマンスを提供します。
デフォルトでは、ApsaraDB for SelectDB V3.0 では MoW 機能は無効になっています。次のプロパティを構成することで、MoW 機能を有効にできます。
"enable_unique_key_merge_on_write" = "true"example_tbl6 テーブルのスキーマを次の表に示します。
列名 | タイプ | 集約タイプ | コメント |
user_id | BIGINT | N/A | ユーザー ID。 |
username | VARCHAR(50) | N/A | ユーザーのニックネーム。 |
city | VARCHAR(20) | NONE | ユーザーが住んでいる都市。 |
age | SMALLINT | NONE | ユーザーの年齢。 |
sex | TINYINT | NONE | ユーザーの性別。 |
phone | LARGEINT | NONE | ユーザーの電話番号。 |
address | VARCHAR(500) | NONE | ユーザーの住所。 |
register_time | DATETIME | NONE | ユーザーが登録された日時。 |
次のサンプルコードは、example_tbl6 テーブルを作成し、テーブルのプロパティを変更する方法の例を示しています。パーティションと分散の情報は省略されています。
CREATE TABLE IF NOT EXISTS test.example_tbl6
(
`user_id` LARGEINT NOT NULL COMMENT "ユーザー ID", /* The user ID */
`username` VARCHAR(50) NOT NULL COMMENT "ユーザーのニックネーム", /* The nickname of the user */
`city` VARCHAR(20) COMMENT "ユーザーが住んでいる都市", /* The city in which the user resides */
`age` SMALLINT COMMENT "ユーザーの年齢", /* The age of the user */
`sex` TINYINT COMMENT "ユーザーの性別", /* The gender of the user */
`phone` LARGEINT COMMENT "ユーザーの電話番号", /* The phone number of the user */
`address` VARCHAR(500) COMMENT "ユーザーの住所", /* The address of the user */
`register_time` DATETIME COMMENT "ユーザーが登録された日時" /* The time when the user is registered */
)
UNIQUE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16
PROPERTIES (
"enable_unique_key_merge_on_write" = "true"
);上記の CREATE TABLE ステートメントを使用して作成されたテーブルスキーマは、集約キーモデルを使用して作成されたテーブルスキーマとはまったく異なります。
ユニークキーモデルを使用し、MoW 機能が有効になっているテーブルでは、上書きおよび更新されるデータはデータインポートステージで削除対象としてマークされ、最新のデータが書き込まれます。テーブルをクエリすると、削除対象としてマークされたすべてのデータが除外され、最新のデータが読み取られます。これにより、MoR 実装方式のデータ集約プロセスが不要になり、ほとんどの場合、複数のタイプの述語プッシュダウンがサポートされます。したがって、これにより、特に集約クエリにおいて、複数のシナリオでパフォーマンスが向上する可能性があります。
デフォルトでは、ApsaraDB for SelectDB V3.0 では MoW 機能は無効になっています。テーブルを作成するときに指定されたプロパティを構成することで、MoW 機能を有効にできます。
MoR 実装方式と MoW 実装方式はデータの編成方法が異なるため、MoR 実装方式を MoW 実装方式にシームレスにアップグレードすることはできません。MoW 実装方式を使用する場合は、
INSERT INTO unique-mow-table SELECT * FROM source_tableステートメントを実行して、新しいテーブルにデータをインポートする必要があります。MoW 実装方式を使用する場合でも、ユニークキーモデルの一意の削除記号とシーケンス列を引き続き使用できます。
MoR
example_tbl4 テーブルのスキーマを次の表に示します。
列名 | タイプ | キー | コメント |
user_id | BIGINT | はい | ユーザー ID。 |
username | VARCHAR(50) | はい | ユーザーのニックネーム。 |
city | VARCHAR(20) | いいえ | ユーザーが住んでいる都市。 |
age | SMALLINT | いいえ | ユーザーの年齢。 |
sex | TINYINT | いいえ | ユーザーの性別。 |
phone | LARGEINT | いいえ | ユーザーの電話番号。 |
address | VARCHAR(500) | いいえ | ユーザーの住所。 |
register_time | DATETIME | いいえ | ユーザーが登録された日時。 |
これは、ユーザーの基本情報を記録する典型的なテーブルです。このタイプのデータを集約する必要はありません。主キーの一意性を確保するだけで済みます。この例では、user_id 列と username 列が主キーとして使用されます。
次のサンプルコードは、example_tbl4 テーブルを作成する方法の例を示しています。パーティションおよび分散情報は省略されています。
CREATE TABLE IF NOT EXISTS test.example_tbl4
(
`user_id` LARGEINT NOT NULL COMMENT "ユーザー ID", /* The user ID */
`username` VARCHAR(50) NOT NULL COMMENT "ユーザーのニックネーム", /* The nickname of the user */
`city` VARCHAR(20) COMMENT "ユーザーが住んでいる都市", /* The city in which the user resides */
`age` SMALLINT COMMENT "ユーザーの年齢", /* The age of the user */
`sex` TINYINT COMMENT "ユーザーの性別", /* The gender of the user */
`phone` LARGEINT COMMENT "ユーザーの電話番号", /* The phone number of the user */
`address` VARCHAR(500) COMMENT "ユーザーの住所", /* The address of the user */
`register_time` DATETIME COMMENT "ユーザーが登録された日時" /* The time when the user is registered */
)
UNIQUE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;example_tbl5 テーブルのスキーマを次の表に示します。
列名 | タイプ | 集約タイプ | コメント |
user_id | BIGINT | N/A | ユーザー ID。 |
username | VARCHAR(50) | N/A | ユーザーのニックネーム。 |
city | VARCHAR(20) | REPLACE | ユーザーが住んでいる都市。 |
age | SMALLINT | REPLACE | ユーザーの年齢。 |
sex | TINYINT | REPLACE | ユーザーの性別。 |
phone | LARGEINT | REPLACE | ユーザーの電話番号。 |
address | VARCHAR(500) | REPLACE | ユーザーの住所。 |
register_time | DATETIME | REPLACE | ユーザーが登録された日時。 |
次のサンプルコードは、example_tbl5 テーブルを作成する方法の例を示しています。パーティションおよび分散情報は省略されています。
CREATE TABLE IF NOT EXISTS test.example_tbl5
(
`user_id` LARGEINT NOT NULL COMMENT "ユーザー ID", /* The user ID */
`username` VARCHAR(50) NOT NULL COMMENT "ユーザーのニックネーム", /* The nickname of the user */
`city` VARCHAR(20) REPLACE COMMENT "ユーザーが住んでいる都市", /* The city in which the user resides */
`age` SMALLINT REPLACE COMMENT "ユーザーの年齢", /* The age of the user */
`sex` TINYINT REPLACE COMMENT "ユーザーの性別", /* The gender of the user */
`phone` LARGEINT REPLACE COMMENT "ユーザーの電話番号", /* The phone number of the user */
`address` VARCHAR(500) REPLACE COMMENT "ユーザーの住所", /* The address of the user */
`register_time` DATETIME REPLACE COMMENT "ユーザーが登録された日時" /* The time when the user is registered */
)
AGGREGATE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;上記の example_tbl4 テーブルのスキーマは、集約キーモデルを使用する example_tbl5 テーブルのスキーマと同じです。
ユニークキーモデルの MoR 実装方式は、集約キーモデルの REPLACE タイプで置き換えることができます。それらの内部実装とデータ格納方法はまったく同じです。
複製キーモデル
データの複数のディメンションを分析する場合、主キーを使用したり、データを集約したりする必要がない場合があります。この場合、複製キーモデルを使用してビジネス要件を満たすことができます。
複製キーモデルの場合、指定されたキー列に同じデータを含む複数の行を、相互に影響を与えることなくシステムに格納できます。これらの行のデータは事前に集約する必要はなく、一意性制約も必要ありません。
複製キーモデルは、ディメンションに基づくアドホッククエリに適しており、ログなどの詳細データの分析に使用できます。複製キーモデルでは、データを事前に集約してパフォーマンスを向上させたり、一意性制約を構成して自動更新を実装したりすることはできません。
次の例では、複製キーモデルの実装方式について説明します。
example_tbl7 テーブルのスキーマを次の表に示します。
列 | タイプ | ソートキー | コメント |
timestamp | DATETIME | はい | ログが生成された日時。 |
type | INT | はい | ログのタイプ。 |
error_code | INT | はい | エラーコード。 |
error_msg | VARCHAR(1024) | いいえ | エラーメッセージ。 |
op_id | BIGINT | いいえ | オーナー ID。 |
op_time | DATETIME | いいえ | エラーが処理された日時。 |
次のサンプルコードは、example_tbl7 テーブルを作成する方法の例を示しています。パーティションおよび分散情報は省略されています。
CREATE TABLE IF NOT EXISTS test.example_tbl7
(
`timestamp` DATETIME NOT NULL COMMENT "ログが生成された日時", /* The time when the log was generated */
`type` INT NOT NULL COMMENT "ログのタイプ", /* The type of the log */
`error_code` INT COMMENT "エラーコード", /* The error code */
`error_msg` VARCHAR(1024) COMMENT "エラーメッセージ", /* The error message */
`op_id` BIGINT COMMENT "オーナー ID", /* The owner ID */
`op_time` DATETIME COMMENT "エラーが処理された日時" /* The time when the error was handled */
)
DUPLICATE KEY(`timestamp`, `type`, `error_code`)
DISTRIBUTED BY HASH(`type`) BUCKETS 16;複製キーモデルは、集約キーモデルやユニークキーモデルとは異なります。生データは集約されることなくインポートおよび格納されます。同じデータが含まれている場合でも、行は保持されます。CREATE TABLE ステートメントで指定された複製キーは、基盤となるストレージでデータがソートされる列を示します。最初の 2 ~ 4 列を複製キーとして選択することをお勧めします。
データモデル間の違い
集約キーモデルの制限
集約キーモデルは、完全に集約されたデータを示します。集約されていないデータ (2 つのバッチでインポートされたデータなど) は、特定の方法で一貫して表示する必要があります。次の例では、集約キーモデルがデータを一貫して表示する方法について説明します。
次の表は、example_tbl8 テーブルのスキーマを示しています。
列名 | タイプ | 集約タイプ | コメント |
user_id | LARGEINT | N/A | ユーザー ID。 |
date | DATE | N/A | データがテーブルに書き込まれた日付。 |
cost | BIGINT | SUM | ユーザーが費やした金額。 |
次の表に示すデータを、2 つのバッチで example_tbl8 テーブルに書き込みます。
次の表は、最初のバッチでインポートされたデータを示しています。
user_id | date | cost |
10001 | 2017-11-20 | 50 |
10002 | 2017-11-21 | 39 |
次の表は、2 番目のバッチでインポートされたデータを示しています。
user_id | date | cost |
10001 | 2017-11-20 | 1 |
10001 | 2017-11-21 | 5 |
10003 | 2017-11-22 | 22 |
ID が 10001 のユーザーについて、2 つのバッチで 3 行がインポートされます。ApsaraDB for SelectDB でデータが内部的に集約される前に、上記の 5 行が基盤となるストレージに格納されます。次の集約データのみをクエリできるように、システムはクエリエンジンに集約演算子を自動的に追加します。次の表は、クエリできる集約データを示しています
user_id | date | cost |
10001 | 2017-11-20 | 51 |
10001 | 2017-11-21 | 5 |
10002 | 2017-11-21 | 39 |
10003 | 2017-11-22 | 22 |
たとえば、次の SQL ステートメントを実行すると、返される結果は 1 ではなく 5 になります。
SELECT MIN(cost) FROM example_tbl8;このように、データは一貫して表示されます。ただし、一部のクエリではクエリ効率が低下します。次の例では、COUNT(*) クエリが使用されています。
SELECT COUNT(*) FROM example_tbl8;他のデータベースでは、このようなクエリの結果はすぐに返されます。実際の実装では、モデルはデータのインポート時に行をカウントしてカウント値を保存するか、データのクエリ時に 1 つの列のみをスキャンしてカウント値を取得することでクエリ結果を取得できます。これにはわずかなオーバーヘッドが必要です。ただし、集約キーモデルでは、このようなクエリに大きなオーバーヘッドが必要です。
たとえば、上記のデータをクエリします。
SELECT COUNT(*) FROM example_tbl8; ステートメントを実行すると、4 が返されることが期待されます。ただし、user_id 列のみがスキャンされ、クエリ中にデータが集約されると、3 が返されます。クエリ中にデータが集約されない場合、2 つのバッチで 5 行がインポートされるため、5 が返されます。どちらの結果も間違っています。
これは、集約されるデータに多数の列が含まれる場合、COUNT() クエリ中に大量のデータをスキャンする必要があるためです。正しい結果 4 を取得するには、モデルは user_id 列と date 列の両方を読み取り、クエリ中にデータを集約する必要があります。具体的には、COUNT() クエリ中に、モデルはすべての集約キー列をスキャンし、データを集約して、セマンティック的に正しい結果を取得する必要があります。この例では、集約キー列には user_id 列と date 列が含まれます。
頻繁に COUNT(*) クエリを実行する必要がある場合は、値が 1 に固定され、集計タイプが SUM の列を追加して COUNT(*) 関数をシミュレートすることをお勧めします。たとえば、count 列が example_tbl8 テーブルのスキーマに追加されます。次の表は、変更されたテーブルのスキーマを示しています。
列名 | タイプ | 集約タイプ | コメント |
user_id | BIGINT | N/A | ユーザー ID。 |
date | DATE | N/A | データがテーブルに書き込まれた日付。 |
cost | BIGINT | SUM | ユーザーが費やした金額。 |
count | BIGINT | SUM | 行の総数をカウントするために使用される列。 |
count 列の値は 1 に固定されています。この場合、SELECT COUNT(*) FROM table; ステートメントは SELECT SUM(COUNT) FROM table; ステートメントと同じです。後者のクエリ効率は前者のクエリ効率よりもはるかに高くなります。ただし、この方法には制限があります。集約キー列に同じデータを含む行を繰り返しインポートすることはできません。そうしないと、SELECT SUM(COUNT) FROM table; ステートメントは、インポートされた行の元の数のみをクエリできます。この場合、ステートメントは SELECT COUNT(*) FROM table; ステートメントのセマンティクスを表すことができません。
または、値が 1 に固定され、集約タイプが REPLACE である count 列を追加します。この場合、SELECT SUM(COUNT) FROM table; ステートメントと SELECT COUNT(*) FROM table; ステートメントで同じ結果が返されます。さらに、この方法では、集約キー列に同じデータを含む行を繰り返しインポートできます。
ユニークキーモデルの MoW
ユニークキーモデルの MoW 実装方式には、集約キーモデルと同じ制限はありません。MoW を使用する場合、ユニークキーモデルはインポートされた行の各バッチに削除ビットマップを追加して、上書きまたは削除されるデータをマークします。上記の例では、最初のバッチのデータがインポートされた後、データは次の方法で格納されます。
次の表は、最初のバッチのデータがインポートされた後の結果を示しています。
user_id | date | cost | 削除ビット |
10001 | 2017-11-20 | 50 | false |
10002 | 2017-11-21 | 39 | false |
2 番目のバッチのデータがインポートされた後、最初のバッチのデータの重複行は削除済としてマークされます。次の表は、2 つのバッチのデータがインポートされた後の結果を示しています。
次の表は、最初のバッチでインポートされたデータがマークされた後の結果を示しています。
user_id | date | cost | 削除ビット |
10001 | 2017-11-20 | 50 | true |
10002 | 2017-11-21 | 39 | false |
次の表は、2 番目のバッチのデータがインポートされた後の結果を示しています。
user_id | date | cost | 削除ビット |
10001 | 2017-11-20 | 1 | false |
10001 | 2017-11-21 | 5 | false |
10003 | 2017-11-22 | 22 | false |
クエリ中に、モデルは削除ビットマップで true とマークされたデータを読み取らず、データは集約されません。この例では、上記のデータに 4 つの有効な行が存在し、4 が返されることが期待されます。1 つの列のみがスキャンされるため、これによりオーバーヘッドが最小限に抑えられます。
テスト環境では、ユニークキーモデルの MoW 実装方式を使用する COUNT(*) クエリは、集約キーモデルを使用するクエリよりも 10 倍高いパフォーマンスを提供します。
複製キーモデルの制限
複製キーモデルには、集約キーモデルと同じ制限はありません。これは、複製キーモデルが集約セマンティクスを含まないためです。モデルは列をスキャンして、COUNT(*) クエリのセマンティック的に正しい結果を取得できます。
キー列
複製キーモデル、集約キーモデル、またはユニークキーモデルを使用する場合、キー列は CREATE TABLE ステートメントで指定されます。ただし、キー列はデータモデルによって異なります。
重複キーモデルの場合、キー列は、一意の識別子列ではなく、並べ替え列と見なすことができます。
集計キーモデルまたは一意キーモデルの場合、テーブルのキー列は、並べ替え列と一意の識別子列の両方です。
データモデルを選択するための推奨事項
データモデルはテーブルの作成時に決定され、変更できません。したがって、適切なデータモデルを選択することが重要です。
集約キーモデルは、データを事前に集約することで、スキャンされるデータ量とクエリに必要なコンピューティングリソースの量を削減できるため、クエリを高速化できます。したがって、集約キーモデルは、固定パターンを持つレポートベースのクエリシナリオまたはレポートベースの集約クエリシナリオに適しています。ただし、このモデルは COUNT(*) クエリには最適ではありません。さらに、各値列に集約タイプが指定されています。他のタイプの集約クエリを実行する場合は、クエリステートメントのセマンティックな正確性を考慮する必要があります。
ユニークキーモデルは、一意性制約を必要とするシナリオで主キーの一意性を確保します。ユニークキーモデルは、注文やトランザクションデータなどのリレーショナルデータの分析に適しています。ただし、ユニークキーモデルは、クエリで ROLLUP などの事前集約によってもたらされる利点を利用できません。集約クエリの高パフォーマンス要件がある場合は、ApsaraDB for SelectDB V3.0 以降で使用可能な MoW 実装方式を使用することをお勧めします。
複製キーモデルは事前集約を利用できませんが、このモデルには集約キーモデルの制限はありません。このモデルは、列指向ストレージの利点を利用できます。列指向ストレージは、すべてのキー列からデータを読み取る必要なく、必要な列からのみデータを読み取ります。複製キーモデルは、ディメンションに基づくアドホッククエリに適しており、ログなどの詳細データの分析に使用できます。
部分列を更新する必要がある場合は、関連する推奨事項を入手できます。詳細については、「部分更新」をご参照ください。