基于Doris Unique Key与临时分区实现亿级别数据防抖动技术

发布: 2024-04-19   作者: Youcai   分类: BigProject 标签: Doris BigData  未经允许,禁止转载(可分享链接)!


写在前面

本文编写于2024-04-19,最近2026-03-28进行了重新编辑,主要对内容进行压缩,去掉了跟业务有关的文字,由原来PDF排版改成网页排版。内容是基于某广告消耗统计系统的实际案例,分析了在业务侧将数据更新频率由每日一次调整为每小时一次后,导致查询结果出现大幅波动的情况。简单点说就是,在更新过程中存在新旧数据混合的中间状态,对数据查询造成了干扰。在低频更新或数据量比较小的情况下,该问题很难暴露,而在高频大数据更新情况下,如果更新过程与查询发生重叠,导致用户读取到不完整或会产生误导的数据,也叫数据抖动问题。针对该问题,本文提出了一种基于 Apache Doris的数据防抖动技术,主要是通过结Unique Key模型与分区替换机制,实现数据计算与数据查询过程的隔离,在亿级数据查询条件下也实现数据的稳定性与一致性。


1. 结论(Conclusion)

在大规模数据处理系统中,合理利用 Doris 的临时分区(Temporary Partition) 与 Unique Key 数据模型,能够显著降低数据更新过程中产生的数据抖动问题。

在亿级数据规模下,该方案具备如下工程价值:

  • 有效避免数据更新过程中的中间态被用户读取
  • 提高系统查询结果的一致性与稳定性
  • 降低数据更新期间的业务风险
  • 提供更加可靠的数据观测能力

本文从实际业务场景出发,分析数据抖动问题产生的根本原因,并逐步介绍多种常见实现方案,最终给出基于 Doris 的工程化解决方案。通过对方案演进过程的系统性说明,读者可深入理解该技术在大规模数据场景中的实践价值。


2. 背景(Introduction)

在 2023 年 9 月的一次生产运行过程中,业务系统出现了明显的数据波动现象。具体表现为:

  • 在 11:00 查询某媒体广告消耗数据时,结果约为 1000 万美元
  • 在 15:00 查询同一数据时,结果约为 400 万美元
  • 在 19:00 再次查询时,结果约为 1500 万美元

上述数据虽为示例数值,但其变化趋势表明,在不同时间点查询同一业务指标时,系统返回结果存在显著差异。该问题原来并没有存在,也就是这几天经常出现的事情,通过上线日志发现,最近数据更新策略发生了变化。原来系统每天 07:00 执行数据更新任务,而业务人员一般在 09:00 之后查询数据。所以9点前,即使存在数据变化的中间状态,但也不会被用户感知,系统整体表现稳定。

随着业务需求的变化,数据策略在近期进行了调整:由每日更新一次,改为每小时执行一次更新任务。经深入的技术排查,就是这个问题引起的:数据更新过程中中间状态被用户读取,从而导致数据可见性不一致。

基于上述分析结果,团队启动了针对数据处理流程的优化工作,重点目标包括:

  • 提升数据更新过程的稳定性
  • 减少用户可感知的数据波动
  • 保证查询结果的一致性

本文将对该问题的形成机制以及最终优化方案进行系统性说明。


3. 问题原因分析(Problem Analysis)

数据抖动问题的本质,通常来源于:数据更新过程中的中间状态被用户读取。

在对数据执行以下操作时:

  • 新增(Insert)
  • 删除(Delete)
  • 修改(Update)

不同时间点查询同一数据,可能得到不同的计算结果。在数据规模较小的情况下,该问题通常不易被观察;但在百万级或亿级数据规模下,该问题会被显著放大,并可能影响业务决策。以下通过若干典型场景进行说明。

3.1 被感知的数据更新过程

假设某广告主在广告平台投放广告,并每日产生广告消耗数据。在一次数据更新过程中,系统可能执行如下操作:

  • 删除 2 条错误归属数据
  • 新增 3 条新的归属数据
  • 修改 2 条已有数据
照片类型
图3-1 数据更新过程被查看数据

假设初始余额为:500 万美元

在执行上述多个操作过程中,如果用户在不同时间点进行查询,例如:

  • t1
  • t2
  • t3

则可能分别观察到如下结果:

  • t1:100 万美元
  • t2:1000 万美元
  • t3:1100 万美元

虽然最终余额为正确值,但由于查询发生在不同阶段,用户可能在更新过程中观察到不一致的数据。

当数据更新耗时较长时,该问题将更加明显,并可能导致业务层误判。

该现象说明:能被用户感知的数据更新中间态是数据波的原因。

3.2 数据更新顺序不一致

