快照数据, 是数据仓库必不可少的一部分, 尤其是对一些业务来说, 比如贷款风控业务,
常常需要申请贷款前一天的用户画像和相关特征的快照, 作为训练数据集.
最方便的, 比如每一天都存一份全量数据的快照, 容易实现和方便管理.
然而这种方式对于较大的表来说 :
- 存储快照数据的成本是巨大的, 尤其在云上精细计费以及行业降本增效的今天.
- 当大部分行的数据是没有变更的, 但我们仍旧需要再次保存一份.
- 当只有少许行的数据有变更, 但我们仍旧需要全量的计算, 并全量的写入新的快照中去.
当计算和储存的成本太过巨大时, 我们或许应该想想优化的方法了, 而拉链表就是常见的设计.
拉链表
快照表主要多了两个字段: start_date
和 end_date
, 分别代表记录的有效起始时间和结束时间.
例如:
uid | name | city | start_date | end_date |
---|---|---|---|---|
1 | Adam | GZ | 2015-01-01 | 9999-12-31 |
2 | Ben | SZ | 2015-01-01 | 2015-01-02 |
2 | Benny | SZ | 2015-01-02 | 9999-12-31 |
假设用 9999-12-31
代表: 这个记录在最新时刻, 它仍然是有效的记录,
而第2条记录的 end_date
代表它在 2015-01-02
之后是无效的,
也就是说, uid=2 的用户在那天之后的 name 叫做 Benny.
查询
如果我们要查询最新的数据, 只需条件 end_date=9999-12-31
即可, 结果将会如下所示:
uid | name | city | start_date | end_date |
---|---|---|---|---|
1 | Adam | GZ | 2015-01-01 | 9999-12-31 |
2 | Benny | GZ | 2015-01-02 | 9999-12-31 |
如果我们要查询 2015-01-01
那天的快照数据, 那么我们需要这些过滤条件:
start_date
<= 2015-01-01and
end_date
> 2015-01-01
查询结果如下:
uid | name | city | start_date | end_date |
---|---|---|---|---|
1 | Adam | GZ | 2015-01-01 | 9999-12-31 |
2 | Ben | SZ | 2015-01-01 | 2015-01-02 |
修改
假设 2015-01-01 那天的最新快照数据如下 :
uid | name | city | start_date | end_date |
---|---|---|---|---|
2 | Ben | SZ | 2015-01-01 | 9999-12-31 |
假设在 2015-01-02 那天, Ben 修改了 name, 那么当天的快照数据需要:
- 把第二行记录的
end_date
改为 2015-01-02, - 插入新的有效记录, 有效
start_date
设置为 2015-01-02 :
uid | name | city | start_date | end_date |
---|---|---|---|---|
2 | Ben | SZ | 2015-01-01 | 2015-01-02 |
2 | Benny | GZ | 2015-01-02 | 9999-12-31 |
问题: 分区
当实际使用快照表时, 我们要如何进行分区呢?
用 start_date 和 end_date 进行分区
假如直接用 start_date 和 end_date 进行分区, 一年预估也需要 66430 个分区.
<阿里巴巴大数据实践> 中提到一个做法: 每个月初做一个全量表, 然后当月的每天基于这个全量表
进行 start_date 和 end_date 的分区, 这样一年需要的分区可以降到 5232 个.对 end_date 进行分类和分区
当然也有只对 end_date 进行分类和分区的做法, 比如把 end_date 简单分成
latest
和history
两类.
latest 分区存放end_date=9999-12-31
的数据, history 分区存放end_date<今天
, 如果 history 分区太大,
还可以按年份等等维度继续拆分成多个分区, 这种方法的好处就是写入存储和查询时逻辑简单, 分区数量少.
问题: 下游用户易用性
拉链表极限存储, 虽然节省了存储成本, 但在使用上增加了复杂度.
如果有下游用户需要使用到这个拉链表的数据, 那么还是需要对拉链表作一层封装
再对外使用的,
比如可以做一个视图, 或者在 SQL 解析中进行 捕捉和转换.
根据我的经验, 有些数据表在大部分业务中, 下游用户用的最多的还是 T-1 的数据,
出于性能和重复计算成本的考虑, 可以每天出一个 T-1 的全量表,
设置相应的生命周期 自动删除, 这样一次写入, 多次读取.
而对于 T-1 之前的数据 再来采用拉链表的查询方式, 因为只有少数业务和少数时间才需要访问历史快照,
当然, 这些都是要根据你的业务上下游的具体情况, 具体分析.
参考
<阿里大数据实践>