MySQL内核月报 2014.08

出自淘宝数据库研发组

跳转到: 导航, 搜索

目录

MySQL· 参数故事·timed_mutexes

提要

MySQL 5.5.39 Release版本正式从源码里删除了全局参数timed_mutexes。timed_mutexes原本用来控制是否对Innodb引擎的mutex wait进行计时统计,以方便进行性能诊断。为什么要删除这个参数呢? 下面介绍下相关背景:

Innodb的同步锁机制

Innodb封装了mutex和rw_lock结构来保护内存的变量和结构,进行多线程同步,考虑可移植性, mutex使用lock_word或者OS mutex来保证原子操作,并使用event条件变量进行阻塞和唤醒操作。

 os_event_t	event;
 volatile lock_word_t	lock_word;
 os_fast_mutex_t os_fast_mutex;

Innodb同步锁引入的数据结构和开销

1. 全局mutex链表

Innodb引入了一个全局的链表ut_list_base_node_t mutex_list,并使用一个单独的mutex来保护链表。 所有的mutex在create或者free的时候来修改链表,有了全局链表,也使统计汇总有了可能性,参考命令“show engine innodb mutex”. 虽然需要维护一个全局的链表,但这并不会影响太多的性能,因为大部分的mutex的生命周期都是从Innodb启动一直到shutdown。

2. 统计信息

mutex的结构中,有几个统计信息:

 count_os_wait:请求mutex进入等待的次数
 count_using: 请求mutex的次数
 count_spin_loop: 请求mutex时spin的轮数
 count_spin_rounds: 请求mutex的spin次数
 count_os_yield:请求mutex spin失败后os等待次数
 lspent_time: 统计等待mutex的时间

lock mutex的主要步骤:

 1. 首先trylock mutex,如果没有获取到mutex,并不马上进行wait,而是进行spin。
 2. 尝试spin,如果在SYNC_SPIN_ROUNDS次后,仍然没有lock,那么就进入等待队列,等待唤醒。

在MySQL5.5的版本里,非UNIV_DEBUG模式下,Innodb仅仅保留了count_os_wait的次数,这也是为了性能的考虑。所以5.5的版本后, timed_mutexes在Release下,其实已经不再起作用,所以5.5.39,以及5.6以后,源码里都不再保留timed_mutexes。 要么在debug模式下,启用这些统计,但上线版本又不可能使用DEBUG模式,所以对于mutex的统计,MySQL在后面的版本中使用了performance_schema的等待事件来代替,即:

 mysql> SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
 -> WHERE TABLE_SCHEMA = 'performance_schema'
 -> AND TABLE_NAME LIKE '%instances';
 +------------------+
 | TABLE_NAME       |
 +------------------+
 | cond_instances   |
 | file_instances   |
 | mutex_instances  |
 | rwlock_instances |
 +------------------+

3. 全局等待队列

Innodb为所有的等待状态的线程准备了一个队列,如果获取mutex失败,那么就申请一个cell,进入阻塞状态,等待signal。 sync_primary_wait_array,有了这个全局的队列,Innodb就可以对这些wait的线程进行统计,比如long semaphore waits就是根据这个队列进行的查询。

4. signal丢失

这里再讨论下signal丢失的情况,我们重新再看下lock mutex的步骤:

 线程1:
 1. try lock mutex
 2. fail,然后spin
 3. fail,然后进入队列,然后wait event
 线程2:
 1.own mutex
 2.free mutex
 3.signal event

如果按照这个时序,在线程2 signal event后,线程1才进入队列,那么线程1就永远处在阻塞状态,无法唤醒。为了解决signal丢失的情况, Innodb启动了一个后台线程:sync_arr_wake_threads_if_sema_free,每隔1s就轮询wait数组,如果可以lock,就signal这个event来唤醒线程。


从上面来看,Innodb为了mutex和rwlock的移植性,以及为了监控和诊断,添加了多个全局的数据结构,这样实时的统计才有可能,但也带来了维护数据结构的开销。 而timed_mutexes控制的mutex wait时间统计,因为只在debug模式下进行编译,而且5.6以后使用performance schema的等待事件进行替代,所以参数做了删除处理。

MySQL· 参数故事·innodb_flush_log_at_trx_commit

背景

  innodb_flush_log_at_trx_commit 这个参数可以说是InnoDB里面最重要的参数之一,它控制了重做日志(redo log)的写盘和落盘策略。 具体的参数意义见手册

  简单说来,可选值的安全性从0->2->1递增,分别对应于mysqld 进程crash可能丢失 -> OS crash可能丢失 -> 事务安全。

  以上是路人皆知的故事,并且似乎板上钉钉,无可八卦。