在分布式系统中,数据处理顺序往往无法严格保证一致,假设上面的数据,顺序乱了,按下面的数据顺序来到达(主要是数据依赖的Kafka是异步消息)。

如果调整新增与删除操作的执行顺序,则可能得到另一组查询结果:

  • t1:1400 万美元
  • t2:1000 万美元
  • t3:1100 万美元

虽然最终结果仍然一致,但中间阶段的数据差异依然明显。

照片类型
图3-2 更新顺序不一致

该现象说明:数据执行顺序的不确定性是数据波动的原因。

3.3 重复消费数据造成误差

在消息队列系统(如 Kafka)中,如果消费逻辑未进行严格控制,可能产生:

  • 数据重复消费
照片类型
图3-3重复消费不一致

因为程序异常挂机,造成Kafka偏移量不正确,从而导致数据重复累积,最终造成数据异常增长。


4. 常见实现方案(MySQL 版本)

在理解数据抖动问题产生机制之后,有必要先分析传统关系型数据库(如 MySQL)中的常见处理方案,为后续 Doris 方案提供对比基础。

4.1 基于版本号的数据替换方案

该方案通过引入 版本号(Version) 字段,实现数据更新控制。

假设初始数据如下,同时分配版本号 Version = 1:

广告主 投放地区 消耗金额 消耗日期 版本号
11111 US 100 2024-04-20 1
11111 UK 200 2024-04-21 1
11111 SG 100 2024-04-22 1

第二天系统从外部接口获取到新的数据,数据如下表所示:

广告主 投放地区 消耗金额 消耗日期
11111 US 100 2024-04-20
11111 UK 300 2024-04-21
22222 IN 100 2024-04-22

此时:

  • 原有 SG 数据被删除
  • UK 数据金额发生变化
  • 新增 IN 数据

更新流程如下,首先写入新数据,并带上版本号 version=2:

广告主 投放地区 消耗金额 消耗日期 版本
11111 US 100 2024-04-20 2
11111 UK 300 2024-04-21 2
11111 SG 100 2024-04-22 1
22222 IN 100 2024-04-22 2

随后删除旧版本数据,这里是删除version != 2的数据,最终得到:

广告主 投放地区 消耗金额 消耗日期 版本
11111 US 100 2024-04-20 2
11111 UK 300 2024-04-21 2
22222 IN 100 2024-04-22 2

该方案的优点是逻辑清晰,但在更新过程中仍可能出现数据短暂不一致。

4.2 全量删除后重新插入方案

另一种简单但风险较高的方案为:

DELETE FROM table;

随后重新插入全部数据,该方案在执行过程中,系统可能出现:

  • 数据瞬时为 0
  • 数据逐步增长

因此用户在更新过程中查询数据时,会明显感知数据变化,该方案虽然实现简单,但在大规模系统中风险较高。

4.3 新方案设想

综合上述方案,可以提出一种更加理想的设计思路:在数据更新过程中,新数据对用户不可见,待计算完成后再整体切换,见下图4-3所示。

照片类型
图4-3 数据更新假想图

该思路类似于:双数据结构 + 指针切换机制,其基本逻辑如下:

  • 系统内部维护两份数据
  • 新数据在后台计算中时
  • 查询仍访问旧数据
  • 当新数据准备完成后,再统一切换到新数据

通过该方式,可以避免用户读取中间状态数据。


5. Doris 方案调研

基于上述设计思路,可以利用 Doris 提供的特性,实现无抖动数据更新机制。

核心思路为:利用临时分区构建新数据,并通过分区替换实现原子切换。

5.1 Doris数据库关键特性

什么是Doris,它是一款基于 MPP 架构的开源实时分析型数据库,主打极速查询、极简架构与实时数仓能力,简单理解就是:兼容 MySQL 协议的分布式分析型数据库。在本方案中,主要使用以下两个核心特性。

Unique Key 数据模型

Doris 提供多种数据模型,其中 Unique Key 模型 在本方案中具有重要作用,大概原来跟redis基本一样,见下图所示。

照片类型
图5-1 模型基本原理

在 Unique Key 模型中:

  • 部分字段定义为 Key 列
  • 其余字段定义为 Value 列
  • 当插入具有相同 Key 的记录时,新记录将覆盖旧记录

例如执行如下SQL:

Insert into table(f1,f2,f3,f4,f5,f6,f7,f8,f9)  
values(A,B,C,D,1,1,1,1,1);

Insert into table(f1,f2,f3,f4,f5,f6,f7,f8,f9)  
values(A,B,C,D,2,2,2,2,2);

Insert into table(f1,f2,f3,f4,f5,f6,f7,f8,f9)  
values(A,B,C,D,3,3,3,3,3);

