mita2 database life

主にMySQLに関するメモです

MySQL Shell のパラレルテーブルインポートの実装がスマートだった件

MySQL Shell のパラレルテーブルインポートの実装が興味深かった

MySQL Shell のパラレルテーブルインポート

MySQL Shell 8.0.17 で導入された MySQL Shell パラレルテーブルインポートユーティリティ util.importTable() は、大規模なデータファイルの MySQL リレーショナルテーブルへの高速データインポートを提供します。 このユーティリティは、入力データファイルを分析してチャンクに配布し、パラレル接続を使用してチャンクをターゲット MySQL サーバーにアップロードします。 このユーティリティは、LOAD DATA ステートメントを使用した標準のシングルスレッドアップロードよりも数回高速に大規模データインポートを完了できます。 https://dev.mysql.com/doc/mysql-shell/8.0/ja/mysql-shell-utilities-parallel-table.html

MySQL Shell にはインポート機能があります。 このインポート機能は1つのファイルを小分けにして(チャンクにして)、並列でテーブルにインポートすることで、高速に動作します。内部の実装には上記の説明にあるとおり、LOAD DATA ステートメントが利用されています。

mysqlsh> util.import_table("products.csv", {"dialect": "csv-unix", "table": "products"})'

どのように実装されているか

パラレル機能はどのように実装されているのでしょうか。ごく単純な実装として、インポート対象のファイルを分割したファイルをまず用意し、それを LOAD DATA で並列にインポートする流れが思いつきます。 しかし、実際は、このような実装になっていません(もうちょっと賢い実装になってる)。

インポート中に流れているSQL

インポート中に実行されているSQLを見ると、同じ LOAD DATA が並列に実行されていました。 LOAD DATA で指定されているファイルは全て同じファイルimport_table で指定したファイル名 /src/data/products.csv ) になっています。

mysql> SHOW FULL PROCESSLIST \G
<snip>
*************************** 4. row ***************************
     Id: 232
   User: root
   Host: localhost
     db: onlinemall
Command: Query
   Time: 5
  State: executing
   Info: LOAD DATA LOCAL INFILE '/src/data/products.csv' INTO TABLE `onlinemall`.`products` FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n'
*************************** 5. row ***************************
     Id: 233
   User: root
   Host: localhost
     db: onlinemall
Command: Query
   Time: 5
  State: executing
   Info: LOAD DATA LOCAL INFILE '/src/data/products.csv' INTO TABLE `onlinemall`.`products` FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n'
*************************** 6. row ***************************
     Id: 234
   User: root
   Host: localhost
     db: onlinemall
Command: Query
   Time: 5
  State: executing
   Info: LOAD DATA LOCAL INFILE '/src/data/products.csv' INTO TABLE `onlinemall`.`products` FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n'

通常であれば、これはまったく意味のない行為です。重複エラーになるでしょうし、パフォーマンス面でもメリットはありません。 しかし、MySQL Shell は LOAD DATAプロトコルの仕様をうまく活用して、実際は高速なデータロードを実現しています。

LOAD DATA のフロー

LOAD DATAのフローは特殊です。サーバは LOAD DATA を受け取ると、クライアントにファイルの内容を送るようリクエストします。 INSERTUPDATE と違って、クエリにデータが含まれていませんから、クライアントにファイルの中身を別途、送ってもらう必要があるわけです。

このあたりは、@tmtms さんの解説が詳しいです。

blog.tmtms.net

https://cdn-ak.f.st-hatena.com/images/fotolife/t/tmtms/20171130/20171130224205.png

@tmtms さんのブログから図をお借りしました

パラレルテーブルインポートの実装

MySQL Shell は上記の仕様をうまく使って、該当のコネクションが担当する範囲(チャンク)のみをサーバに返しています。 つまり、各コネクションは同じファイルをサーバ側から読むようにリクエストされたにも関わらず、実際には異なる内容をレスポンスしています。

f:id:mita2db:20211026234515p:plain

圧縮ファイルの扱いも同じ

このインポートユーティリティは圧縮されたファイルも扱えます。

mysqlsh> util.import_table("products.csv.gz", {"dialect": "csv-unix", "table": "products"})'

同様の流れで、解凍した内容をサーバに返すことで、本来は LOAD DATA がサポートしていない、圧縮ファイルを LOAD DATA できるように見せかけてます。 賢いですね〜。

-- 普通はエラーになる
mysql> LOAD DATA LOCAL INFILE '/src/data/products.csv.gz' INTO TABLE `onlinemall`.`products` FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n';
ERROR 1300 (HY000): Invalid utf8mb4 character string: ''
-- MySQL Shell からだと実行できているように見える
<snip>
*************************** 4. row ***************************
     Id: 225
   User: root
   Host: localhost
     db: onlinemall
Command: Query
   Time: 117
  State: executing
   Info: LOAD DATA LOCAL INFILE '/src/data/products.csv.gz' INTO TABLE `onlinemall`.`products` FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n'
4 rows in set (0.00 sec)

まとめ

  • LOAD DATA のファイル名は飾り