innodb_use_global_flush_log_at_trx_commit

  直到2010年的某一天,Percona的CTO Vadim同学觉得这种一刀切的风格不够灵活,最好把这个变量设置成session级别,每个session自己控制。

  但同时为了保持Super权限对提交行为的控制,同时增加了innodb_use_global_flush_log_at_trx_commit参数。 这两个参数的配合逻辑为:

  1、若innodb_use_global_flush_log_at_trx_commit为OFF,则使用session.innodb_flush_log_at_trx_commit;

  2、若innodb_use_global_flush_log_at_trx_commit为ON,则使用global .innodb_flush_log_at_trx_commit(此时session中仍能设置,但无效)

  3、每个session新建时,以当前的global.innodb_flush_log_at_trx_commit 为默认值。


业务应用

  这个特性可以用在一些对表的重要性做等级定义的场景。比如同一个实例下,某些表数据有外部数据备份,或允许丢失部分事务的情况,对这些表的更新,可以设置 Session.innodb_flush_log_at_trx_commit为非1值。


  在阿里云RDS服务中,我们对数据可靠性和可用性要求更高,将 innodb_use_global_flush_log_at_trx_commit设置为ON,因此修改session.innodb_flush_log_at_trx_commit也没有作用,统一使用 global.innodb_flush_log_at_trx_commit = 1。

MySQL· 捉虫动态·Count(Distinct) ERROR

背景

  MySQL现行版本中存在一个count(distinct)语句返回结果错误的bug,表现为,实际结果存在值,但是用count(distinct)统计后返回的是0。

   drop table if exists tb;
   set tmp_table_size=1024;
   create table tb(id int auto_increment primary key, v varchar(32)) charset=gbk;
   insert into tb(v) values("aaa");
   insert into tb(v) (select v from tb);
   insert into tb(v) (select v from tb);
   insert into tb(v) (select v from tb);
   insert into tb(v) (select v from tb);
   insert into tb(v) (select v from tb);
   insert into tb(v) (select v from tb);
   insert into tb(v) (select v from tb);
   insert into tb(v) (select v from tb);
   update tb set v=concat(v, id);
   select count(distinct v) from tb;
   返回0

  上述中update语句的目的是将所有的v值设为各不相同。


原因分析

  Count(distinct f)的语义就是计算字段f的去重总数,计算流程大致如下:

  流程一:

1、 构造一个unique集合A1(用tree实现) 2、 对每个值都试图插入集合A1中 3、 若和A1中现有item重复则直接跳过,不重复则插入并+1 4、 完成后计算集合中元素个数。

  细心的同学会看到上面的语句中有一个set tmp_table_size的过程,集合A1并不能无限扩大,大小上限为tmp_table_size。若超过则上述流程变为

  流程二:

1、 构造一个unique 集合A1 2、 插入item过程中若大小超过tmp_table_size,则将A1暂时写到文件中,再构造集合A2 3、 重复步骤2直到所有的item插入完成 因此若item很多则可能重复生成多个集合A1~An。 4、 对A1~An作合并操作。由于只是每个集合A保证unique,因此需要做类似归并排序的操作(实际上不需要排序,只是扫一遍) 5、 因此合并操作需要一个临时内存,长度为n,单元大小为key_length (key大小)。这个临时内存,用的也是tmp_table_size定义的大小。实际上在合并过程中还需要长为key_length的预留空间作临时内存保存。因此需要的空间为 (n+1)*key_length。 6、 在进行合并前会判断tmp_table_size >=(n+1)*key_length, 不满足则直接放弃合并。其结果就是返回为0。


案例分析

  以上面这个case为例。字段v的单key大小为65 (65 = 32*2+1) 加上tree节点字占空间24字节共89字节。单个集合只能放11个item (1024/89), 因此n为 24 (24>=256/11), 在合并时需要 (24+1)*65= 1625字节的临时空间,大于1024,放弃合并。


Sql_big_tables

  实际上在最初处理这个问题时,DBA同学发现社区也有人讨论这个bug,并且指出在set sql_big_tables=on的时候,执行count(distinct)就能正确返回结果。原因就是在sql_big_tables=on的情况下,构造集合的方式是直接生成一个临时表,全部插入后直接计算临时表的大小作为结果,整个过程与tmp_table_size无关。


