MySQL shell을 통한 Table dump&load

기존 mysqldump 같은 Logical backup의 단점은 사용은 간편하나, dump&load 시 single thread를 사용하여 굉장히 느리다는 단점이 있는데
mysqlshell 의 dump , load 기능을 사용하면 logical 백업도 빠르게 수행할 수 있습니다.
이번 글에서는 mysqlshell을 통해 특정 테이블만 dump&load test를 해보겠습니다. 물론 instance, schema 단위로도 백업 가능합니다.
mysqlshell은 8.0 뿐만 아니라 5.6, 5.7에서도 사용이 가능합니다. ( backup lock 등 버전 별 기능차이로 인해 안되는 기능도 있음)

mysqlshell 설치

$ sudo yum install mysql-shell
Loaded plugins: fastestmirror, security
Setting up Install Process
Loading mirror speeds from cached hostfile
Resolving Dependencies
--> Running transaction check
---> Package mysql-shell.x86_64 0:8.0.23-1.el6 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================================================================================================================================================================================================================
 Package                                                        Arch                                                      Version                                                            Repository                                                                    Size
================================================================================================================================================================================================================================================================================
Installing:
 mysql-shell                                                    x86_64                                                    8.0.23-1.el6                                                       mysql-tools-community-el6                                                     32 M

Transaction Summary
================================================================================================================================================================================================================================================================================
Install       1 Package(s)

Total download size: 32 M
Installed size: 141 M
Is this ok [y/N]: y
Downloading Packages:
mysql-shell-8.0.23-1.el6.x86_64.rpm                                                                                                                                                                                                                      |  32 MB     00:00
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing : mysql-shell-8.0.23-1.el6.x86_64                                                                                                                                                                                                                              1/1
  Verifying  : mysql-shell-8.0.23-1.el6.x86_64                                                                                                                                                                                                                              1/1

Installed:
  mysql-shell.x86_64 0:8.0.23-1.el6

Complete!

mysqlsh 접속

$ mysqlsh

MySQL Shell 8.0.23

Copyright (c) 2016, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
Other names may be trademarks of their respective owners.

Type '\help' or '\?' for help; '\quit' to exit.
 MySQL  JS >
 MySQL  JS > \connect testuser@localhost:13306
Creating a session to 'testuser@localhost:13306'
Fetching schema names for autocompletion... Press ^C to stop.
Your MySQL connection id is 982607
Server version: 8.0.16 MySQL Community Server - GPL
No default schema selected; type \use <schema> to set one.
 MySQL  localhost:13306 ssl  JS > \use testdb
Default schema set to `testdb`.
 MySQL  localhost:13306 ssl  testdb  JS >

mysqlsh dump

util.dumpTables

 MySQL  localhost:13306 ssl  testdb  JS > util.
checkForServerUpgrade()  dumpInstance()           dumpTables()             help()                   importTable()
configureOci()           dumpSchemas()            exportTable()            importJson()             loadDump()


 MySQL  localhost:13306 ssl  testdb  JS > util.dumpTables("testdb",["tb_test"],"/home1/testuser/db/work/")
Acquiring global read lock
Global read lock acquired
Gathering information - done
All transactions have been started
Locking instance for backup
Global read lock has been released
Writing global DDL files
Preparing data dump for table `testdb`.`tb_test`
Writing DDL for schema `testdb`
Writing DDL for table `testdb`.`tb_test`
Data dump for table `testdb`.`tb_test` will be chunked using column `tb_test_no`
Running data dump using 4 threads.
NOTE: Progress information uses estimated values and may not be accurate.
Data dump for table `testdb`.`tb_test` will be written to 247 files
1 thds dumping - 106% (79.56M rows / ~74.49M rows), 672.89K rows/s, 118.70 MB/s uncompressed, 19.94 MB/s compressed
Duration: 00:02:21s
Schemas dumped: 1
Tables dumped: 1
Uncompressed data size: 14.03 GB
Compressed data size: 2.36 GB
Compression ratio: 5.9
Rows written: 79564325
Bytes written: 2.36 GB
Average uncompressed throughput: 98.80 MB/s
Average compressed throughput: 16.62 MB/s
  • 14GB 데이터를 PK 기준으로 chunk 단위로 4개 thread를 활용하여 백업을 수행함

  • 백업 후 결과는 2.36GB이며 zst 파일로 압축 수행함

  • 2분20초 소요됨

  • 백업을 위해 read lock & backup lock 을 순간적으로 걸게됨

