TiDB Lightningを使ってCSVファイルからTiDBにデータをインポートしてみた

2024.02.01

CX事業本部@大阪の岩田です。TiDBにデータをインポートするためのツールTiDB Lightningを利用してCSVファイルからTiDB Serverlessにデータをインポートする方法を調べたので、手順等をご紹介します。

環境

今回検証に利用した環境です

  • TiDB: 5.7.28-TiDB-v6.6.0-serverless
  • TiDB Lightning: v7.6.0

TiDB Lightningの概要

TiDB LightningはTiDBにデータをインポートするためのツールです。利用可能なファイル形式として以下の3種に対応しています。

  • Dumplingでエクスポートされたファイル
  • CSVファイル ※オプションを調整することでTSVファイルや|(パイプ)区切りのファイルも対応可能です
  • Amazon Auroraまたは Apache Hive によってエクスポートされた Parquet ファイル

また、インポート用のファイルは以下の場所から読み込み可能です。

  • TiDB Lightningを実行するマシンのローカル
  • Amazon S3
  • Google Cloud Storage

インポートには物理インポートと論理インポートの2種類があります。物理インポートの場合、SQLは利用せずにインポート対象ファイルの中身をキー&バリューのペアとしてTiKVに直接INSERTします。論理インポートの場合はインポート対象ファイルからSQL文を生成してTiDBにINSERTします。SQLによるオペレーションなので、ACID特性への準拠が保証されるのがメリットです。

物理インポートと論理インポートの特徴を比較すると以下のようになります。

TiDB Lightningの概要より引用

インポートモード 物理インポートモード 論理インポートモード
バックエンド local tidb
スピード 高速 (100 ~ 500 GiB/時間) 低 (10 ~ 50 GiB/時間)
資源の消費 高い 低い
ネットワーク帯域幅の消費量 高い 低い
インポート時のACID準拠 いいえ はい
ターゲットテーブル 空でなければなりません データを含めることができる
TiDB クラスターのバージョン = 4.0.0 全て
TiDB クラスターがインポート中にサービスを提供できるかどうか 制限あり はい

やってみる

ここからは実際にTiDB Lightningを使ったデータのインポートをやってみます

tiupとTiDB Lightningのインストール

なにわともあれTiDB Lightningが必要になるのでインストールします。今回はtiupでインストールする方式で進めてみます。まずは以下のコマンドでtiupをインストールします。

$ curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 5093k  100 5093k    0     0  8503k      0 --:--:-- --:--:-- --:--:-- 8489k
WARN: adding root certificate via internet: https://tiup-mirrors.pingcap.com/root.json
You can revoke this by remove /home/iwata.tomoya/.tiup/bin/7b8e153f2e2d0928.root.json
Successfully set mirror to https://tiup-mirrors.pingcap.com
Detected shell: bash
Shell profile:  /home/iwata.tomoya/.bashrc
/home/iwata.tomoya/.bashrc has been modified to add tiup to PATH
open a new terminal or source /home/iwata.tomoya/.bashrc to use it
Installed path: /home/iwata.tomoya/.tiup/bin/tiup
===============================================
Have a try:     tiup playground
===============================================

続いてtiupを使ってTiDB Lightningをインストールしますsource ~/.bashrcでtiupにパスを通してから以下のコマンドを実行します。

$ tiup install tidb-lightning
download https://tiup-mirrors.pingcap.com/tidb-lightning-v7.6.0-linux-amd64.tar.gz 76.22 MiB / 76.22 MiB 100.00% 59.85 MiB/s

インストールできたか確認するためにバージョン情報を表示してみます。

$ tiup tidb-lightning -V
Checking updates for component tidb-lightning... Timedout (after 2s)
Starting component tidb-lightning: /home/iwata.tomoya/.tiup/components/tidb-lightning/v7.6.0/tidb-lightning -V
Release Version: v7.6.0
Git Commit Hash: 52794d985ba6325d75a714d4eaa0838d59425eb6
Git Branch: heads/refs/tags/v7.6.0
Go Version: go1.21.5
UTC Build Time: 2024-01-22 14:15:25
Race Enabled: false

問題無さそうですね。

インポート用のデータを準備

続いてインポート用の適当なデータを準備します。今回はRedshiftにデータをロードするチュートリアルで利用しているサンプルデータを利用させてもらいます。

$ wget https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/samples/LoadingDataSampleFiles.zip