解决方法

  运维上,set sql_big_tables是一个方法,不过会影响性能。调高tmp_table_size算是正招。当然本质上这是一个bug。   代码上,对于已经走到合并操作的这个逻辑,如果tmp_table_size不够,应该直接申请新的临时空间用于合并,完成后释放。虽然会造成临时征用内存,不过以现有的逻辑来看,临时征用的内存已经不少了.

  另外一种时间换空间的方法,就是作多次合并。

  相比之下第一种改造比较简单安全。该bug在RDS MySQL 5.5 中已经修复。

MySQL· 捉虫动态·mysqldump BUFFER OVERFLOW

bug背景

  在上个月发布的新版本中,官方修复了一个mysqldump输入库名或表明长度越界的bug。

  在MySQL的当前约束中,库名和表名字符串最大长度为NAME_LEN=192字节。在myqldump实现中,需要对输入的表名做处理,比如增加``防止表名中的特殊字符。这些临时处理的内存,声明为类似name_buff[NAME_LEN+3],这样在用户输入的库名或表名长度过长时,会造成数组越界读写,导致不可预期的错误。

  这个修复的逻辑也比较简单,就是在开始dump前作参数检查,若发现长度超过NAME_LEN的库/表名,直接抛错返回“argument too long”。


细节说明

  需要注意的是,该修复改变了mysqldump的行为。由于名字长度超过NAME_LEN的库/表肯定不存在,因此修复之前的逻辑,是报告该表不存在。“table not exists”这个逻辑是可以通过--force 跳过的。而“argument too long”则无视force参数,直接抛错返回。


MySQL· 捉虫动态·long semaphore waits

现象描述:

Innodb引擎,父表和子表通过foreign constraint进行关联,因为在更新数据时需要check外键constraint,当父表被大量的子表referenced时候,那么在open Innodb数据字典的时候,需要open所有的child table和所有的foreign constraint,导致持有dict_sys->mutex时间过长,产生long semaphore wait, 然后innodb crash了。

case复现

 CREATE TABLE `t1` (
 `f1` int(11) NOT NULL,
 PRIMARY KEY (`f1`)
 ) ENGINE=InnoDB
 CREATE TABLE `fk_1` (
   `f1` int(11) NOT NULL,
   PRIMARY KEY (`f1`),
   CONSTRAINT `pc1` FOREIGN KEY (`f1`) REFERENCES `t1` (`f1`) ON DELETE CASCADE ON UPDATE CASCADE
 ) ENGINE=InnoDB
 ......
 这里建了fk_[0-10000]张表。

分析过程

1. 数据字典

innodb使用系统表空间保存表相关的数据字典,系统的数据字典包括:

 SYS_TABLES
 SYS_INDEXES
 SYS_COLUMNS
 SYS_FIELDS
 SYS_FOREIGN
 SYS_FOREIGN_COLS
 SYS_STATS

在load某个表的时候,分别从这些表中把表相关的index,column, index_field, foreign, foreign_col数据保存到dictionary cache中。 对应的内存对象分别是:dict_col_struct,dict_field_struct,dict_index_struct,dict_table_struct,dict_foreign_struct。

2. open过程

dict_load_table:

 1. 通过sys_tables系统表,load table相关的定义
 2. 通过sys_indexes系统表,根据table_id load 所有相关index
 3. 通过sys_columns系统表,根据table_id load 所有的columns
 4. 通过sys_fields系统表,根据index_id load 所有index的field
 5. 通过sys_foreign系统表,load所有关联的表和foreign key

3. load foreign的详细过程

3.1 根据表名t1 查找sys_foreign.

而sys_foreign表上一共有三个索引:     

 index_1(id): cluster_index
 index_2(for_name): secondary_index
 index_3(ref_name): secondary_index

所以,根据for_name='t1', ref_name='t1'检索出来所有相关的foreign_id.

3.2 加入cache

因为没有专门的cache,foreign分别加入到for_name->foreign_list, ref_name->referenced_list。 问题的关键:因为foreign是全局唯一的,但foreign又与两个表关联,所以,有可能在open 其它表的时候已经打开过,所以,create foreign对象后,需要判断以下四个list,是否已经存在,如果存在就直接使用。

dict_foreign_find:分别查询这四个list,如果已经存在,则free新建的foreign对象,引用已经存在的。

 for_name->foreign_list
 for_name->referenced_list
 ref_name->foreign_list
 ref_name->referenced_list

