绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
MyRocks在XA事务场景的使用和优化-2
2020-01-06 10:34:12

内存泄漏问题

解决了slave 回放xa 事务的问题,业务顺利部署MyRocks,但上线不久后发现有内存泄漏。

线下也很容易模拟出来。rocksdb_block_cache_size=4G,利用sysbench_xa 32线程跑update_index测试,top抓取的内存消耗:

show engine rocksdb status显示rocksdb_block_cache和memtable的总大小才1G多,内存泄露明显。show engine innodb status 显示有大量处于not started状态的trx_t对象存在,线上都是rocksdb表,为什么存在这么多innodb的事务对象?

同时DBA在利用mysqladmin shutdown slave的时候反映无法shutdown。我们线下利用sysbench_xa跑了一会后执行shutdown,MySQL的error.log有下面的输出:

(有关无法shutdown的问题在下一节详细介绍。)

内存泄漏与这么多遗留的trx_t有很大的关系,为什么会有这么多innodb trx_t的对象?在操作rocksdb表的过程中难道涉及到了某些innodb表?如果是涉及到了某些innodb表,又是什么操作导致了trx_t泄漏呢?调试代码发现这些trx与系统表更新有关,主要是slave复制过程中更新下面这些表:

gtid_executed

slave_relay_log_info

这些表都是innodb表,打开这些表时会创建trx_t对象,但这些对象没有立刻释放,从innodb引擎角度看,trx_t对象是可以复用的,每个连接复用一个trx_t对象,等连接断开后再回收trx。这些trx_t对象为什么会被泄漏(在无法shutdown问题这一节中会详细介绍)。这是因为xa start的时候是所有引擎参与deattach自己的事务对象,xa prepare的时候只有实际参与的引擎reattach回自己的事务对象,这样的逻辑会导致没有innodb参与prepare时innodb trx无法reattach,从而导致对象被泄漏。代码中增加了xa-prepare-memleak.patch(patch在文章末尾给出)内存泄漏得以解决,利用sysbench_xa测试发现内存消耗平稳:

解决了slave 无法回放xa事务问题,内存泄漏问题也缓解了,延迟从库稳得以稳定运行。延迟从库稳定运行了一段时间后,在日常的巡检中发现个别实例内存消耗有点高,这又让人很困惑,内存泄漏问题如果没有个大概的方向线下模拟很难复现,还是从现场找原因。排查了一圈,发现这些实例上存在业务的innodb表,这也正常,业务方有时候会临时创建innodb表,难道混合引擎会导致内存泄漏。为了验证这个猜测,线下利用sysbench_xa模拟,在没有xa事务的情况下没有内存泄漏,在有xa事务的情况下,内存泄漏明显,看起来又是与xa事务有关。

xa-prepare-memleak.patch不能解决这种内存泄漏。混合引擎场景导致的内存泄漏通过线下模拟也是与innodb trx_t对象有关系,实验中发现存在大量的处于not started 状态的 trx_t对象,shutdown也是存在大量的innodb活跃事务,可以确认又是trx_t对象泄漏,这次的trx_t泄漏又是什么操作导致的呢?

上文介绍过innodb 的 trx_t对象是在每个连接中复用的,调试发现每次在打开表时会判断trx_t是否存在,如果不存在则创建trx_t对象,如果存在就会复用存在的trx_t,代码如下图所示:

trx_t对象的回收是在连接断开后执行的,innodb_close_connection函数执行下图函数回收trx_t对象。

innodb 引擎xa 事务 的 trx_t的生命周期如下:

(1)在start slave后因为会读取slave_worker_info表信息会创建trx(标记为trx1),利用gdb调试发现Innodb引擎在xa start detach 的trx也就是这个trx1。

(2)xa start 之后innodb执行sql时,由于trx1已被detach,所以会再分配一个trx(标记为trx2),xa prepare的时候调用ha_prepare后,trx的状态变为TRX_STATE_PREPARED,接着会调用innodb_replace_trx_in_thd, 先把trx2与thd

解除关联,并且从trx_sys->mysql_trx_list中移除,trx2还在trx_sys→rw_trx_list中,trx2处理完之后,reattach回trx1,trx1重新与thd保持关联。

(3)xa commit xid的时候,遍历trx_sys->rw_trx_list中的trx→xid查找与该提交的xid相同的trx也就是trx2,找到后进行提交,提交结束后释放该trx(trx2)。

上述步骤在只操作innodb表时逻辑没有问题,但是在混合引擎的场景下,可能就会出问题。假设有下面这种场景:

  1. rocksdb 表的 xa事务;
  2. innodb表的dml普通事务;
  3. rocksdb 表的xa事务;

