TokuDB 使用问题汇总

简单介绍

TokuDB 引擎提供很好的压缩比(笔者环境中的数据在默认的 zlib 设置下压缩比大致为 InnoDB:TokuDB ~= 5:1), 以及快速增加, 删除, 重命名列, 热更索引等特性, 这些特性很适合日志记录类的表来使用, 该类表可以仅做主从复制, 不做定期的备份. 如果需要备份可以参考 AliSQLBackup, 其基于 xtrabackup-2.3.5版本, 可以同时备份 InnoDB 和 TokuDB 引擎的表. 另外innodb_buffer_pool_sizetokudb_cache_size 两个参数的值需要按需分配, 如果没有多少 InnoDB 表, 最好调小 innodb_buffer_pool_size 的值.

更多基础的特性说明可以参考以前的文章: TokuDB 使用简单说明. 下述的问题列表则主要介绍在使用 TokuDB 的过程中碰到的一些问题, 后期碰到的问题也会在该列表中持续更新.

问题列表

修改分区表耗时长问题处理

tokudb 修改分区表耗时长问题处理

转换 InnoDB 大表到 TokuDB 崩溃问题

在将一个 80G 的 InnoDB 表转为 TokuDB 的时候出现以下错误, 数据库进程也崩溃退出:

Feb 18 12:25:56 db1 mysqld-3328: /mnt/workspace/percona-server-5.6-binaries-release/label_exp/centos6-64/percona-server-5.6.38-83.0/storage/tokudb/PerconaFT/src/load
er.cc:471 toku_loader_abort: Assertion `r == 0' failed (errno=28) (r=28)
Feb 18 12:25:56 db1 mysqld-3328: : No space left on device
Feb 18 12:25:56 db1 mysqld-3328: Backtrace: (Note: toku_do_assert=0x0x7fb7404daa10)
Feb 18 12:25:56 db1 mysqld-3328: /opt/Percona-Server-5.6.38-rel83.0-Linux.x86_64.ssl101/lib/mysql/plugin/ha_tokudb.so(_Z19db_env_do_backtraceP8_IO_FILE+0x1b)[0x7fb7404d3adb]
...

从上面的错误来看是因为磁盘空间不足而引起的, 修改表的时候需要保存一些临时文件到 MySQL 的 tmpdir 指定的目录中, 上面的实例中 tokudb_tmp_dir 的值为 /dev/shm, 使用了 tmpfs 来存储临时的文件. 该系统下 /dev/shm 的可用空间仅有 32G, 如下所示:

# df -hl /dev/shm
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           32G   16G   83G  50% /dev/shm

这也就说明了临时文件的大小超过了 /dev/shm 设置的容量. 如下所示为修改表的过程中 /dev/shm 中产生的临时文件:

# ls -hl /dev/shm/ | less
total 18G
-rw------- 1 mysql mysql    0 Feb 18 10:15 __tokudb_lock_dont_delete_me_temp
-rw------- 1 mysql mysql 1.1M Feb 18 13:10 tokuld00TjtL
-rw------- 1 mysql mysql 1.3M Feb 18 13:24 tokuld022Xnh
-rw------- 1 mysql mysql 1.2M Feb 18 13:13 tokuld028HPP
......

解决该问题可以使用下面两种方式中的一种:

临时增大 /dev/shm 设备的大小

可以临时增大设备大小, 更改完成后再改为 32G 即可. 该设备的最大有效大小受 kernel.shmmax 参数控制. 默认为系统内存的大小.

mount -o remount,size=64G,noatime /dev/shm

修改 tokudb_tmp_dir 参数路径信息, 重启实例

也可以修改 tmpdir 的路径, 确保有足够的大小, 更改完成后再修改回来.

修改 tokudb_data_dir 参数出现不能找到文件错误

在设置完 tokudb 参数后, 所有创建的表都通过 tokudb.directory 保存对应的映射关系, 如下所示:

mysql root@[localhost:s3340 information_schema] > select dictionary_name, internal_file_name, table_name from TokuDB_file_map; 
+---------------------------+------------------------------------------------------------------------------------+------------+
| dictionary_name           | internal_file_name                                                                 | table_name |
+---------------------------+------------------------------------------------------------------------------------+------------+
| ./percona/user-key-idx_id | /export/mysql/node3340/data/tokudb_data/_percona_user_key_idx_id_6_3_1d_B_0.tokudb | user       |
| ./percona/user-main       | /export/mysql/node3340/data/tokudb_data/_percona_user_main_4_2_1d.tokudb           | user       |
| ./percona/user-status     | /export/mysql/node3340/data/tokudb_data/_percona_user_status_4_1_1d.tokudb         | user       |
+---------------------------+------------------------------------------------------------------------------------+------------+
.....

这种绝对路径的映射关系是固定的不可修改的. 这种方式意味着我们在做备份恢复的时候不能修改 tokudb_data_dir 参数值, 对应的绝对路径也不能修改. 如下所示, 在更改 tokudb_data_dir 为不同的路径后, 查询表出现以下错误, 因为映射关系对应不上:

ERROR 1017 (HY000): Can't find file 'xxxxx'

这点限制没有 InnoDB 方便, 也带来了很多不便, 比如我们想更换实例端口, 变更目录等都不可行, 除非 tokudb_data_dir 指定为相对路径的目录, 变更起来就很方便, 如下所示:

tokudb_data_dir = .tokudb_data

这种参数即为相对目录, 具体的路径则为 $mysql_datadir/.tokudb_data, 查看映射关系如下:

mysql root@[localhost:s3342 information_schema] > select * from TokuDB_file_map;
+----------------------------+-----------------------------------------------------+--------------+------------+-----------------------+
| dictionary_name            | internal_file_name                                  | table_schema | table_name | table_dictionary_name |
+----------------------------+-----------------------------------------------------+--------------+------------+-----------------------+
| ./percona/user_find-main   | .tokudb_data/_percona_user_find_main_4_2_1d.tokudb   | percona      | user_find  | main                  |
| ./percona/user_find-status | .tokudb_data/_percona_user_find_status_4_1_1d.tokudb | percona      | user_find  | status                |
+----------------------------+-----------------------------------------------------+--------------+------------+-----------------------+

也可以不设置 tokudb_data_dir 参数, 仅开启 tokudb_dir_per_db, 这样每个 tokudb 的数据在各自的 db 中存放, 如下所示:

mysql root@[localhost:s3342 information_schema] > select * from TokuDB_file_map;
+-----------------------+-------------------------------------+--------------+------------+-----------------------+
| dictionary_name       | internal_file_name                  | table_schema | table_name | table_dictionary_name |
+-----------------------+-------------------------------------+--------------+------------+-----------------------+
| ./percona/user-main   | ./percona/user_main_a_2_1d.tokudb   | percona      | user       | main                  |
| ./percona/user-status | ./percona/user_status_a_3_1d.tokudb | percona      | user       | status                |
+-----------------------+-------------------------------------+--------------+------------+-----------------------+

tokudb 备份问题

在开启 tokudb_dir_per_db 参数后, AliSQLBackup 无法对 tokudb 表进行备份. 从代码的说明commit-36573de, AliSQLBackup 工具是假定 tokudb 日志和表数据都在 MySQL 的 datadir 目录下(也可以是 datadir 下的一个子目录)才实现的备份功能, 如果开启了 tokudb_per_db(现在新版默认开启, 不过和 tokudb_data_dir 目录互斥), 则需要对 AliSQLBackup 进行修改才能实现备份功能, 如下所示:

// AliSQLBackup
 412 /************************************************************************
 413 Get next TokuDB file in datadir.
 414 TokuDB data and log files are both put in datadir, not under database dir(like
 415 InnoDB or MyISAM), so we add this new function alternative to
 416 datadir_iter_next(). Maybe in future, TokuDB data files will put in database         // <== 需要 tokudb 日志和表数据都在 datadir 目录下或 datadir 下的一个子目录中
 417 dir, we'll remove this function. */
 418 static
 419 bool
 420 datadir_iter_next_tokudb(datadir_iter_t *it, datadir_node_t *node)
 421 {
......
 447         /* datadir iterate finished */
 448         return(false);
 449 }

不过从 github 上看, 该工程已经停止了维护, 实际使用的时候, 如果需要备份最好不要开启 tokudb_per_db 选项, 如果开启了就使用 tokudb-xtrabackup 进行备份, 不过该工具不支持子目录, 在设定了 tokudb_data_dir 选项的时候备份会失败, 刚好和 AliSQLBackup 相反, 另外其基于 xtrabackup-2.4 版本, 可以备份 5.5 ~ 5.7 的数据库, 更为通用.

备份不一致问题

在使用上述的 xtrabackup 备份的时候, PerconaFT 在备份期间可能由于数据更新而使 ft_closeFT header 变更, 进而导致备份的数据不一致, 更多参考 FT-701. 官方已确认此问题, 不过从 pull-331 来看, 并没有合并到官方代码中.

在没有使用官方 tokudb-backup 的时候, 建议手动合并此补丁到 PerconaFT, 源文件参见路径 src/storage/tokudb/PerconaFT 并重新编译 TokuDB.

temp 锁占用不能启动问题

在一台主机上运行多个 TokuDB 实例的时候, 出现了以下错误:

Feb 18 14:22:27 dbinfo8 mysqld-3342: 2019-02-19 14:22:27 7f2b6dff9700 InnoDB: Loading buffer pool(s) from .//ib_buffer_pool
Feb 18 14:22:27 dbinfo8 mysqld-3342: Couldn't start tokuft because some other tokuft process is using the same directory [/dev/shm] for [temp]
Feb 18 14:22:27 dbinfo8 mysqld-3342: 2019-02-19 14:22:27 37280 [ERROR] TokuDB unknown error 11
Feb 18 14:22:27 dbinfo8 mysqld-3342: 2019-02-19 14:22:27 37280 [ERROR] Plugin 'TokuDB' init function returned error.
Feb 18 14:22:27 dbinfo8 mysqld-3342: 2019-02-19 14:22:27 37280 [ERROR] Plugin 'TokuDB' registration as a STORAGE ENGINE failed.

/dev/shm 目录中仅包含一个文件/dev/shm/__tokudb_lock_dont_delete_me_temp, 使用 lsof 查看使用的进程如下:

# lsof /dev/shm/__tokudb_lock_dont_delete_me_temp    
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
mysqld  35338 mysql   15uW  REG   0,19        0 11786530 /dev/shm/__tokudb_lock_dont_delete_me_temp

可以看到已经有进程占用了 __tokudb_lock_dont_delete_me_temp, 该文件用来保护 TokuDB 的临时文件, 避免多个进程并发访问的时候操作同一临时目录的文件. 由此来看出现错误的原因是另一个 TokuDB 实例的 tmp 目录设置的也是 /dev/shm, 将 tokudb_tmp_dir 修改掉即可解决该问题. 该参数留空的话则 __tokudb_lock_dont_delete_me_temp 会存放到 tokudb_data_dir 的目录中.

cpu 占用问题

在更新较多的实例中, 系统每隔一定时间(比如60s) cpu 的占用就会出现一次小高峰,这是由 tokudb 的 checkpoint 机制引起的, 在指定 tokudb_checkpointing_period(默认 60s) 的时候会处理一次 checkpoint, 每次处理的时候需要按 tokudb 表的压缩算法对数据进行压缩, 如下 perf 的数据所示, 每次 cpu 小高峰的时候, 都会启用 libz 库文件进行压缩, 压缩算法由创建表时的 row_format 选项指定, 默认 zlib 压缩算法, 如果更新很多, 可以调小该参数的值, 避免一次处理过多:

Samples: 461K of event 'cycles:ppp', Event count (approx.): 97192185619                                                                                                                                                                      
Overhead  Shared Object                 Symbol                                                                                                                                                                                               
  10.32%  libz.so.1.2.7                 [.] 0x0000000000002d11
   2.91%  libz.so.1.2.7                 [.] 0x0000000000002cf2
   1.82%  libc-2.17.so                  [.] __memmove_ssse3_back
   1.82%  libz.so.1.2.7                 [.] 0x0000000000002d19
   1.81%  libz.so.1.2.7                 [.] 0x0000000000002e30

data_free 过大问题

详见: tokudb 表信息 data_free 引起 psam 崩溃问题处理.

内存占用过高问题

详见: TokuDB 内存占用过高问题处理

DDL 修改大表问题

TokuDB 使用简单说明 我们提到了 TokuDB 在线更改列的一些问题, 更多可以参考 MySQL 高压缩引擎 TokuDB 揭秘. 简单而言, 如果要修改表的字段, DDL 仅对表有轻微的全局阻塞, 其很快执行完, 其实际的数据修改却是放到了后台执行, 这种模式受 tokudb_disable_slow_alter 参数影响, 默认为 OFF, 表示在后台慢慢修改. 更多此类说明见 hot-column-addition-and-delete-part-1hot-column-addition-and-delete-part-2.

不过对于大表的修改而言, 轻微阻塞到底是多久, 或者说多久能执行完 DDL 操作, 我们需要做一些验证, 如下所示为对一个含有 20 亿条记录(120G) 大小的 TokuDB 表进行增删字段操作, 简单测试如下:

mysql > ALTER TABLE _old_text_data ADD COLUMN `bid`  varchar(32) NULL AFTER `sense`;
Query OK, 0 rows affected (0.85 sec)
Records: 0  Duplicates: 0  Warnings: 0


mysql > ALTER TABLE _old_text_data drop COLUMN `bid`;  
Query OK, 0 rows affected (0.69 sec)
Records: 0  Duplicates: 0  Warnings: 0

可以看到 rows affected 均为 0, 这应该就是将修改操作放到了后台. 不过这个时间还是很乐观的, 最多也就阻塞 0.85 秒. 不过在实际的使用中, 考虑到主机配置的不同, 外加我们也碰到过 tokudb 修改分区表耗时长问题处理 的问题, 在对线上的表操作的时候, 建议先在备库中做一些操作测试, 以免碰到不可预知的错误.