如果不存在,把新建的foreign加入到for_name->foreign_list,ref_name->referenced_list链表中。


4. 问题的原因:

因为第一次load,所以find都没有找到,但这四个都是list,随着open的越来越多,检索的代价越来越大。 而整个过程中,都一直持有trx_sys->mutex,最终导致了long semaphore wait。


5. 问题改进方法:

在MySQL 5.5.39版本中,进行了修复,修复的方法就是,除了foreign_list,referenced_list。 另外又增加了两个red_black tree,如下源码所示:

 struct dict_table_struct{
   table_id_t    id;      /*!< id of the table */
   mem_heap_t*    heap;    /*!< memory heap */
   char*    name;        /*!< table name */
   UT_LIST_BASE_NODE_T(dict_foreign_t)
   foreign_list;          /*!< list of foreign key constraints in the table; these refer to columns in other tables */
   UT_LIST_BASE_NODE_T(dict_foreign_t)
   referenced_list;/*!< list of foreign key constraints which refer to this table */
   ib_rbt_t*    foreign_rbt;    /*!< a rb-tree of all foreign keys listed in foreign_list, sorted by foreign->id */
   ib_rbt_t*    referenced_rbt;    /*!< a rb-tree of all foreign keys listed in referenced_list, sorted by foreign->id */
 }

这样dict_foreign_find的过程中,通过red_black tree进行检索,时间复杂度降到O(log n).

MariaDB·分支特性·支持大于16K的InnoDB Page Size

背景

最近发布的MariaDB 10.1 Alpha版本,提交了一个改动,放宽了InnoDB Page<=16K的限制,将上限提高到64K。 从MDEV-6075需求文档中可以看出,目前只支持COMPACT的结构,DYNAMIC结构能否支持还在研究,COMPRESSED结构则确定无法支持。

 revno: 3987
 committer: Jan Lindström <jplindst@mariadb.org>
 branch nick: 10.1
 timestamp: Tue 2014-05-13 13:28:57 +0300
 message:
 MDEV-6075: Allow > 16K pages on InnoDB
 This patch allows up to 64K pages for tables with DYNAMIC, COMPACT and REDUNDANT row types. Tables with COMPRESSED row type allows still only <= 16K page size. Note that single row size must be still <= 16K and max key length is not affected.

业务应用

什么情况下需要64K这么大的页面呢? 我们知道一个Page,不是所有的page_size都可以用来存数据,还有一些管理信息要存,例如页头和页尾(InnoDB Page)。 此外,InnoDB Buffer Pool管理页面本身也有代价,Page数越多,那么相同大小下,管理链表就越长。

因此当我们的数据行本身就比较长,尤其是做大块插入的时候,更大的页面更有利于提升如速度,因为一个页面可以放入更多的行,每个IO写下去的大小更大,就可以以更少的IOPS写更多的数据。 而且,当行长超过8K的时候,如果是16K的页面,就会强制转换一些字符串类型为TEXT,把字符串主体转移到扩展页中,会导致读取列需要多一个IO,更大的页面也就支持了更大的行长,64K页面可以支持近似32K的行长而不用使用扩展页。 但是,如果是短小行长的随机读取和写入,则不适合使用这么大的页面,这会导致IO效率下降,大IO只能读取到小部分有效数据,得不偿失。

MariaDB·分支特性·FusionIO特性支持

背景


随着存储设备越来越快,InnoDB许多原有的设计不再适合新的高速硬件,因此MariaDB 10.1 Alpha版本针对FusionIO PCI-E SSD做出了专门的优化,充分利用了Fio的硬件特性。 MDEV-6246这个需求改造了MariaDB,以利用fio的Atomic writes和文件系统压缩特性。

 revno: 3988 [merge]
 committer: Jan Lindström <jplindst@mariadb.org>
 branch nick: 10.1
 timestamp: Thu 2014-05-22 14:24:00 +0300
 message:
 MDEV-6246: Merge 10.0.10-FusionIO to 10.1.

为何Fio会更快呢,因为传统的存储设备读取,是左图的方式,要经过RAID控制器,来回的路径就长了。而Fio才有右图的方式,设备通过PCI槽直接与CPU交互,大大缩短了路径。

fusionio.png


Atomic writes


