如何将数据热导出到文件

随着时间推移,数据库中数据量会越来越大,如果把查询分析都挂到数据库上,有可能会影响到生产系统的正常运行。所以,一般都会将生产数据库中不再变动的数据定期移出到另一个分析数据库中,由分析数据库来承担查询分析的压力。

不过,我们知道,文件系统比数据库有更好的 IO 性能,对于不再变动的历史数据,使用文件还可以采用更灵活的压缩技术。这样,如果我们把移出的数据存储到文件中,只要有好的计算引擎(比如集算器),那么基于文件计算将获得比分析数据库更好的性能,而历史数据常常巨大,性能提升很有意义。


要实现这种结构,需要定期把历史数据从生产数据库中导出到文件,这看起来也没什么难的,导出是很常规的数据库操作。

如果是冷导出,那确实没什么。所谓冷导出,是指在数据导出过程中,基于文件的查询分析系统会暂停使用,等导出完毕后再继续使用。比如在每天夜间没有查询工作的时候进行,把导出的新历史数据追加到原来的文件之后就可以了,有需要建索引的情况也可以同时维护好。

但是如果是热导出,情况就不一样了。所谓热导出,是指查询分析系统永不停机,随时需要能响应请求。而数据导出本身也需要时间,在导出的过程之中仍然可能有查询请求进来。但是,这种有特殊格式的文件在追加和维护(索引)过程中,经常是不可用的,这时候就不能在导出数据的同时还响应查询请求了。

采用数据库却没有这个问题。原因是数据库拥有事务一致性的能力,在数据写入(导出对于目标数据库来讲是写入)过程中,数据库仍然可以应对查询请求,并且不会使尚未完全写入的数据参与查询。不过,如果每批数据量写入太多时,也会给目标数据库造成较大的负担,数据库回滚机制的成本并不低。


那么,我们怎么能够即享受到文件的高性能,又支持不停机的热导出呢?

一个简单的办法是把数据文件拆细。比如,假如数据是每天导出,那么就可以每天保存一个文件,每次导出时形成新文件,在导出过程中原有的文件不变,可以继续使用。新的一天的文件导出维护完成后,在某个时刻才开始启用。比如每天 0 点开始导出前一天数据,假定一小时内能全部完成,则可以约定凌晨 1 点起启用新文件数据(即 1 点以后的查询将开始使用这个新文件)。这样的坏处是文件系统中积累过多碎文件,对管理造成麻烦,而且每次查询时都可能要涉及多个文件,运算代码不好写而且性能也会受到影响。


在数据库一致性能力的支持下,再配合备份文件,我们还是可以实现将数据热导出成单一文件。

准备工作:

1. 将数据文件复制成相同的两份:A 份和 B 份,平时查询使用 A 份;

2. 在(生产)数据库中建立表 X,用于记录当前查询正在使用的数据文件是 A 份还是 B 份,以及当前正在执行的查询;

查询响应过程:

1. 从 X 中读出当前使用哪个数据文件,并在 X 中写入一条记录表示当前查询开始,需要生成一个唯一码,同时记录该查询基于哪个数据文件;

2. 使用相应的数据文件进行查询计算并获得返回数据后;

3. 将步骤 1 中写入的记录删除(用生成的唯一码),表示查询已经结束;

导出过程:

1. 开始导出数据时,此时 X 中记录的当前使用文件为 A,将这个值改为 B,后续出现的查询将基于 B 进行;

2. 等待 X 中基于 A 的查询全部结束,即 X 中不再有关于 A 的查询记录,此时 A 已不再被任何查询使用了;

3. 现在可以导出数据追加到文件 A,完成 A 的维护工作;

4. 将 X 中记录的当前使用文件改为 A,再有的查询将转回基于 A 进行;

5. 等待 X 中基于 B 的查询全部结束;

6. 将 A 追加的数据也同时追加给 B(这时只要读 A,不会影响 A 上的运算),完成 B 的维护工作,B 进入可用状态,导出结束;

基本原理是在导出数据过程中使用另一个文件,完成导出后再换回来去维护备份文件。期间要考虑到查询的并发性,借助数据库的一致性确保不会发生写入和查询在同一个时刻针对同一文件进行。并发读写导致的错误,不是刻意或大规模使用时很难测试出来,在设计时要特别小心。

许多机构期望数据库系统能支持 T+0 全量实时查询,在数据量很大时一般只能进行数据库扩容了(包括上述分库手段也需要扩容数据仓库),成本高昂。如果采用文件系统和生产数据库混合运算,就可以实现低成本高性能的 T+0 查询了,而热导出机制则是这个方案的基础(需要进行简单改造,在 X 表中记录文件中数据的截止时刻,超过此时刻的查询请求将转给生产数据库去执行)。

不过,这个过程确实有些复杂,实现起来还是很麻烦,我们在乾学院上放一个以 T+0 查询为目标而实现的热导出例程( 实时报表 T+0 的实现方案 )。另外,在新的集算器仓库版也将支持这一机制,直接向组表追加数据就可以了,集算器会自动处理热导出中的问题。当然,集算器不能依赖有数据库,它会自己实现一致性效果。