サンプルデータがダウンロードできたら解凍します。

$ unzip LoadingDataSampleFiles.zip
Archive:  LoadingDataSampleFiles.zip
   creating: LoadingDataSampleFiles/
  inflating: LoadingDataSampleFiles/customer-fw-manifest
  inflating: LoadingDataSampleFiles/customer-fw.tbl-000
  inflating: LoadingDataSampleFiles/customer-fw.tbl-000.bak
  inflating: LoadingDataSampleFiles/customer-fw.tbl-001
  inflating: LoadingDataSampleFiles/customer-fw.tbl-002
  inflating: LoadingDataSampleFiles/customer-fw.tbl-003
  inflating: LoadingDataSampleFiles/customer-fw.tbl-004
  inflating: LoadingDataSampleFiles/customer-fw.tbl-005
  inflating: LoadingDataSampleFiles/customer-fw.tbl-006
  inflating: LoadingDataSampleFiles/customer-fw.tbl-007
 extracting: LoadingDataSampleFiles/customer-fw.tbl.log
  inflating: LoadingDataSampleFiles/dwdate-tab.tbl-000
  inflating: LoadingDataSampleFiles/dwdate-tab.tbl-001
  inflating: LoadingDataSampleFiles/dwdate-tab.tbl-002
  inflating: LoadingDataSampleFiles/dwdate-tab.tbl-003
  inflating: LoadingDataSampleFiles/dwdate-tab.tbl-004
  inflating: LoadingDataSampleFiles/dwdate-tab.tbl-005
  inflating: LoadingDataSampleFiles/dwdate-tab.tbl-006
  inflating: LoadingDataSampleFiles/dwdate-tab.tbl-007
  inflating: LoadingDataSampleFiles/part-csv.tbl-000
  inflating: LoadingDataSampleFiles/part-csv.tbl-001
  inflating: LoadingDataSampleFiles/part-csv.tbl-002
  inflating: LoadingDataSampleFiles/part-csv.tbl-003
  inflating: LoadingDataSampleFiles/part-csv.tbl-004
  inflating: LoadingDataSampleFiles/part-csv.tbl-005
  inflating: LoadingDataSampleFiles/part-csv.tbl-006
  inflating: LoadingDataSampleFiles/part-csv.tbl-007

解凍したディレクトリに移動します

$ cd LoadingDataSampleFiles

このディレクトリ内に各種CSVファイルが展開されているのですが、今回はpart-csv.tbl-000という形式のCSVファイルをインポートすることにします。TiDB LightningでCSVファイルをインポートする場合、デフォルトの設定ではファイル名<DB名>.<テーブル名>.<連番>.csvという形式になっている必要があります。今回はblogというDBのpartというテーブルにデータをインポートしてみます。まずpart-csv.tbl-000という形式のファイル名を上記の形式にリネームします。

$ ls part-csv.tbl-* | sed 'p;s/part-csv\.tbl-\([0-9]\{3\}\)/blog.part.\1.csv/' | xargs -n2 mv

リネームしたファイル達をまとめて適当なディレクトリに突っ込みます。

$ mkdir part
$ mv blog.part.00?.csv part/

続いてテーブルを作成するためのDDLを用意します。デフォルトでは<DB名>.<テーブル名>-schema.sqlというファイル名でDDLを用意する必要があります。Redshiftのチュートリアルに記載されている通り以下のSQLを用意しました。

CREATE TABLE part 
(
  p_partkey     INTEGER NOT NULL,
  p_name        VARCHAR(22) NOT NULL,
  p_mfgr        VARCHAR(6),
  p_category    VARCHAR(7) NOT NULL,
  p_brand1      VARCHAR(9) NOT NULL,
  p_color       VARCHAR(11) NOT NULL,
  p_type        VARCHAR(25) NOT NULL,
  p_size        INTEGER NOT NULL,
  p_container   VARCHAR(10) NOT NULL
);

設定ファイルの作成

続いて設定ファイルを作成します。今回は以下のように作成しました。

[lightning]
level = "info"
file = "tidb-lightning-part.log"

[tikv-importer]
backend = "tidb"

[mydumper]
data-source-dir = "<各種CSV/SQLファイルを配置したディレクトリ>"

[mydumper.csv]
separator = ','
delimiter = '"'
header = false
not-null = false
null = '\N'
backslash-escape = true
trim-last-separator = false

