SingleStore の特許取得済みUniversal Storage - パート 3

SingleStore の特許取得済みUniversal Storage - パート 3

SingleStore Universal Storage テクノロジーは、列ストアデータフォーマットを飛躍的に進化させたもので、運用ワークロードと分析ワークロードの両方を低い総所有コスト(TCO)でサポートすることを可能にします。メモリ最適化されたデータ構造とクエリのマシンコードへのコンパイルに基づく優れたパフォーマンスにより、OLTPスタイルのアクセスパターンにおいて行ストアは高い評価を得ています。しかし、大規模なデータセットを扱うユーザーにとって、すべてのデータを保持できる十分なRAMをサーバーに搭載するコストが大きな負担となっていました。Universal Storageは、このコスト問題を解決するだけでなく、列指向ストレージフォーマット、ベクトル化実行、SIMD(Single-Instruction Multiple-Data)命令を活用したクエリ実行により、さらに優れた分析パフォーマンスを提供します。

これは、SingleStore独自の特許取得済みUniversal Storage機能について説明する4部構成の記事の第3弾です。全容をご理解いただくには、パート1パート2パート4をお読みください。

7.0リリースではUniversal Storageが導入され、7.1リリースでは機能が強化されました。そして今回、7.3リリースではUniversal Storageのエピソード3をお届けします。今回は以下の機能が追加されました。

  • 新しいエンジン変数 default_table_type を 'columnstore' に設定することで、列ストアをデフォルトのテーブル タイプにすることができます。

  • 単一列の一意のキーを持つ列ストアテーブルに対するUPSERTサポート
    • INSERT ON DUPLICATE KEY UPDATE

    • INSERT IGNORE
    • REPLACE
    • パイプラインとLOAD DATAの類似機能
  • 非常に大規模な INSERT および UPDATE に対する一意のキーの強制のサポート
    • 今後は、更新される行数に関係なく、任意のINSERTとUPDATEが成功します。7.1では、小規模なノードで1つのステートメントで例えば2,000万~3,000万行以上更新されると、メモリ不足により操作が失敗する可能性がありました。

SingleStore 7.3 のUniversal Storage アップデートの詳細については、次のビデオをご覧ください。

列ストアをデフォルトとして

Singlestore Heliosでは、7.3のデプロイ時に新規クラスターのデフォルトのテーブルタイプが列ストアに設定されます。SingleStoreDB Self-Managedでは、デフォルトのテーブルタイプは行ストアですが、次のコマンドを実行することで列ストアに設定できます。

 

1set global default_table_type = 'columnstore';

これで、MySQL、MariaDB、またはSingleStoreの行ストアテーブルでアプリケーションを作成する際に実行したほぼすべてのCREATE TABLEステートメントが正常に実行され、列ストアが作成されます。新規ユーザーにとっての最大のメリットは、大量のデータを追加してもRAMが不足しないことです。列ストアを頻繁に使用するベテランユーザーにとっても、この利便性は大きなメリットとなるでしょう。

たとえば、次の CREATE TABLE ステートメントを実行すると、このモードで列ストアが作成されます。

1create table fact_sales(2  ts datetime(6),3  qty int,4  prod_id int,5  unit_price decimal(18,2),6  store_id int7);
このステートメントは、show create table fact_sales によって出力される次のステートメントと同等です。
1create table `fact_sales` (2  `ts` datetime(6) default null,3  `qty` int(11) default null,4  `prod_id` int(11) default null,5  `unit_price` decimal(18,2) default null,6  `store_id` int(11) default null,7  key `__UNORDERED` () using clustered columnstore ,8  shard key ()9);

デフォルトのシャードキーが空であるため、リーフノード間でラウンドロビン方式でデータが分散されることに注意してください。また、デフォルトの列ストアソートキーも空であるため、システムはデータのソート順を積極的に維持しません。多くのユースケースや、開発を始めたばかりの開発者にとっては、これらの選択で十分です。

KEY(…) USING CLUSTERED COLUMNSTORE と同等の新しい短縮形 SORT KEY を使用すると、列ストアの並べ替えキーを簡単に指定できます。

さらに、CREATE TABLE文でKEY(…)を指定すると、ハッシュキーが自動的に作成されます。詳細は7.3のドキュメントをご覧ください。

列ストアをデフォルトモードにしていて、行ストアが必要な場合は、次のように記述します。

1create rowstore table tableName (...)

つまり、SingleStoreを初めてご利用で、RAM不足を心配したくない、ワークロードが主に分析中心、あるいはその両方である場合は、default_table_type = 'columnstore' を設定してください。当社のマネージドサービスでは、これがデフォルトで有効になります。

Universal StorageのUPSERT

Universal Storageのロードマップを設計した際、多くのユーザーが新しいデータの受信、重複の検出、UPSERTを行うために行ストアを使用していることに気づきました。そして、データが落ち着いた後、列ストアに移動します。そして、コールド側の列ストア部分とホット側の行ストア部分を一緒にクエリし、両方のデータを結合するために、特別なクエリロジックを実行する必要がありました。

Universal Storageにより、このような手間が省けます。7.3では、その重要な機能である列ストアのUPSERT機能が提供されます。

最も一般的な種類のUPSERTは、キーが存在しない場合は条件付き挿入を行い、そうでない場合は既存のレコードを何らかの方法でそのキーで更新します。例えば、生の入力データには「ネットワークイベント」が記録されている場合があります。

  • device_id — ネットワークデバイスの一意の識別子
  • bytes_transmitted — このイベントで送信されたバイト数
  • ts — 送信が行われた時刻

