谈一谈反范式设计下简单高效的冗余数据刷新思路

随着业务数据量的增长,不可避免的需要需要在数据库层面引入一些反范式的设计手段,以保证更好的数据库查询性能,但反范式设计不是银弹,解决了一些问题的同时,也会带来新的问题。

这就是冗余数据的同步与刷新问题,当一份数据存在于多个拷贝,如何简单高效的保证各份数据的同步和一致,变成了新的需要解决的难题。

本文总结一下笔者在这一块的经验与思考,首先会介绍我们遇到的问题及常规解决方案,然后探讨更加简单高效的方案,希望对读者有所启发~

冗余数据刷新之痛

笔者所在公司毫无疑问也面临过这样的问题,其中以我们的成绩管理业务为最甚,在我们的成绩管理的业务中,整体由以下三大部分组成:

过程性成绩

过程性成绩可以是一次考试(比如月考,期中考试)、一次测验、也可以是老师课堂的一次随堂测验等等。

总成绩

总成绩大部分是根据过程性成绩按一定的计算规则计算而来的,但也可以直接录入或者修改。

成绩单

过程性成绩而总成绩都是归属与某一个课的,比如语文,数学。而成绩单是横跨多个课程的汇总信息,包含学生的已修学分,GPA等信息。

当然,这只是一个简单的模型,实际上的数据的层级更多,有许多的场景会导致过程性成绩被修改,总成绩需要被更新,学生的成绩单需要相应的变化,还有各种排名数据需要刷新,等等等等,不胜枚举。

常规的做法

常规的做法是当数据被更新时手动刷新冗余数据,方式是在业务代码中手动埋点,触发数据刷新操作。这也是我们目前使用的方案,但其存在几个明显的缺点:

1. 复杂业务时难以全面考虑所有需要进行数据刷新的场景,可能漏掉一些场景引起数据不同步,导致bug

2. 数据刷新可能导致连锁效应,比如过程性成绩更新了,总成绩需要更新,进而成绩单也需要更新
3. 性能问题,一些小的修改会触发大量的数据刷新,导致 API 响应缓慢

有了这些问题,我们就在思考,有没有可能存在一种简单高效的方式,能够把问题简化,统一处理,让这些问题都烟消云散。

简单高效的做法

经过日以继夜的思考,想起前端了 MVVM 框架(比如 Angular)中的脏数据检查机制,这让我灵光一现,如果我们能够识别哪些数据是脏数据,不就可以通过统一的机制进行数据刷新了么?

这让我进一步深入思考,怎么识别某一条数据是脏数据呢?

想啊想,终于有了答案,那就是记录的更新时间,如果冗余数据的更新时间早于对应原始数据的更新时间,那么这条冗余数据就是脏数据,需要被刷新。

但有一个例外,数据被删除了怎么办,不过一个同事的话点醒了我,我们可以利用软删除机制,此时的更新时间等于删除时间。

这样的话,我们只需要在业务中保证更新数据的同时将更新时间设为当前时间就可以了,相比之前的各种埋点,简单可靠许多。

至此整套逻辑完备了,我们可以建立一个脏数据检测与刷新框架,声明数据与数据的依赖关系,依据更新时间字段找出需要进行刷新的的脏数据,进而找到刷新对应数据的 handler,调用 handler 进行刷新。

如此便实现了一套统一的冗余数据刷新机制,简单可靠,让我们从繁琐的业务埋点中解脱出来~

更好的实时性

如果说定期的脏数据刷新不能满足业务实时性的需求,这个时候我们可以采用监控数据库 binlog 来就行脏数据检查,一旦检查到脏数据,然后根据已经建立好的数据依赖关系,执行数据刷新操作。