[tidb]
host = "TiDB Serverlessのエンドポイント"
port = 4000
user = "<TiDBのユーザー名>"
password = "TiDBのパスワード"
status-port = 10080
tls = "cluster"

[security]
ca-path = "/etc/pki/tls/certs/ca-bundle.crt"

各設定値の詳細についてはTiDB Lightningコンフィグレーションを参照してください。ポイントとしてTiDB ServerlessにTLSで接続できるようsecurityca-pathにCA証明書が配置されたディレクトリを指定しています。

いざインポート

準備が整ったので、いざインポートです。以下のコマンドを実行します。

$ tiup tidb-lightning -config <設定ファイルのパス>

少し待つと、以下のように出力されました。

Checking updates for component tidb-lightning... Timedout (after 2s)
Starting component tidb-lightning: /home/iwata.tomoya/.tiup/components/tidb-lightning/v7.6.0/tidb-lightning -config tidb-lightning-part.toml
Verbose debug logs will be written to tidb-lightning-part.log

+---+------------------------------------------------------------------------------------------------------------------+-------------+--------+
| # | CHECK ITEM                                                                                                       | TYPE        | PASSED |
+---+------------------------------------------------------------------------------------------------------------------+-------------+--------+
| 1 | Source data files size is proper                                                                                 | performance | true   |
+---+------------------------------------------------------------------------------------------------------------------+-------------+--------+
| 2 | the checkpoints are valid                                                                                        | critical    | true   |
+---+------------------------------------------------------------------------------------------------------------------+-------------+--------+
| 3 | the config [mydumper.csv.header] is set to false, and CSV header lines are really not detected in the data files | critical    | true   |
+---+------------------------------------------------------------------------------------------------------------------+-------------+--------+
| 4 | Cluster version check passed                                                                                     | critical    | true   |
+---+------------------------------------------------------------------------------------------------------------------+-------------+--------+
| 5 | Lightning has the correct storage permission                                                                     | critical    | true   |
+---+------------------------------------------------------------------------------------------------------------------+-------------+--------+

tidb lightning exit successfully

諸々のチェック処理完了後にtidb lightning exit successfully と表示されているので、問題なくインポートできてそうです。CSVファイルとテーブルのレコード数の一致を確認してみます。

$ wc -l blog.part.*.csv
   6197 blog.part.000.csv
   6248 blog.part.001.csv
   6331 blog.part.002.csv
   6187 blog.part.003.csv
   6230 blog.part.004.csv
   6318 blog.part.005.csv
   6203 blog.part.006.csv
   6285 blog.part.007.csv
  49999 total
MySQL [blog]> select count(*) from part;
+----------+
| count(*) |
+----------+
|    49999 |
+----------+
1 row in set (0.03 sec)

全件インポート成功しているようです。

簡単にデータの中身も確認してみます

MySQL [blog]> select * from part limit 10;
+-----------+-----------------+--------+------------+-----------+-----------+-------------------------+--------+-------------+
| p_partkey | p_name          | p_mfgr | p_category | p_brand1  | p_color   | p_type                  | p_size | p_container |
+-----------+-----------------+--------+------------+-----------+-----------+-------------------------+--------+-------------+
|        15 | NUL next        |        | MFGR#47    | MFGR#3438 | indigo    | LARGE ANODIZED BRASS    |     45 | LG CASE     |
|        22 | floral beige    | MFGR#4 | MFGR#44    | MFGR#4421 | medium    | PROMO, POLISHED BRASS   |     19 | LG DRUM     |
|        23 | bisque slate    | MFGR#4 | MFGR#41    | MFGR#4137 | firebrick | MEDIUM "BURNISHED" TIN  |     42 | JUMBO JAR   |
|        24 | dim white       | MFGR#4 | MFGR#45    | MFGR#459  | saddle    | MEDIUM , "PLATED" STEEL |     20 | MED CASE    |
|        27 | drab cornflower | MFGR#2 | MFGR#22    | MFGR#2225 | plum      | LARGE, ANODIZED TIN     |     20 | MED PKG     |
|        28 | honeydew dim    | MFGR#2 | MFGR#21    | MFGR#2130 | green     | SMALL, "PLATED" COPPER  |     19 | JUMBO PKG   |
|        42 | azure bisque    | MFGR#1 | MFGR#14    | MFGR#1414 | midnight  | MEDIUM BURNISHED TIN    |     45 | LG BOX      |
|        47 | firebrick red   | MFGR#2 | MFGR#23    | MFGR#2334 | sky       | LARGE BURNISHED BRASS   |     14 | JUMBO PACK  |
|        81 | NUL next        |        | MFGR#23    | MFGR#2320 | misty     | ECONOMY BRUSHED TIN     |     21 | MED BAG     |
|        83 | green deep      | MFGR#1 | MFGR#15    | MFGR#1514 | chocolate | PROMO BURNISHED NICKEL  |     47 | SM CAN      |
+-----------+-----------------+--------+------------+-----------+-----------+-------------------------+--------+-------------+
10 rows in set (0.01 sec)

