原文标题:Changing Fundamental Behavior With Two Lines of Code(用两行代码改变基础行为)html
做者:Oren Eini, CEO RavenDB数据库
一直以来咱们对 RavenDB 的启动时间并无太多关注 —— 5 秒或者 15 秒都不是什么问题。可是若是超过 15 秒,甚至长达 3 分钟的话,那咱们就必须关注了。性能
咱们的一个用户提供了一个有意思的用例。他们的系统运行在 Azure 上,并充分利用了多设备存储,也就是将数据库临时文件(journals)放在高性能存储服务上,而数据自己放在更大(且相对较慢)的普通存储上。这么作是由于他们的数据量很大,好比某条索引的大小就超过了 256GB。大数据
在他们系统当中,RavenDB 的启动慢到没法接受。咱们通过排查发现根本缘由在于启动过程当中的数据恢复阶段,此时数据库会从新执行临时文件中的最近事务,以保证数据完整性。一般来说这步花不了多少时间,由于默认状况下,即便数据库负载较大时,临时文件大小也只有 256MB 左右。但咱们这位客户的使用场景比较特殊,咱们观察到临时文件中一个事务的数据量能达到几个 GB 的大小,再加上临时文件的内容是通过压缩的,因此当数据恢复到主存储当中时,实际的数据量会达到 10GB 以上。虽然那些已经执行过的事务不须要重复执行,但咱们必需要先扫描临时文件,来找到哪部分是已经执行过的。spa
在这个基础上,考虑到数据库的个数,以及每一个数据库的索引个数,RavenDB 启动时的总体消耗就变得十分可观了。显然这不是咱们所要的。若是咱们遇到系统崩溃,那么目前尚未什么好的办法来避免从新执行这些事务;但问题是就算没有遇到崩溃,就算是正常关闭,从新启动的过程仍是同样的缓慢。htm
这个问题本质上是由于 RavenDB 没有在临时文件中标记哪一个位置是已经同步过的,因此在启动过程当中咱们必须扫描整个临时文件,来找到须要从新执行的部分。固然咱们能够在临时文件中补上这个标记,可是咱们不能直接去更改客户现有系统的数据文件格式,这种解决方案不但感受别扭,并且可能带来兼容性问题。索引
咱们也考虑修改数据库的行为,使得在对某个大数据量的事务同步成功后,更加主动的切换到另外一个临时文件。今天我在查看相关代码时,发现了一个有问题的地方。每当咱们处理一个大事务(事务大小超过临时文件最大大小)时,为了有足够的空间,咱们会在磁盘上扩充临时文件的大小,而咱们分配空间的计算方式颇有意思:图片
如图所示,若是当前的临时文件大小小于须要的最小空间,咱们会要增长其空间;同时为了不过于频繁的扩充文件操做,咱们还会考虑给下一个事务预留足够的空间。如今咱们假设当前临时文件大小为 256MB(也是系统预设的最大值),而事务大小为 1.56GB。事务
这种状况下,临时文件将会扩充到 2GB 大小,而其中只有 1.56GB 用到了。原本剩余的空间咱们是能够继续利用的,除非下一个事务很大,好比有 800MB,咱们就会要重建一个新的 1GB 大小的文件。get
这个时候问题来了。对于这个 2GB 大小的临时文件,假设咱们已经成功将其内容同步到了主存储,那么剩下还有 440MB 的空间可用,咱们就会保留这个临时文件用它来存储下一个事务。若是在这个点上数据库进行了重启,那么在启动阶段就必须扫描整个 2GB 的临时文件来确保没有丢失数据。要修复这个问题也超简单:
咱们要作的就是当扩充后的大小大于临时文件最大大小时,取临时文件最大大小和实际须要的大小之间的最大值做为最终的实际大小。这样扩充后的临时文件就恰好只能容纳一个大事务。当这个事务成功同步后,由于没有额外的空间再容纳另外一个事务,因而 Voron 便会立刻清理这个文件。这样临时文件中就不存在残留的大事务数据。这个改动既简洁又有效,很是棒,我很是喜欢。