おそらくこれは次の表のとおりです。

1create table network_events_new (2  device_id_n int,3  bytes_transmitted_n int,4  ts_n datetime(6),5  sort key(ts_n),6  shard key (device_id_n)7);

覚えておいてください、これは列ストアです。SORT KEY によってそれが実現されます。

ここで、最終的な目標は、デバイスによって転送されたバイト数の合計と、そのデバイスに影響を与えるイベントの最新の時刻を次のテーブルに保持することだとします。

1create table network_events_summary (2  device_id int,3  bytes_transmitted int,4  ts datetime(6),5  unique key(device_id) using hash,6  sort key(ts),7  shard key (device_id)8);
ここで、「新しい」ネットワーク イベント テーブルに初期データをロードします。
1load data infile "/data/network_events.csv"2into table network_events_new3fields terminated by ','4lines terminated by '\n';
.csv ファイルには次の内容が含まれます。
11,1024,2020-12-09 14:22:06.76538422,4096,2020-12-09 14:22:43.61313132,2048,2020-12-09 14:23:06.78644741,512,2020-12-09 14:23:27.96493953,128,2020-12-09 15:55:48.209948
network_events_summary の初期データが次のように作成されるとします。
1insert into network_events_summary values (1,256,"2020-12-09 12:57:46.244642");
つまり、次の内容だけが含まれます。
1+-----------+-------------------+----------------------------+2| device_id | bytes_transmitted | ts                         |3+-----------+-------------------+----------------------------+4|         1 |               256 | 2020-12-09 12:57:46.244642 |5+-----------+-------------------+----------------------------+
ここで、UPSERTを実行して送信された合計バイト数を集計し、初めて検出された新しいデバイスの行を追加できます。
1insert into network_events_summary (device_id, bytes_transmitted, ts)2select * from network_events_new3on duplicate key update4bytes_transmitted = bytes_transmitted + values(bytes_transmitted),5ts = values(ts);
これで、概要には、次のように 3 つのデバイスすべてとその合計数および最終更新時刻が表示されます。
1select * from network_events_summary order by device_id;
+-----------+-------------------+----------------------------+
| device_id | bytes_transmitted | ts                         |
+-----------+-------------------+----------------------------+
|         1 |              1792 | 2020-12-09 14:23:27.964939 |
|         2 |              6144 | 2020-12-09 14:23:06.786447 |
|         3 |               128 | 2020-12-09 15:55:48.209948 |
+-----------+-------------------+----------------------------+

この種のUPSERTパターンは、以前は一意キーを持つSingleStore行ストアテーブルで動作していました。新機能として、単一列の一意キーを持つ列ストアテーブルでも動作するようになりました。

LOAD DATAとパイプラインのUPSERTスタイルのロジック機能(REPLACE、IGNORE、SKIP DUPLICATE KEY)が、列ストアでも使用できるようになりました。単一列の一意キー制限も適用されます。Universal Storageは機能的にほぼ完成しており、既にほとんどのユースケースをカバーできます。エピソード4では、マルチ列キーのサポートも導入され、Universal Storageの全機能が完成する予定ですので、お楽しみに!

ユニークキーを持つ大規模なINSERT/UPDATEのサポート

一意のキーを持つ大規模なバッチ INSERT の簡単な例として、次のスクリプトを確認してください。

1drop database if exists db1;2create database db1;3use db1;4
5create table t(a int not null, unique key(a) using hash, shard(a), key(a) using clustered columnstore);6
7insert into t values(1);8
9delimiter //10/* Fill table t with n or more rows, doubling the size until the goal is reached. */11create procedure inflate(n bigint) as12declare13  c bigint;14begin15  select count(*) from t into c;16  while (c < n) loop17    insert into t 18    select a + (select max(a) from t)19    from t;20  select count(*) from t into c;21  end loop;22end //23delimiter ;
8GB の RAM を搭載したラップトップ上の VM では、次の結果が得られます。
1call inflate(32 * 1000 * 1000); /* success on 7.1.13 and 7.3.1 */
1call inflate(64 * 1000 * 1000); /* OOM on 7.1.13, success on 7.3.1 */

つまり、テーブルを 6,700 万行以上に拡張すると、7.1.13 ではメモリ不足エラーで失敗しますが、7.3.1 では成功します。

ユニークキーを強制するアルゴリズムが変更され、データがRAMに収まらない場合でも機能するようになりました。これは、INSERT INTO … SELECT FROM … 操作で大きなテーブルをコピーし、新しいターゲットテーブルにユニークキーがある場合など、多くの実際の状況で役立ちます。7.1では、これを行うにはデータをチャンクに分割し、複数のコマンドを実行する必要がありました。今なら、ワンステップで実行できます。

まとめ

Universal Storageは現在成熟しつつあり、従来は行ストアを必要としていたアプリケーションをサポートするために必要なほぼすべての機能を備えています。これはTCOの大幅な改善につながります。サポートされる機能領域を考えると、多くの組織ではデフォルトのテーブルタイプを列ストアに設定できる可能性があり、これはシングルストアHeliosのデフォルトになります。最後に、データサイズに関係なく一意のキーを適用できるようになったことで、信頼性が向上し、開発が簡素化されます。

SingleStore の特許取得済みUniversal Storage機能の全容を知るには、シリーズのパート 1パート 2パート 4をお読みください。


Share