問題無さそうですね!

設定ファイルで指定したログファイルの中身もざっと確認してみます。詳細は割愛しますが、以下のようなログが出力されていました。

[2024/02/01 08:54:38.075 +00:00] [WARN] [config.go:1574] ["currently only per-task configuration can be applied, global configuration changes can only be made on startup"] ["global config changes"="[lightning.level,lightning.file]"]
[2024/02/01 08:54:38.079 +00:00] [INFO] [info.go:49] ["Welcome to TiDB-Lightning"] [release-version=v7.6.0] [git-hash=52794d985ba6325d75a714d4eaa0838d59425eb6] [git-branch=heads/refs/tags/v7.6.0] [go-version=go1.21.5] [utc-build-time="2024-01-22 14:15:25"] [race-enabled=false]
...略
[2024/02/01 08:54:40.887 +00:00] [INFO] [chunk_process.go:205] ["restore file start"] [table=`blog`.`part`] [engineNumber=0] [fileIndex=5] [path=blog.part.005.csv:0]
[2024/02/01 08:54:41.276 +00:00] [INFO] [chunk_process.go:220] ["restore file completed"] [table=`blog`.`part`] [engineNumber=0] [fileIndex=5] [path=blog.part.005.csv:0] [readDur=9.537705ms] [encodeDur=6.676525ms] [deliverDur=370.78032ms] [checksum="{cksum=0,size=651000,kvs=6318}"] [takeTime=389.562419ms] []
[2024/02/01 08:54:41.277 +00:00] [INFO] [chunk_process.go:205] ["restore file start"] [table=`blog`.`part`] [engineNumber=0] [fileIndex=6] [path=blog.part.006.csv:0]
[2024/02/01 08:54:41.620 +00:00] [INFO] [chunk_process.go:220] ["restore file completed"] [table=`blog`.`part`] [engineNumber=0] [fileIndex=6] [path=blog.part.006.csv:0] [readDur=9.384585ms] [encodeDur=6.327212ms] [deliverDur=325.254007ms] [checksum="{cksum=0,size=638928,kvs=6203}"] [takeTime=343.557928ms] []
[2024/02/01 08:54:41.620 +00:00] [INFO] [chunk_process.go:205] ["restore file start"] [table=`blog`.`part`] [engineNumber=0] [fileIndex=7] [path=blog.part.007.csv:0]
[2024/02/01 08:54:41.979 +00:00] [INFO] [chunk_process.go:220] ["restore file completed"] [table=`blog`.`part`] [engineNumber=0] [fileIndex=7] [path=blog.part.007.csv:0] [readDur=9.579668ms] [encodeDur=6.603673ms] [deliverDur=339.844508ms] [checksum="{cksum=0,size=647739,kvs=6285}"] [takeTime=358.574692ms] []
[2024/02/01 08:54:41.979 +00:00] [INFO] [table_import.go:847] ["encode kv data and write completed"] [table=`blog`.`part`] [engineNumber=0] [read(bytes)=0] [written=5150606] [takeTime=3.045301836s] []
[2024/02/01 08:54:41.979 +00:00] [INFO] [backend.go:326] ["engine close start"] [engineTag=`blog`.`part`:0] [engineUUID=213d5196-a589-50e2-be67-97fb2ef231cf]
[2024/02/01 08:54:41.979 +00:00] [INFO] [backend.go:328] ["engine close completed"] [engineTag=`blog`.`part`:0] [engineUUID=213d5196-a589-50e2-be67-97fb2ef231cf] [takeTime=711ns] []
[2024/02/01 08:54:41.979 +00:00] [INFO] [table_import.go:521] ["restore engine completed"] [table=`blog`.`part`] [engineNumber=0] [takeTime=3.04577893s] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [table_import.go:1320] ["import and cleanup engine start"] [engineTag=`blog`.`part`:0] [engineUUID=213d5196-a589-50e2-be67-97fb2ef231cf]
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:368] ["import start"] [engineTag=`blog`.`part`:0] [engineUUID=213d5196-a589-50e2-be67-97fb2ef231cf] [retryCnt=0]
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:374] ["import completed"] [engineTag=`blog`.`part`:0] [engineUUID=213d5196-a589-50e2-be67-97fb2ef231cf] [retryCnt=0] [takeTime=1.111µs] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:387] ["cleanup start"] [engineTag=`blog`.`part`:0] [engineUUID=213d5196-a589-50e2-be67-97fb2ef231cf]
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:389] ["cleanup completed"] [engineTag=`blog`.`part`:0] [engineUUID=213d5196-a589-50e2-be67-97fb2ef231cf] [takeTime=270ns] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [table_import.go:1350] ["import and cleanup engine completed"] [engineTag=`blog`.`part`:0] [engineUUID=213d5196-a589-50e2-be67-97fb2ef231cf] [takeTime=136.191µs] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [table_import.go:547] ["import whole table completed"] [table=`blog`.`part`] [takeTime=3.046055159s] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:326] ["engine close start"] [engineTag=`blog`.`part`:-1] [engineUUID=4477b674-002d-50ac-bcb0-14689ff5659a]
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:328] ["engine close completed"] [engineTag=`blog`.`part`:-1] [engineUUID=4477b674-002d-50ac-bcb0-14689ff5659a] [takeTime=189ns] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [table_import.go:1320] ["import and cleanup engine start"] [engineTag=`blog`.`part`:-1] [engineUUID=4477b674-002d-50ac-bcb0-14689ff5659a]
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:368] ["import start"] [engineTag=`blog`.`part`:-1] [engineUUID=4477b674-002d-50ac-bcb0-14689ff5659a] [retryCnt=0]
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:374] ["import completed"] [engineTag=`blog`.`part`:-1] [engineUUID=4477b674-002d-50ac-bcb0-14689ff5659a] [retryCnt=0] [takeTime=520ns] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:387] ["cleanup start"] [engineTag=`blog`.`part`:-1] [engineUUID=4477b674-002d-50ac-bcb0-14689ff5659a]
[2024/02/01 08:54:41.980 +00:00] [INFO] [backend.go:389] ["cleanup completed"] [engineTag=`blog`.`part`:-1] [engineUUID=4477b674-002d-50ac-bcb0-14689ff5659a] [takeTime=210ns] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [table_import.go:1350] ["import and cleanup engine completed"] [engineTag=`blog`.`part`:-1] [engineUUID=4477b674-002d-50ac-bcb0-14689ff5659a] [takeTime=125.791µs] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [import.go:1708] ["restore table completed"] [table=`blog`.`part`] [takeTime=3.059169817s] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [import.go:1411] ["cancel periodic actions"] [do=false]
[2024/02/01 08:54:41.980 +00:00] [INFO] [import.go:1669] ["restore all tables data completed"] [takeTime=3.059444146s] []
[2024/02/01 08:54:41.980 +00:00] [INFO] [import.go:1911] ["skip full compaction"]
[2024/02/01 08:54:41.980 +00:00] [INFO] [import.go:1266] ["everything imported, stopping periodic actions"]
[2024/02/01 08:54:41.980 +00:00] [INFO] [import.go:2062] ["clean checkpoints start"] [keepAfterSuccess=remove] [taskID=1706777678079010277]
[2024/02/01 08:54:41.981 +00:00] [INFO] [import.go:2070] ["clean checkpoints completed"] [keepAfterSuccess=remove] [taskID=1706777678079010277] [takeTime=28.557µs] []
[2024/02/01 08:54:41.981 +00:00] [INFO] [import.go:571] ["the whole procedure completed"] [takeTime=3.541304224s] []
[2024/02/01 08:54:41.981 +00:00] [INFO] [main.go:108] ["tidb lightning exit"] [finished=true]

まとめ

TiDB Lightningを使ってCSVファイルをTiDBにインポートしてみました。今回は非常にシンプルなパターンを検証しましたが、今後様々なバリエーションや異常系についても検証してみたいと思います。

参考