InnoDB一直存在一个叫做Double Write Buffer的东西,目的就是为了防止页面写到一半系统崩溃,导致页面损坏,因为InnoDB的Page是16K,而一般的机械硬盘扇区是512字节,SSD大都是4K的块大小,都不能保证16K的写入是完整的。 而Fio的NVMFS文件系统则提供了原子写的保证,只要对文件句柄增加DFS_IOCTL_ATOMIC_WRITE_SET的ioctl标记位,就可以启用这个文件的原子写支持。

 ioctl(file, DFS_IOCTL_ATOMIC_WRITE_SET, &atomic_option)

MariaDB新增了一个参数来启用这个特性,一旦开启,所有文件会用DFS_IOCTL_ATOMIC_WRITE_SET标记打开。

 innodb_use_atomic_writes = 1

这样一来Double Write Buffer就没有存在的价值了,因为不会出现部分写,每个write下去都可以保证所写内容全部完成,这可以相当程度上提升InnoDB的性能。


Page compression


InnoDB标准的页面大小是16K,InnoDB也提供1K、2K、4K、8K的压缩页面大小,通过KEY_BLOCK_SIZE来设置压缩大小,使用zlib标准库来进行压缩。 但是Page是频繁被更新的,如果每次修改都重新压缩页面,代价很高,InnoDB就采用了modification log来暂存部分修改信息,而避免了频繁解压缩,待modification log存满时,再重新对整个Page做一次重构压缩。 但是Compressed Page载入InnoDB Buffer Pool时,InnoDB只能处理未压缩的页面,因此还要在内存中存一份解压页面,回写到磁盘时再次压缩。

总而言之,InnoDB的Compressed Page有这些缺点:

 内存开销
 空间: 压缩和解压缩页面都要存在InnoDB Buffer Pool
 访问: 修改需要同时写入到压缩页面和未压缩页面
 CPU开销
 软件压缩库zlib (从磁盘读取时需要解压缩放入内存, 页面分裂时需要重新压缩)
 Split & Recompress & Rebalance when mlog overflows
 空间收益
 固定的页面压缩大小 – 给压缩的效果设置了一个固定的边界
 Modification log和Page预留空间弱化了压缩带来的空间缩减
 糟糕的实现
 代码过于复杂而导致压缩和未压缩的表性能差距非常明显


MariaDB与FusionIO合作利用NVMFS文件系统的特性,修改InnoDB的Page结构来支持文件系统级的压缩。 Page compression要求InnoDB做了如下配置:

 innodb_file_per_table = 1
 innodb_file_format = Barracuda.

它的实现方法是,只在Page即将写入到文件系统时,才进行压缩,因此最终只有压缩后的容量被写入到磁盘,如果压缩失败,那么就把没有压缩的容量写入磁盘。另外还会对Page内的512字节的倍数的未使用空间清理掉,不占用实际存储:

 fallocate(file, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, off, trim_len);

当页面被读取时,会在放入Buffer Pool之前进行解压缩,将原始页面载入内存。因此需要在文件头中加入一个新的Page type:FIL_PAGE_PAGE_COMPRESSED page.jpeg

综合起来可以这样定义一张表:

 CREATE TABLE t3 (a int KEY, b int)  DATA DIRECTORY=’/dev/fioa’ PAGE_COMPRESSED=1 PAGE_COMPRESSION_LEVEL=4 ATOMIC_WRITES=’ON’;

意思是将t3表存到/dev/fioa盘,开启Page compression,采用4级压缩,开启原子写。

经过测试,可以看出,LZ4的压缩比例最好,而且,对性能影响非常小。

storage.png

tpcc.png

TokuDB· 性能优化·Bulk Fetch

Bulk Fetch是为了提升区间操作性能的,聊它之前,先简单唠叨下读取机制,TokuDB由两部分组成: tokuFTtokudb-engine
tokuFT是个支持事务的key/value存储层,tokudb-engine是MySQL API对接层,调用关系为:tokudb-engine ->tokuFT。
tokuFT里的一个value,在tokudb-engine里就是一条row数据,底层存储与上层调用解耦,是个很棒的设计。
在tokuFT是个key里,索引的每个node都是大块头(4MB),node又细分为多个"小块"(internal node的叫做partition,leaf node的叫做basement)。
从磁盘读取数据到内存的方式有2种:

  1. 仅读一个"小块"的数据,反序列化到内存(提升point query性能,只读取需要的那部分数据即可)
  2. 读取整个node数据,反序列化到内存(提升区间性能,一次读取整个node磁盘数据)

对于tokudb-engine层的区间操作(比如get_next等),tokuFT这层是无状态的,必须告诉当前的key,然后给你查找next,流程大体是:

 tokudb-engine::get_next(current_key) --> tokuFT::search_next(current_key) --> tokuFT::return next

