大数据架构系列:预计算场景的数据一致性问题

2022-11-21 19:11:05 浏览数 (1)

背景

结合 Wikipedia 和业界一些数据(仓)库产品对物化视图的定义,简单说明:物化视图是原始数据某个时刻快照的预计算结果,其中原始数据一般为表或者多张表的join,预计算过程一般是较为简单的sql查询,结果一般都会存储到新的表。可以将物化视图的生成过程抽象为Source、Transform、Sink,数据可以落地到Hdfs、Cos、Clickhouse、kudu等,用来减少数据的重复计算;另外某些场景需要在极短的时间内进行响应,如果直接查询原始数据,一般无法达到业务的需求,预计算后速度可以大大提升;在某些场景下物化视图也是数据资产,例如Cube(维度建模、kylin的概念)代表的业务模型,有时为了节省存储成本,只保留物化视图。

当前业界的数据仓库架构上一般也会对数据进行分层ODS、DW(DWD,DWB,DWS)、ADS,其中DW层一般会被做成数据集市,基于维度建模来对数据进行预计算,给更上层的应用提供服务。DW层减少了大量的重复数据扫描和计算,也可以大大提升用户的查询速度,还可以作为数据资产。看上去物化视图和数据仓库分层从结果上是非常类似的,这里留下一个思考,是否可以减少人力数据仓库分层的工作投入,使用物化视图来替代(tip: 基于用户历史sql ,后续会有文章来说明)。

物化视图作为预计算后的结果,用户一般不需要直接查询物化视图的数据,常用做法是直接查询原始数据,然后查询引擎来进行改写优化将查询语句中可以命中物化视图的部分进行替换,以此来达到自动化加速的目的。当然物化视图改写是非常受限的,后续会有文章来讲改写优化的原理。

这里我们知道,物化视图是基于原始数据某一个时刻的快照来预计算的,那么如果原始数据变动了呢,是不是此时用户只通过物化视图计算出最后结果,则结果不一致;类推到数据仓库分层,即血缘关系没做好,原始数据有了变动,但是没有就此去更新更上层的数据,此时使用DW层计算出的结果和ODS原始数据不一致,数据仓库中有大量的工作也在于此。

预计算数据一致性问题

为什么一开始就要聊数据一致性问题,因为在数据领域一旦计算结果是错的,那么一切努力都是白费的。当然负负得正不在本文的考虑中。

抛开数据从源端开始就是错的和用户自己计算逻辑导致的数据错误,通过直接计算最原始的数据得出的结果一定是正确的。如果物化视图包含的数据,在原始数据中被修改了,那么此时使用物化视图来回答用户的查询请求,则会出现数据异常。

场景说明

为了让大家有个更具象的了解,接下来我们基于Hive的物化视图来做分析,hive的物化视图是基于基表计算而来,同时会将计算结果保存到另外一张表;举例基表为BT1,拥有列(imp_date, a, b, c, d, e),其中imp_date为分区列,物化视图的表为MV1,物化视图的简单sql为:SELECT imp_date, a, b, c FROM BT1 WHERE imp_date >= 20220101 and imp_date < 20220130 。

场景1:

BT1的数据往最后增加了一行 (20220130, a, b, c, d, e),不在物化视图的分区内,则数据一致;

例如用户查询 SELECT imp_date, a, b, c FROM BT1 WHERE imp_date >= 20220101 and imp_date < 20220201,则此时会被查询引擎优化为,SELECT * FROM MV1 UNION ALL SELECT imp_date, a, b, c FROM BT1 WHERE imp_date >20220130 and imp_date < 20220201。

说明:某些查询必须依赖全量的历史数据才能得出正确结果例如精确TopN,此物化视图无法创建成功,即能创建为物化视图的场景是受限的,且都符合场景1 。

场景2:

BT1的数据更新了历史分区 20220101的数据,例如增加一行、删除一行、修改一行,那么此时无论是用户直接查询 MV1 还是通过sql查询引擎自动改写,也会导致该行数据不会被统计,则数据不一致。

