TiDB Lightningを使ってCSVファイルからTiDBにデータをインポートしてみた
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で接続できるようsecurity
のca-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にインポートしてみました。今回は非常にシンプルなパターンを検証しましたが、今後様々なバリエーションや異常系についても検証してみたいと思います。