若:(f1,f2,f3,f4)被定义为 Unique Key,则最终数据库中仅保留:

A,B,C,D,3,3,3,3,3

该机制可有效支持:

  • 覆盖更新
  • 幂等写入
  • 数据版本替换

在大规模数据更新场景中具有重要工程价值。

数据分区(Partition)机制

Doris 支持灵活的数据分区管理,详细技术使用可以到官网上查看,包括:

  • 分区(Partition)
  • 分桶(Bucket)
  • 临时分区(Temporary Partition)

在本方案中,重点使用,逻辑分区与临时分区。

通过将数据按时间字段划分到不同分区,可以实现数据的高效管理与替换操作。

例如,可按日期字段创建分区:

PARTITION BY RANGE(report_date)

当数据写入时,将自动进入对应时间范围的分区。

此外,系统支持动态创建 临时分区,用于存储临时计算数据。临时分区具有如下特性:

  • 可动态创建
  • 不影响正式分区
  • 可在计算完成后删除
  • 支持分区级替换

该机制满足4.3节的新方案设想,为后续的数据无抖动更新提供了重要基础。

5.2 基于临时分区的数据防抖动方案

在前述设计思路的基础上,可以利用 Doris 的 Temporary Partition(临时分区)机制 实现数据更新过程中的隔离计算。

其核心思想为:在独立的临时分区中完成数据计算,在确认数据完整后,再通过分区替换机制一次性切换到正式分区。

该方案的基本流程如下:

按时间维度读取外部系统(如 Facebook)的最新数据
创建对应时间范围的临时分区
将新数据写入临时分区
使用分区替换(Partition Replace)机制,将临时分区替换为正式分区
完成数据切换

在整个计算过程中:

  • 用户查询使用的还是旧数据
  • 新数据仅存在于临时分区
  • 切换操作具有原子性

因此,用户无法感知数据更新过程中的中间状态。

5.2.1 基础实现流程示例

假设需要更新 2024-04-24 的广告消耗数据,其典型处理流程如下。

首先,从外部系统读取指定日期的数据:

facebook_data = fetch_url(
    "http://facebook.com/api/2024-04-24"
)

随后,在目标表中创建临时分区:

ALTER TABLE adv_spend_table
ADD TEMPORARY PARTITION temp_partition_20240424
VALUES [('2024-04-24'), ('2024-04-25'));

需要注意:

该时间范围采用 左闭右开区间:

[2024-04-24, 2024-04-25)

然后,将读取到的数据写入临时分区:

INSERT INTO adv_spend_table
TEMPORARY PARTITION temp_partition_20240424
(report_date, adv_id, geo, spend, version)
VALUES (...);

待数据写入完成后,执行分区替换操作:

ALTER TABLE adv_spend_table
REPLACE PARTITION (p20240424)
WITH TEMPORARY PARTITION
(temp_partition_20240424);

该操作的本质为:使用临时分区中的数据整体替换原有正式分区的数据。

此操作具备以下特点:

  • 原子性(Atomic Operation)
  • 高度一致性
  • 无中间状态暴露

因此,在数据更新过程中,用户始终只能读取到完整数据。

5.2.2 基础方案的实践意义

该方案能够有效解决以下问题:

  • 数据更新过程中的余额波动问题
  • 中间态数据被查询的问题
  • 批量更新导致的数据不一致问题

在中等规模数据场景中,该方案已经能够满足大部分业务需求。然而,在超大规模数据场景(例如亿级数据写入)中,仍存在进一步优化空间。


6. 总结(Conclusion)

本文从实际业务问题出发,系统分析了数据更新过程中出现数据抖动的主要原因,并对多种实现方案进行了对比与优化。首先,通过分析传统数据库方案,指出以下常见问题,随后,提出基于 Doris 的数据更新优化思路,并重点介绍了以下核心技术。

  • 分批更新导致中间态数据暴露
  • 全量删除再插入造成瞬时数据缺失
  • 异步数据处理带来的顺序不确定性

6.1 核心技术总结

本方案的关键技术包括:

Unique Key 数据模型

通过覆盖写入机制,实现幂等更新能力,减少重复数据问题。

Temporary Partition 临时分区

通过构建隔离计算环境,实现数据更新过程的可见性隔离。

Partition Replace 分区替换

通过原子切换机制,实现数据的整体替换,避免中间态暴露。

6.2 工程实践价值

在实际生产环境中,该方案能够显著提升系统稳定性,并有效解决以下问题:

  • 数据查询结果波动问题
  • 大规模数据更新风险
  • 用户可见数据不一致问题

在亿级数据规模场景中,该方案表现出良好的稳定性与可扩展性。