之前介绍过了,MySQL解决xa prepare与xa commit分离的方法是所有引擎在xa start 的时候都deattach掉自己的事务对象,xa prepare的时候只有实际参与的引擎去re-attach 自己的事务对象。按照这种解决方法我们走一遍上面的场景:

rocksdb表的xa事务开始执行,xa start的时候innodb引擎也会deattach trx,但是在xa prepare的时候只有rocksdb引擎re-attach自己的事务对象,innodb的trx泄漏。

innodb表执行普通的dml,因为上一步trx已经泄漏了,需要再重新分配一个trx,并且在事务提交后该trx没有马上回收,同一个连接会复用。

又来了一个rocksdb表的xa 事务,跟步一样,第二步中分配的innodb trx又被泄漏。

这种场景也是线上存在的场景,线下利用sysbench_xa模拟也是这种场景。同时线下实验也验证过如果全是xa事务不存在内存泄漏。

xa-prepare-memleak.patch如下:

diff --git a/mysql-5.7.20/sql/rpl_gtid_persist.cc b/mysql-5.7.20/sql/rpl_gtid_persist.cc
index df5457e..5c74f04 100644
--- a/mysql-5.7.20/sql/rpl_gtid_persist.cc
+++ b/mysql-5.7.20/sql/rpl_gtid_persist.cc
@@ -166,22 +166,23 @@ bool Gtid_table_access_context::init(THD **thd, TABLE **table, bool is_write)
     m_tmp_disable_binlog__save_options= (*thd)->variables.option_bits;
     (*thd)->variables.option_bits&= ~OPTION_BIN_LOG;
   }
-
+  
   if (!(*thd)->get_transaction()->xid_state()->has_state(XID_STATE::XA_NOTR))
   {
-    /*
-      This type of caller of Attachable_trx_rw is deadlock-free with
-      the main transaction thanks to rejection to update
-      'mysql.gtid_executed' by XA main transaction.
-    */
+
     DBUG_ASSERT((*thd)->get_transaction()->xid_state()->
                 has_state(XID_STATE::XA_IDLE) ||
                 (*thd)->get_transaction()->xid_state()->
                 has_state(XID_STATE::XA_PREPARED));
-
-    (*thd)->begin_attachable_rw_transaction();
   }
 
+  /*
+	This type of caller of Attachable_trx_rw is deadlock-free with
+	the main transaction thanks to rejection to update
+	'mysql.gtid_executed' by XA main transaction.
+  */
+  (*thd)->begin_attachable_rw_transaction();
+
   (*thd)->is_operating_gtid_table_implicitly= true;
   bool ret= this->open_table(*thd, DB_NAME, TABLE_NAME,
                              Gtid_table_persistor::number_fields,


diff --git a/mysql-5.7.20/sql/rpl_info_table.cc b/mysql-5.7.20/sql/rpl_info_table.cc
index 204dd69..f1029db 100644
--- a/mysql-5.7.20/sql/rpl_info_table.cc
+++ b/mysql-5.7.20/sql/rpl_info_table.cc
@@ -172,6 +172,8 @@ int Rpl_info_table::do_flush_info(const bool force)
   tmp_disable_binlog(thd);
   thd->is_operating_substatement_implicitly= true;
 
+  thd->begin_attachable_rw_transaction();
+
   /*
     Opens and locks the rpl_info table before accessing it.
   */
@@ -255,6 +257,17 @@ end:
     Unlocks and closes the rpl_info table.
   */
   access->close_table(thd, table, &backup, error);

+  if (thd->is_attachable_rw_transaction_active())
+    thd->end_attachable_transaction();
+  
   thd->is_operating_substatement_implicitly= false;
   reenable_binlog(thd);
   thd->variables.sql_mode= saved_mode;
分享好友

分享这个小栈给你的朋友们,一起进步吧。

数据库内核开发
创建时间:2019-12-11 16:43:06
网易数据库内核技术专家 8年多数据库和存储系统开发经验,《MySQL内核:InnoDB存储引擎 卷1》作者之一,申请技术专利10+,已授权5+。曾主导了网易公有云RDS、MongoDB等数据库云服务建设 现负责网易MySQL分支InnoSQL开发和维护。专注于数据库内核技术和分布式系统架构,擅长分析解决疑难问题。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • 温正湖
    栈主

小栈成员

查看更多
  • xzh1980
  • else
  • Jack2k
  • at_1
戳我,来吐槽~