解决方案1:需要在更新基表数据时,先下线物化视图,待刷新完物化视图完成后上线。

解决方案2:在一个事务内同时刷新基表和物化视图。(流式物化视图场景)

场景3:

BT1表增加了一列 f,此时物化视图不变,查询时数据一致;

场景4:

BT1表修改了列a的类型或者删除了列a,那么此时如果还用MV1回答用户的sql,那么就可能导致数据不一致。

解决方案1:需要在更新基表的列信息时,先下线物化视图,待刷新完物化视图完成后上线。

解决方案2:查询优化器主动识别改写不匹配,跳过物化视图且标记物化视图为异常。

场景5:

BT1表被删除,此时物化视图失效。如果是物化视图已经可以作为数据资产留下来,那么就可以保留。

解决方案:需要在删除BT1表时,同时删除物化视图。

场景6:

BT1表名被修改为BT2,此时物化视图失效。

解决方案1:表名修改等于,删除表再创建一张表,关联删除物化视图。

解决方案2:关联更新物化视图的元数据,只修改元数据。

上述场景2是物化视图是主要的数据不一致场景,其他的场景一般都可以通过规范化操作来避免,例如场景4,可以规定用户只允许增加列,变成场景3。很多情况用户会把场景2往场景1方向靠,但是由于实际原因基本上不可避免历史数据需要重新刷新的场景。

另外上述的解决方案中,很多场景都需要关联用户的每一次操作,很多时候是比较难以处理的,例如Hive虽然可以控制用户的INSERT、LOAD等操作,以此来同步更新物化视图,但是用户还可以直接往原始表的hdfs目录直接写数据。其实 SuperSql 物化视图也存在该问题,内部系统繁杂,想获取用户针对数据的所有操作是较难实现的。

在流式构建物化视图的场景下,如果要实时刷新历史所有的物化视图是不现实的,一般会采取同一个事务的方式进行更新,物化视图的场景也会特别受限,吞吐是个大问题。

结论

理论上当我们可以监听到所有更新,清理掉当前命中物化视图的查询后刷新物化视图,那么就可以做到物化视图数据的一致性。但是在实际场景中,考虑到性能问题不太可能所有存储计算引擎的元数据能同步更新,一般会使用异步的方式,所以会导致物化视图和原始数据会有一段时间的不一致;同时也可能出现用户操作事件丢失,导致数据永远无法一致;当然可以使用定时刷新物化视图,那计算成本又上升来了。

开放环境下,要从工程实践上想保证物化视图与原始数据的一致性,只能规定用户在场景1和场景3的情况下才可以使用物化视图;但是用户一般存在其他场景,此时考虑让高级用户自己去保证数据一致性,即用户自己监听到数据的更新,产生对应的操作。

在大数据场景下,部分数据的变动,可能并不影响整体的统计结果;用户也不需要关心一段时间内的数据不一致,此时用户只要专注关心如何加速查询即可。

Reference

https://en.wikipedia.org/wiki/Materialized_view

https://clickhouse.com/docs/en/sql-reference/statements/create/view#materialized

https://cwiki.apache.org/confluence/display/Hive/Materialized views

https://docs.cloudera.com/HDPDocuments/HDP3/HDP-3.1.0/using-hiveql/content/hive_using_materialized_views.html

https://trino.io/docs/current/sql/alter-materialized-view.html

https://github.com/prestodb/presto/blob/e3c9af6c4b386c4dd366e3fea4760a2a49592086/presto-hive/src/test/java/com/facebook/presto/hive/TestHiveIntegrationSmokeTest.java

https://www.youtube.com/watch?v=U-bANi2eGC8

https://cloud.google.com/bigquery/docs/materialized-views-intro

https://docs.aws.amazon.com/redshift/latest/dg/materialized-view-auto-rewrite.html

0 人点赞