2021-04-25T05:56:27.492374Z     982893 Query    FLUSH TABLES
2021-04-25T05:56:27.500004Z     982893 Query    FLUSH TABLES WITH READ LOCK
2021-04-25T05:56:27.500186Z     982893 Query    SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
2021-04-25T05:56:27.500280Z     982893 Query    START TRANSACTION WITH CONSISTENT SNAPSHOT
2021-04-25T05:56:27.558601Z     982893 Query    LOCK INSTANCE FOR BACKUP
2021-04-25T05:56:27.558715Z     982893 Query    UNLOCK TABLES
  • default 로 4개의 thread 가 PK를 기준으로 chunk 단위로 백업을 수행함
mysql> select id,user,state,info from information_schema.processlist where user='testuser'\G;

*************************** 2. row ***************************
   id: 982896
 user: testuser
state: Sending data
 info: SELECT SQL_NO_CACHE `tb_test_no`,`tb_test_ymdt`,`tb_test_bundle_no`,`origin_tb_test_no`,`order_no`,`member_seq`,`trade_type`,`tb_test_amount`,`taxable_amount`,`taxfree_amount`,`vat_amount`,`service_amount`,`cancel_possible_amount`,`pg_code`,`pg_cp_code`,`pg_id` FROM `testdb`.`tb_test` WHERE `tb_test_no` BETWEEN '202010012712327236' AND '202010022712820928' ORDER BY `tb_test_no`,`tb_test_ymdt` /* mysqlsh dumpTables, dumping table `testdb`.`tb_test`, chunk ID: 221 */
*************************** 3. row ***************************
   id: 982897
 user: testuser
state: Sending data
 info: SELECT SQL_NO_CACHE `tb_test_no` FROM `testdb`.`tb_test` WHERE `tb_test_no` > '202010182721965634' ORDER BY `tb_test_no`,`tb_test_ymdt` LIMIT 323231,1 /* mysqlsh dumpTables, chunking table `testdb`.`tb_test`, chunk ID: 242 */

*************************** 5. row ***************************
   id: 982894
 user: testuser
state: Sending to client
 info: SELECT SQL_NO_CACHE `tb_test_no`,`tb_test_ymdt`,`tb_test_bundle_no`,`origin_tb_test_no`,`order_no`,`member_seq`,`trade_type`,`tb_test_amount`,`taxable_amount`,`taxfree_amount`,`vat_amount`,`service_amount`,`cancel_possible_amount`,`pg_code`,`pg_cp_code`,`pg_id` FROM `testdb`.`tb_test` WHERE `tb_test_no` BETWEEN '202009302711832424' AND '202010012712327233' ORDER BY `tb_test_no`,`tb_test_ymdt` /* mysqlsh dumpTables, dumping table `testdb`.`tb_test`, chunk ID: 220 */
*************************** 6. row ***************************
   id: 982895
 user: testuser
state: Sending data
 info: SELECT SQL_NO_CACHE `tb_test_no`,`tb_test_ymdt`,`tb_test_bundle_no`,`origin_tb_test_no`,`order_no`,`member_seq`,`trade_type`,`tb_test_amount`,`taxable_amount`,`taxfree_amount`,`vat_amount`,`service_amount`,`cancel_possible_amount`,`pg_code`,`pg_cp_code`,`pg_id` FROM `testdb`.`tb_test` WHERE `tb_test_no` BETWEEN '202010022712820929' AND '202010032713303142' ORDER BY `tb_test_no`,`tb_test_ymdt` /* mysqlsh dumpTables, dumping table `testdb`.`tb_test`, chunk ID: 222 */
6 rows in set (0.00 sec)

ERROR:
No query specified

dump 결과

