[Clickhouse问题]: 物化视图初始化数据丢失与重复问题

存在的问题

现在CK创建视图有2种初始化数据的方式

  1. 在 CREATE 语句中带上 polulate 关键字,会将执行时间点之前的底表的历史数据全部初始化到视图中。执行完成后的底表新数据也会进视图,但是执行过程中的数据会丢失

  2. 在 CREATE 语句中不带 polulate 关键字,会将执行完成后的底表新数据会进视图,但是执行完成之前的数据都不会进视图;如果而后手工执行insert命令, 导入历史数据, 那么重复数据

Polulate数据丢失

根因分析

先明晰一下MV更新的时候的时序图

下图中有4个角色: 插入语句执行者, 底表插入数据执行者, MV1插入数据执行者, MV2创建语句执行者

image-20220227113952566

首先, 对于底表来说, 第一批写开始时, 因为MV2并没有被创建, 因此对于它来说, 只会给MV1主动推送数据, 相当于这批次的数据全部丢失了.

其次, 对于MV2创建来说, 由于带有populate字段, 他会主动去拉取历史数据, 但是由于底表没有全部写完, 只写了DP1, 那么DP2和DP3的数据就丢失了.

因此, 总结来说, 在插入过程之中创建底表会丢失的数据是, 在底表里DataPart没有写完的那些数据, 写完的数据还是会被读取的.

最后, 看第二批写的情况, 由于检查MV的时候, MV1和MV2已经存在(虽然这个时候, 创建MV2的语句被没有结束, 但是由于CK没有一致性保证, 所以元数据的信息可以被外面捕获到), 此时第二批次的数据, 在正常情况下能够推送给MV2.

解决方案

利用存储的锁机制, 在插入时禁止populate操作

image-20220227114132890

  1. 写入时, 加入写入的共享锁, 可以支持同时插入到一个数据表中
  2. 执行create table的元数据创建时, 获取底表的排它锁, 如图中所示, 此时正好在插入, 则会等待插入执行完毕后, 再进场元数据操作
  3. 元数据执行完毕后立即释放锁, 耗时的populate阶段, 是无锁状态. 因此第二次插入, 只需要等待极小的一个时间即可执行数据插入动作.

使用限制

目前如果有大型insert的操作的话, 此时会无法完成基于底表的创建视图操作

复制表问题

目前视图使用create view to table的方式建立的, 如果视图里面带有populate, 就会出现MV多数据的情况, 原因如下图所示

image-20220227114313082

解决方案

只允许一个节点写入, 跟DLAP-Manager交流, 目前复制表只有一个节点可写, 符合预期

image-20220227114345189

从上面的分析来看, 整体物化视图的初始化, 比较合适构建在外部的Manager中. 但此时就需要处理场景2中数据重复的问题

Insert数据重复

Insert数据重复比较好了解, 因为是人工操作, 因此创建完毕物化视图, 和执行insert命令期间, 有可能已经有一些批次的数据写入到物化视图中, 如果此时导入全部的历史数据, 那么数据就会出现重复的情况.

去重视图一般不影响, 但如果是聚合是视图, 那么结果将不正确.

解决方案

数据快照

大底表的处理方案

  1. 创建视图时, 获取底表所有的分区

  2. 通过参数设置并发, 创建并发个数的临时表

  3. 按照分区修改插入语句, 此时引擎测需要保证: 该表达式能够完成分区裁剪, 并在实际执行时忽略掉判断(这是一个难点)

    where splitByChar(‘‘,_part)[1]=’1993’ and toInt32(splitByChar(‘‘,_part)[3]) < 500

  4. 临时表的插入, 只能有一个并发. 插入完毕后, 用attach parition的方式, 将分区加入到物化视图的底表中, 然后删除临时表, 再重新创建一个临时, 开启下一轮操作.

  5. 如果插入临时表时候, 系统需要自动清空(或者删除重建)临时表, 并重试. 如果attach时失败, 则返回异常, 由用户删除物化视图, 并开始重建工作.

  6. 期间Clickhouse Server节点如果出现异常, 则整个任务失败. 如果DLAP-Manager出现重启, 则可以继续redo整个任务.