又搞了一次事故

2017-01-27

又被我搞了个事故出来,大过年的。

我们binlog系统是这样的,数据库的每个分布式结点都有一个叫做pump的程序,会将所有log收集起来,发送到一个地方去汇总。汇总的节点叫做cistern,上层备份库从cistern拉数据。cistern会定期的清理老的数据,比如删除7天之前的。

问题出在删除的逻辑,超过多久的,全部删除,我代码直接这么写了。这是一个巨~~巨~巨~大的事务。用户线上跑了2天多以后,今天开始执行到那段逻辑。从监控的观察来复盘,当时应该是内存用涨到了500G以上,然后去跑交换,就死了。

发现问题后,修改GC,限制每次删除1000条。重启,仍然挂。关掉交换了也是,cistern目前是用boltdb做存储的,即使没有执行清理的逻辑,起来后内存直接会涨到0.5个T。这块应该是有问题的,cistern完全起不来。

算起来,我们在删除以及GC数据相关的事情已经栽了太多跟头。

  • rocksdb的删除,LMS删除不会直接删除,而是append操作,到一定时机做compaction。compaction时会比较影响到性能。
  • MVCC的删除,事务MVCC,删除也是标记而不是直接删掉。保留老版本后,读的时候需要扫描到所有版本,由于版本过多会影响性能。
  • 大表的删除,这是一个巨大的事务,内存会涨得很厉害,存储层的写入压力也非常大。

binlog设计我参与了的,而且今天导致问题的代码还是我写的,这次暴露不少问题。

cistern要把整个集群的流量都汇总,这个单点对于IO的压力太大了。机器配置要求太高,普通的SATA的IOPS跟不上,IO是会打满的。SSD成本原因,不可能太大,数据保存的时间是比较有限的。即使现在用户的土豪级配置,以那种流量,保留二三天压力都大。

boltdb的事务实现也是够粗暴的,直接上了一个全局的读写锁来实现。有写的时候,同一时间只有一个goroutine能够工作,而从当时的观察看,唯一能工作的那个goroutine还一直在处理freelist的alloc,大量的做memmove,100%的CPU都在干这个了,整个系统都卡在这儿。

之前选型是太过简单粗暴,只求先做出来。现在看来,boltdb还是不适合处理量大一点的数据。一旦IO阻塞一点,数据可能就堆积,都可能出问题。 对于当前的GC场景,是比较适合cistern上面按时间分片的,比如按时间整文件删除。

大到上层的设计,小到一行很不起眼的代码,都有可能让整个系统进入灾难。 对于编程,还真是只能时刻保持敬畏之心啊。