$ ls -ltr
total 2308840
-rw-r----- 1 testuser testuser     977 Apr 25 14:56 testdb@tb_test.json
-rw-r----- 1 testuser testuser     235 Apr 25 14:56 testdb.json
-rw-r----- 1 testuser testuser     240 Apr 25 14:56 @.sql
-rw-r----- 1 testuser testuser     240 Apr 25 14:56 @.post.sql
-rw-r----- 1 testuser testuser     601 Apr 25 14:56 @.json
-rw-r----- 1 testuser testuser     480 Apr 25 14:56 testdb.sql
-rw-r----- 1 testuser testuser    3951 Apr 25 14:56 testdb@tb_test.sql
-rw-r----- 1 testuser testuser     432 Apr 25 14:56 testdb@tb_test@0.tsv.zst.idx
-rw-r----- 1 testuser testuser 9221571 Apr 25 14:56 testdb@tb_test@0.tsv.zst
-rw-r----- 1 testuser testuser     432 Apr 25 14:56 testdb@tb_test@1.tsv.zst.idx
-rw-r----- 1 testuser testuser 9258971 Apr 25 14:56 testdb@tb_test@1.tsv.zst
-rw-r----- 1 testuser testuser     432 Apr 25 14:56 testdb@tb_test@2.tsv.zst.idx
-rw-r----- 1 testuser testuser 9207598 Apr 25 14:56 testdb@tb_test@2.tsv.zst
-rw-r----- 1 testuser testuser     432 Apr 25 14:56 testdb@tb_test@3.tsv.zst.idx
-rw-r----- 1 testuser testuser 9337070 Apr 25 14:56 testdb@tb_test@3.tsv.zst
-rw-r----- 1 testuser testuser     432 Apr 25 14:56 testdb@tb_test@4.tsv.zst.idx
-rw-r----- 1 testuser testuser 9377277 Apr 25 14:56 testdb@tb_test@4.tsv.zst
-rw-r----- 1 testuser testuser     432 Apr 25 14:56 testdb@tb_test@5.tsv.zst.idx
-rw-r----- 1 testuser testuser 9436864 Apr 25 14:56 testdb@tb_test@5.tsv.zst
-rw-r----- 1 testuser testuser     432 Apr 25 14:56 testdb@tb_test@6.tsv.zst.idx
-rw-r----- 1 testuser testuser 9296173 Apr 25 14:56 testdb@tb_test@6.tsv.zst
.
.
.
-rw-r----- 1 testuser testuser 1482164 Apr 25 14:58 testdb@tb_test@@246.tsv.zst
-rw-r----- 1 testuser testuser     440 Apr 25 14:58 testdb@tb_test@243.tsv.zst.idx
-rw-r----- 1 testuser testuser 9639678 Apr 25 14:58 testdb@tb_test@243.tsv.zst
-rw-r----- 1 testuser testuser     440 Apr 25 14:58 testdb@tb_test@245.tsv.zst.idx
-rw-r----- 1 testuser testuser 9521770 Apr 25 14:58 testdb@tb_test@245.tsv.zst
-rw-r----- 1 testuser testuser   12930 Apr 25 14:58 @.done.json
  • chunk 단위로 분할되어 떨어지며 zstd 형태로 압축되어있음
  • json, sql 파일 형태로 백업 관련 테이블의 DDL 이나 백업 메타정보등이 기록됨

mysqlsh load

util.loadDump()

 MySQL  localhost:13306 ssl  testdb  JS > util.loadDump("/home1/testuser/db/work/",{schema:"testdb2"})
Loading DDL and Data from '/home1/testuser/db/work/' using 4 threads.
Opening dump...
Target is MySQL 8.0.16. Dump was produced from MySQL 8.0.16
Checking for pre-existing objects...
Executing common preamble SQL
[Worker001] Executing DDL script for `testdb2`.`tb_test`
[Worker002] testdb@tb_test@0.tsv.zst: Records: 323232  Deleted: 0  Skipped: 0  Warnings: 0
[Worker003] testdb@tb_test@1.tsv.zst: Records: 323232  Deleted: 0  Skipped: 0  Warnings: 0
[Worker000] testdb@tb_test@2.tsv.zst: Records: 323232  Deleted: 0  Skipped: 0  Warnings: 0
[Worker001] testdb@tb_test@3.tsv.zst: Records: 323232  Deleted: 0  Skipped: 0  Warnings: 0
.
.
.
[Worker003] testdb@tb_test@242.tsv.zst: Records: 323232  Deleted: 0  Skipped: 0  Warnings: 0
[Worker000] testdb@tb_test@243.tsv.zst: Records: 323232  Deleted: 0  Skipped: 0  Warnings: 0
[Worker003] testdb@tb_test@@246.tsv.zst: Records: 49253  Deleted: 0  Skipped: 0  Warnings: 0
[Worker001] testdb@tb_test@244.tsv.zst: Records: 323232  Deleted: 0  Skipped: 0  Warnings: 0
[Worker002] testdb@tb_test@245.tsv.zst: Records: 323232  Deleted: 0  Skipped: 0  Warnings: 0

