TokuDB 内存占用过高问题处理

问题说明

近期参考了 xelabs-tokudb-wiki 将 TokuDB 引擎和 jemalloc 内置到 MySQL 中, 在实际的使用中发现 MySQL 的实例占用的内存特别高, 如下所示的配置:

Centos 7 - 3.10.0-862.14.4.el7.x86_64
percona-server-5.6.45-86.1

my.cnf 配置:
   innodb_buffer_size = 16G
   tokudb_cache_size  = 48G

由于内置了 TokuDB 和 jemalloc, 所以运行数据库的系统未安装 jemalloc. 在使用的过程中内存占用很高, 如下所示:

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                 
 21500 mysql     20   0 141.3g 104.7g   7600 S  24.8 23.9 595:38.31 mysqld

几台实例都出现此类情况, 总的内存接近 innodb_buffer_size + 2 * tokudb_cache_size, 正常的情况应该在 innodb_buffer_size + tokudb_cache_size 上下浮动.

备注: 几台实例均为 slave, 处于空闲状态. 并未做 load data 等批量更新之类的操作.

分析处理

最开始我们以为 jemalloc 存在 bug-1128 而引起此类问题, 所以将内置的 jemalloc 变更为以下版本, 在系统未安装 jemalloc-3.6.0-1.el7.x86_64 的情况下依旧出现内存高的问题, 如下所示:

jemalloc-3.6.0
jemalloc-4.2.1
jemalloc-5.2.1

参考以下链接, 可以看到 tokudb 占用内存高的问题反馈已经很多, 不过都没有明确的处理:

jira-mariadb-13403
jire-mariadb-13785
jire-percona-3366

在实际的测试中, 仅发现以下方式可以解决内存高的问题, 更多见 solved-high-mem

We solved the issue by adding this to our cnf file:

[mysqld_safe]
malloc-lib= /path/to/jemalloc

We compiled MariaDB from source, with non-standard configuration files. The problem might have been self inflicted.

mysqld_safe 的代码中, 存在默认加载 libjemalloc 的行为, 所以对上述的配置而言, 只要系统安装了 jemalloc, 是否指定 malloc-lib 选项, 都不会有什么区别, 如下所示:

/opt/percona-server-5.6.45-86.1-linux-x86_64/bin/mysqld_safe

...
load_jemalloc=1
...

      --malloc-lib=*)
        set_malloc_lib "$val"
        load_jemalloc=0
        ;;
...
if test $load_jemalloc -eq 1
then
  for libjemall in "${MY_BASEDIR_VERSION}/lib/mysql" "/usr/lib64" "/usr/lib/x86_64-linux-gnu" "/usr/lib"; do
    if [ -r "$libjemall/libjemalloc.so.1" ]; then
      add_mysqld_ld_preload "$libjemall/libjemalloc.so.1"
      break
    fi  
  done
fi

Centos 7 中, 如果安装了 jemalloc-3.6.0-1.el7.x86_64 的 rpm 包, 默认会以 /usr/lib64/libjemalloc.so.1 文件设置 LD_PRELOAD, mysqld 进程启动的时候会预加载此文件, 以 jemalloc 来管理运行时需要的内存分配.

我们通过 valgrind-mysql 来分析是否预加载 /usr/lib64/libjemalloc.so.1 的行为, 不过遗憾的是, 两种方式并未看到异常, 整体的内存分布大致如下

99.87% (7,309,226,237B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->73.33% (5,367,098,317B) 0xF7F9E8: os_malloc_aligned(unsigned long, unsigned long) (os_malloc.cc:222)
| ->73.33% (5,367,098,317B) 0xF7F4AE: toku_xmalloc_aligned(unsigned long, unsigned long) (memory.cc:402)
...
->18.56% (1,358,583,896B) 0xB59197: pfs_malloc(unsigned long, int) (pfs_global.cc:57)
| ->16.94% (1,239,646,296B) 0xB59366: pfs_malloc_array(unsigned long, unsigned long, int) (pfs_global.cc:144)
| | ->04.60% (336,370,400B) 0xB5ABC2: init_instruments(PFS_global_param const*) (pfs_instr.cc:407)

备注: 分析内存的时候, 需要使用 valgrind 启动 mysqld 的 debug 版本.

其它可能的问题

参考 comment-mariadb, 其中提到:

I finally discovered that my memory problem was caused by innodb not using large pages and there was 
no memory leak in my case, we are not using TokuDb neither. Problem was happening because in huge pages 
configuration I was including the one that were supposed to be used by Innodb but since there were not , 
but still were reserved, when innodb needed memory it was allocated from remaining memory of the system 
so appearing as leaking ... I reconfigured huge pages minus pages needed by innodb and memory usage went 
down immediately.

这个评论指出了内存高可能是由于 innodb 和关闭 huge page 的原因引起的. 这点在实际的使用中我们还没有碰到过. 毕竟很多公司的实例都是 TokuDB + InnoDB 混合使用的, 如果存在这个问题, 官方就会有大量的反馈上报. 另外我们进行以下测试也验证了上述原因是错误的:

1. 仅存在 InnoDB 表, 关闭 huge page. 未出现内存过高的情况;
2. 仅存在 TokuDB 表, 不安装 jemalloc-3.6.0-1.el7.x86_64, 出现内存过高的情况;

处理说明

从 mariadb 的 jira 来看, 各发行版本, Centos, Ubuntu, Debian 等都出现此类问题, 还没有发现该问题的根本原因. 在已知的文档和测试中, 只有 mysqld 预加载 libjemalloc 才能避免此类问题. 可以使用以下方式安装:

yum install jemalloc      # centos/redhat

apt install libjemalloc1  # ubuntu/debian

percona mysql 5.6/5.7 版本中, mysqld_safe 启动脚本默认会加载 libjemalloc.so.1 文件, 此类方式可以直接启动 mysqld 进行以避免内存高的问题. 其它版本可以在配置中直接指定参数进行设置:

[mysqld_safe]
malloc-lib= /path/libjemalloc

另外, 如果对内置的 TokuDB 和 jemalloc 方式存在疑虑, 可以参考 percona-tokudb-install 以插件的方式安装 TokuDB 引擎, 这种方式就必须依赖系统安装的 jemalloc, 等同上面的处理方式, 这种方式在实际的使用中还没有出现内存过高的问题. 如下所示, 可以通过 lsof 命令查看是否生效:

# lsof /usr/lib64/libjemalloc.so.1 
COMMAND   PID  USER  FD   TYPE DEVICE SIZE/OFF    NODE NAME
mysqld  57639 mysql mem    REG    8,1   212096 1427805 /usr/lib64/libjemalloc.so.1