这样,即使tokuFT缓存了整个node数据,tokudb-engine还是遍历着跟tokuFT要一遍:tokuFT每次都要根据当前key,多次调用compare操作最终查出next,路径太长了!
有什么办法优化呢?这就是Bulk Fetch的威力: tokudb-engine向tokuFT一次要回整个node的数据,自己解析出next row数据,tokuFT的调用就省了:

 tokudb-engine::get_next(current_key) --> tokudb-engine::parse_next

从Tokutek的测试看,在使用Bulk Fetch后,能有2x-5x的性能提升。
但并不是所有的区间操作都可以Bulk Fetch的(比如涉及update/delete),TokuDB目前实现了:SELECT、CREATE_TABLE、INSERT_SELECT和REPLACE_SELECT的Bulk Fetch功能,预计发布在7.1.8版,更多Bulk Fetch介绍:
https://github.com/Tokutek/tokudb-engine/wiki/Bulk-Fetch

TokuDB· 数据结构·Fractal-Trees与LSM-Trees对比

最近,TokuDB的创始人Dr. Bradley Kuzmaul发表了一篇文章: A Comparison of Log-Structured Merge (LSM) and Fractal Tree Indexing,从write amplification(WAMP), read amplification(RAMP), and space amplification三个方面对B-Trees,LSM-Trees(LSM)以及Fractal-Trees(FT)进行了详细的分析和对比。

Dr. Bradley Kuzmaul的结果是(页13):
Lsmft.png


从结果来看:

在WAMP上,FT跟LSM(leveled)是相同的
在RAMP(range)上,LSM(leveled)的复杂度明显要高不少(FT的O(logN/B)倍)

不过,RAMP这块的分析有个小问题:
LSM(leveled)在实现上(比如LevelDB),可以通过meta-info打"锚点"的方式,把RAMP(range)降低甚至做到跟FT一样,如果是point queries的RAMP,则可以通过Bloom filter来降低。

具体的推导过程请阅读原作,下面简单分析下FT的RAMP为啥比LSM的要低。
FT的读方式比较"特殊",由于每个节点都有个message buffer,当有读请求时,需要把inner node的message buffer数据(部分)推(apply)到leaf node,最后只在leaf node上做二分查找,所以RAMP基本就是树的高度。

另外,在数据流向上(compaction过程中数据走向),LSM强调"level"(横向),从level-L根据规则选取部分数据merge到level-(L+1),如果选取数据的策略不好,会抢占磁盘带宽,容易引起性能抖动,而FT强调"root-to-leaf"(纵向),数据从root有序的逐层merge到leaf节点,每条数据的merge路径是很明确的。

TokuDB·社区八卦·TokuDB团队

第一期先介绍下TokuDB团队吧。
TokuDB自从开源后(更赞的是开源了所有的commits),逐渐被大家所熟悉,MariaDB 5.5系列和Percona Server 5.6的GA版本中,都以plugin的方式集成。

3位(Tokutek)创始人: Michael A. Bender , Martín Farach-Colton , Bradley C. Kuszmaul
2012年他们合发了一篇208页的pdf[Data Structures and Algorithms for Big Databases],热爱存储引擎算法的朋友们一定要看 :D

TokuDB目前有5名研发:

 @prohaska    --tokudb-engine研发,版本发布(一个人)
 @Leif        --tokuFT研发(Bender学生)
 @zkasheff    --tokuFT研发(Kuszmaul学生)
 @esmet       --tokuFT研发(Farach学生)
 @fizzfaldt   --算法优化(Bender学生)

可以说是个很"精致"团队,是研发也是测试,tokuFT的测试代码达~18w行(而tokuFT的核心代码才~9w行),在代码把控上也很严格,要求所有代码在valgrind(helgrind和drd)下,没有memory、data race(资源竞争)和lock order(死锁)警告,质量很有保障。(BTW:我们也在借鉴和运用)
同时@Leif和@zkasheff也是tokuMX的研发,不久前,他俩就MongoDB Replication可能"丢数据"的问题,写了篇[Ark: A Real-World Consensus Implementation],对Paxos和Raft感兴趣的同学可以去看下。

更有意思的是,当工程中遇到难题,研发们先讨论出方案,递给三位创始人,得到更权威的指导,如果问题不错,他们就深入研究,出paper,工程与学术相结合。

个人工具