Executing common postamble SQL

247 chunks (79.56M rows, 14.03 GB) for 1 tables in 1 schemas were loaded in 27 min 27 sec (avg throughput 8.52 MB/s)
0 warnings were reported during the load.
  • 4개 thread에서 병렬로 수행됨
mysql> select id,user,state,info from information_schema.processlist where user='testuser'\G;


*************************** 2. row ***************************
   id: 984376
 user: testuser
state: executing
 info: /* mysqlsh loadDump(), thread 1, table `testdb2`.`tb_test`, chunk ID: 3 */ LOAD DATA LOCAL INFILE '/home1/testuser/db/work/testdb@tb_test@3.tsv.zst' REPLACE INTO TABLE `testdb2`.`tb_test` CHARACTER SET 'utf8mb4' FIELDS TERMINATED BY '     ' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n' (`tb_test_no`, `tb_test_ymdt`, `tb_test_bundle_no`, `origin_tb_test_no`, `order_no`, `member_seq`, `trade_type`, `tb_test_amount`, `taxable_amount`, `taxfree_amount`, `vat_amount`, `service_amount`, `cancel_possible_amount`, `pg_code`, `pg_cp_code`, `pg_id`)
*************************** 3. row ***************************
   id: 984377
 user: testuser
state: executing
 info: /* mysqlsh loadDump(), thread 2, table `testdb2`.`tb_test`, chunk ID: 4 */ LOAD DATA LOCAL INFILE '/home1/testuser/db/work/testdb@tb_test@4.tsv.zst' REPLACE INTO TABLE `testdb2`.`tb_test` CHARACTER SET 'utf8mb4' FIELDS TERMINATED BY '     ' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n' (`tb_test_no`, `tb_test_ymdt`, `tb_test_bundle_no`, `origin_tb_test_no`, `order_no`, `member_seq`, `trade_type`, `tb_test_amount`, `taxable_amount`, `taxfree_amount`, `vat_amount`, `service_amount`, `cancel_possible_amount`, `pg_code`, `pg_cp_code`, `pg_id`)
*************************** 4. row ***************************
   id: 984378
 user: testuser
state: executing
 info: /* mysqlsh loadDump(), thread 3, table `testdb2`.`tb_test`, chunk ID: 1 */ LOAD DATA LOCAL INFILE '/home1/testuser/db/work/testdb@tb_test@1.tsv.zst' REPLACE INTO TABLE `testdb2`.`tb_test` CHARACTER SET 'utf8mb4' FIELDS TERMINATED BY '     ' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n' (`tb_test_no`, `tb_test_ymdt`, `tb_test_bundle_no`, `origin_tb_test_no`, `order_no`, `member_seq`, `trade_type`, `tb_test_amount`, `taxable_amount`, `taxfree_amount`, `vat_amount`, `service_amount`, `cancel_possible_amount`, `pg_code`, `pg_cp_code`, `pg_id`)

*************************** 6. row ***************************
   id: 984375
 user: testuser
state: executing
 info: /* mysqlsh loadDump(), thread 0, table `testdb2`.`tb_test`, chunk ID: 2 */ LOAD DATA LOCAL INFILE '/home1/testuser/db/work/testdb@tb_test@2.tsv.zst' REPLACE INTO TABLE `testdb2`.`tb_test` CHARACTER SET 'utf8mb4' FIELDS TERMINATED BY '     ' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n' (`tb_test_no`, `tb_test_ymdt`, `tb_test_bundle_no`, `origin_tb_test_no`, `order_no`, `member_seq`, `trade_type`, `tb_test_amount`, `taxable_amount`, `taxfree_amount`, `vat_amount`, `service_amount`, `cancel_possible_amount`, `pg_code`, `pg_cp_code`, `pg_id`)
6 rows in set (0.00 sec)

빠른이유 ?

  • 순서대로 mysqldump, mysqlpump,mysqldumper, mysqlshell 의 성능 그래프
  • mysqlshell은 테이블을 chunk 단위로 잘게 쪼개서 dump & load를 수행하고 mysqldumper에 비해 별도의 스케쥴링 로직이 있어 효율적으로 작업이 수행됨
  • 추가로 MySQL 8.0.21 버전부터는 ALTER INSTANCE DISABLE INNODB REDO_LOG 커맨드를 통해 redo log 에 의한 I/O 병목을 줄일 수 있음