跳到主要内容

文件缓存内部原理

基本原理

(一)缓存切片和预读机制

Doris 采用缓存切片和预读机制来优化数据的缓存管理和读取效率。具体来说,目标文件会被按照1MB 对齐进行切片,每一片数据在完整下载后,会被存储在本地文件系统中作为一个单独的 Block 文件。这种切片方式可以有效减少缓存的粒度,提高缓存灵活性和空间利用率。Doris 可以根据实际需求仅缓存部分数据,避免缓存整个大文件带来的空间浪费。同时,小块缓存也便于管理和淘汰,能够更精准地命中热点数据。

(二)本地文件目录组织

为了更好地管理缓存数据,Doris 采用了特定的本地文件目录组织方式。缓存可能分布在多块磁盘的多个目录中,为了实现数据在多个目录的均匀分布,Doris 会根据缓存目标文件的路径计算哈希值,并将该哈希值作为 Block 文件存放路径的最后一级目录。目录中每个 Block 文件的命名则基于缓存数据在目标文件中的偏移量。

例如,假设目标文件路径为/remote/data/datafile1,计算其哈希值为12345,则缓存的 Block 文件可能被存放在/cache/123/12345/offset1的路径下,其中offset1表示该 Block 数据在原文件中的偏移位置。

(三)多队列机制

Doris 的文件缓存采用了多队列机制,将不同类型的数据分开管理,以避免缓存污染并提高缓存的命中率。具体来说,缓存数据被分为以下几类,并分别存储在不同的队列中。这些队列按照重要程度的优先级排序如下:

  • TTL 队列:存储设置了 TTL(Time-To-Live,生存时间)属性的数据。这类数据在缓存中保留的时间由 TTL 值决定,在 TTL 时间内,这些数据具有最高优先级,不会被轻易淘汰。当缓存空间不足时,系统会优先淘汰其他队列中的数据,以确保 TTL 数据能尽可能长久地存在于缓存中。TTL 是表的属性,比如设定为 3600 则表示凡是导入此表中的数据,在导入完成后 1小时内都尽量存在于文件缓存中。应用场景:适用于希望在本地持久化的小规模数据表。例如,对于常驻表,可以设置较长的 TTL 值来保护其数据。
  • Index 队列:存储索引数据,这类数据主要用于加速查询中的过滤操作,通常具有较高的访问频率。特别的,反向索引文件虽然是“索引”但因其数据量通常很大,为了避免占用 Index 队列,我们把它作为 Normal 队列处理。
  • Normal 队列:存储普通数据,这些数据没有设置 TTL 属性。大部分数据都属于普通数据。
  • Disposable 队列:存储临时使用的数据,比如 compaction 读取的数据。这类数据通常在使用完毕后会被淘汰,优先级最低。

通过这种多队列机制,Doris 能够根据不同类型数据的特点和使用场景,合理分配缓存空间,最大化地利用缓存资源。

(四)淘汰机制

缓存淘汰机制是文件缓存管理中的关键环节,它决定了在缓存空间不足时如何选择需要被淘汰的数据,以为新的数据腾出空间。Doris 的淘汰机制包括以下几种触发时机和目标选择策略。

淘汰发生的时机

  • 空间紧张被动淘汰:
    • 当本地磁盘空间或 inode 数量不足时,Doris 会触发被动淘汰机制,以释放空间。
    • 达到缓存容量设定值:虽然磁盘还有空间但如果缓存空间已经达到了预先设定的容量上限,系统也会启动淘汰机制,淘汰部分数据以腾出空间。
  • 主动提前淘汰:上一种淘汰属于同步淘汰,新数据需要等待旧数据换出才能进入缓存,这会影响当前查询的效率。为了避免这种极端情况的发生,Doris 会在缓存空间达到高水位线时,提前异步地清理旧缓存。
  • 主动垃圾回收淘汰:虽然 LRU 策略能够淘汰无用数据,但为了进一步优化缓存空间,Doris 会主动清理一些垃圾数据,如 compaction、schema change 的原始数据、导入 commit 失败回滚掉的数据 以及 drop table/partition 后的数据。
  • TTL 到期:这是TTL类型数据独有的淘汰机制,即使数据量没有达到上限,但当其中的数据 TTL 时间到期后,这些数据会先进入到 Normal 队列中,降级为普通数据。之后,这些数据将作为 Normal 数据参与正常的淘汰过程。

淘汰目标的选择:

  • 淘汰比例:多个队列共享磁盘空间,并用各自的比例作为自身空间的限制,保证其它队列有足够的空间。在空间足够(其它队列数据量没有占满其分配比例)时,队列可以使用所有剩余磁盘空间。例如系统 Normal 队列的空间被限制在总空间的 40%,但若系统只有 Normal 数据而没有其它类型的数据,那么它可以占满所有可用空间。后面随着其它队列数据的进入,各个队列的占比逐渐趋近预设比例。
  • 淘汰顺序:在写入缓存空间不足时,Doris 会按照 Disposable、Normal、Index、TTL的顺序淘汰数据。例如,如果写入 Normal 时空间不足,那么 Doris 会依次淘汰 Disposable、Index、TTL 队列超出比例部分的数据(各队列按照 LRU 的顺序选择淘汰目标)。如果按照顺序淘汰其他类型的数据后仍不能成功腾出足够的空间,那么将会触发自身类型的 LRU 淘汰。

避免目标数据淘汰的建议:

  • 充足的磁盘空间:确保有足够的磁盘空间来容纳缓存数据,避免因空间不足而频繁触发淘汰机制。因为缓存清理有一定的滞后性,需要留有一定余量。根据经验,一般文件缓存空间约为查询热数据的 1.5 倍可以保证查询数据命中文件缓存。
  • 大查询隔离:将大查询隔离到其他集群,避免因大查询占用大量缓存空间而影响其他查询的缓存命中率。

(五)预热机制

缓存预热是指将数据提前加载到缓存中,以便在后续查询中能够快速命中缓存,提升查询性能。Doris 提供了多种缓存预热机制:

  • 手动预热:用户可以对当前集群的缓存进行预热,预热目标可以是表和分区,也可以使用一个已有集群作为参考,预热已有集群上缓存的表和分区。预热下载的数据源始终是远程存储,而不是其他集群或者其他 BE。用户执行预热指令后,无论目标是表、分区还是参考集群,最终都会转换成一个 tablet 集合,发往 tablet 所在 BE去执行下载。BE 下载的逻辑本质上是对这个 tablet 的所有数据文件进行一次顺序读,这样数据便能缓存在本地文件缓存上。由于预热的数据量可能很大,Doris 会将整个预热任务切分成最大 20GB 粒度的批次依次执行。每完成一批数据的下载,系统会做一个存档点,方便任务中断后恢复执行。如果 BE 节点在下载过程中发生严重问题(比如宕机),或者用户手动执行了取消预热的命令,那么所有的 BE 都会停止下载并结束这次预热。用户可以通过 SHOW WARM UP JOB 浏览当前任务的执行状态(如 FINISHEDCANCELLEDRUNNING)。如果是 RUNNING 状态,则可以查看整体完成进度。用户可以对同样的表和分区进行重复的预热,Doris 会智能识别并不会重复下载已有数据,只会增量更新。
  • 数据均衡触发的预热:当 BE 上的 tablet 数量分布不均时,Doris 会自动进行负载均衡,特别是在节点宕机或者运维进行扩缩容操作时。当一个 tablet 迁移到新的 BE 时,新 BE 会发起 RPC 到旧 BE(如果旧 BE 依然有响应)拉取该 tablet 之前的缓存数据的元信息,并在新 BE 节点利用元信息重新下载数据到文件缓存。这样可以保证新 BE 上的查询也能命中文件缓存。旧节点上的对应缓存数据会在旧 tablet 信息清理时一并主动淘汰以释放空间。需要注意的是,迁移后数据下载到缓存需要一定的时间,在这个时间窗口内可能会发生文件缓存未命中的情况。
  • 计算集群间自动预热(3.1 以后版本支持):在存算分离场景下,用户可能希望多个计算集群的文件缓存能够自动同步,例如数据的导入在 A 计算集群完成,而查询发生在 B 计算集群,就需要A集群把导入数据的缓存同步到B集群。Doris 提供了两种自动同步文件缓存的方式:
    • 周期预热:对于查询另一个集群导入的数据实时性不高的需求,可以在WARM UPSQL 语句中加入同步周期。这样预热任务不再是执行一次就结束,而是会周期性地将一个集群上指定表和分区的数据以增量的方式同步到另一个集群上。
    • 导入和 compaction 触发的预热:对于同步实时性要求高的用户,可以使用导入完成事件触发的预热功能。因为 tablet 在不同集群的分布不同,FE 会将同步目标集群上的 tablet 分布告知源集群。在源集群导入进入 commit 阶段时,会利用上一步的信息找到当前 tablet 所在目标集群的具体 BE。源集群 BE 通知目标集群对应的 BE 下载刚刚导入时上传到远程存储上的数据完成预热。对于 compaction,同样会通过类似的通知路径完成预热。

情景分析

(一)查询场景下文件缓存的工作原理

在查询过程中,文件缓存的作用是减少对远程存储的访问,加速数据读取。以下是查询场景下文件缓存的工作原理:

  • Scanner 读取数据文件内容:当查询请求到达 Doris 时,Scanner 组件会尝试读取所需的数据文件内容。
  • 查询本地文件缓存:在访问远程存储之前,Scanner 会首先查询本地文件缓存,检查所需数据是否已经缓存在本地。
  • 缓存命中:如果文件缓存根据文件路径和偏移信息,在内存缓存管理元数据中找到对应的缓存数据,则返回缓存数据的BlockFile 文件句柄集合,供 Scanner 读取。这样可以避免从远程存储下载数据,显著减少查询延迟。
  • 缓存未命中:如果读取数据的范围中部分或全部未命中缓存,则 Scanner 会访问远程存储下载未命中范围对应的数据。下载完成后,这些数据会被存放在文件缓存中,供后续查询使用。同时,Doris 会根据缓存策略决定是否淘汰其他缓存数据以腾出空间。

(二)导入场景下文件缓存的工作原理

在数据导入过程中,文件缓存的作用是为后续的查询操作提前准备数据。以下是导入场景下文件缓存的工作原理:

  • 数据上传到远程存储:在导入数据时,数据首先会被上传到远程存储。
  • 异步写入本地缓存:同时,Doris 会异步地将这些数据写入本地磁盘的文件缓存中。这样做的目的是为了在导入完成后,紧接着的查询操作可以直接命中缓存,提升查询性能。
  • 缓存类型:导入数据时,根据数据的类型和设置的属性(如是否设置了 TTL),数据会被写入到对应的缓存队列中(如 TTL 队列、Index 队列或 Normal 队列)。

(三)Compaction 场景下文件缓存的工作原理

Compaction 是 Doris 中用于优化数据存储和查询性能的操作,它会将多个小的数据文件合并成一个大的数据文件。Doris 中 Compaction 主要分为两种:负责增量数据间合并的 Cumulative Compaction,以及负责基线数据版本(以0为起始版本的数据)和增量数据版本合并的 Base Compaction。

在 Compaction 过程中,文件缓存的处理方式如下:

  • Cumulative Compaction:输出的新数据在上传到远程存储的同时,会进入文件缓存。这一过程与数据导入时的缓存写入类似,主要是为了加速后续的查询操作。
  • Base Compaction:由于 Base Compaction 通常涉及大量冷数据,且数据量较大,为了兼顾缓存命中率和避免缓存污染,Doris 只有在缓存空间足够的情况下,才会将 Base Compaction 产生的新数据写入文件缓存。用户可以通过设置 BE 配置参数enable_file_cache_keep_base_compaction_output = true,强制让新数据进入文件缓存,从而提高命中率。但需要注意的是,开启后可能导致其他热数据被淘汰,影响其他查询的命中率。Doris 计划在未来版本中提供更完善的自适应策略,结合历史查询统计信息来辅助判断新数据是否进入文件缓存。

(四)重启后文件缓存加载原理

在 Doris 节点重启后,文件缓存的加载过程对于恢复缓存状态和快速响应查询至关重要。在3.1之前的版本中,由于文件缓存 LRU 信息未持久化,重启后 LRU 队列顺序与重启前不一致,导致热数据被淘汰,影响缓存的命中率。

在 3.1 版本中,我们引入了 LRU 信息持久化功能,其原理如下:

  • 定期 dump:Doris 将各个 LRU 队列的顺序信息定期 dump 到磁盘上。
  • 重启后加载:在节点重启时,Doris 会从磁盘加载这些 dump 的 LRU 信息,恢复缓存队列的状态。
  • 全盘扫描加载:由于周期性 dump 时间窗口可能导致元数据和磁盘上的文件不一致,因此 Doris 会在加载 LRU 信息后,对磁盘进行一次全盘扫描,以查漏补缺,确保缓存数据的完整性和准确性。
  • 查询触发并发异步加载:全盘扫描需要一定时间,为了使 BE 节点能够及时提供服务,扫描期间 BE 可以用于查询。如果查询到的数据尚未被扫描到,则会先提前加载,从而减少查询延迟。

(五)扩缩容场景缓存的处理

扩缩容操作是集群管理中的常见场景,Doris 在扩缩容过程中对文件缓存的处理方式如下:

  • 横向扩容:在横向扩容时,Doris 会通过均衡操作将 tablet 迁移到新增的 BE 节点上。当 tablet 迁移到目标 BE 后,目标 BE 会根据源 BE 上的缓存数据元信息,重新下载一份缓存到本地。这样可以保证新节点上的查询也能命中文件缓存。
  • 横向缩容:除了与横向扩容类似的 tablet 均衡操作外,当缩容后集群整体文件缓存容量减小到实际缓存数据量以下时,会发生淘汰。淘汰过程遵循上文提到的淘汰机制,Doris 会根据缓存策略选择性地淘汰部分数据以适应新的缓存容量。
  • 纵向扩容
    • 增加磁盘数量:不建议通过增加磁盘数量的方式来扩容缓存,因为 Doris 目前没有实现 rehash 操作,不会进行磁盘间的均衡。而且缓存目录数量的变动可能导致之前缓存查询故障。如果确实需要增加磁盘数量,则需要清理缓存并按需预热。
    • 增加单盘容量:如果是保持磁盘数量不变,仅通过增加单个磁盘的空间来扩容缓存,则需要通过 curl http://BE_IP:WEB_PORT/api/file_cache?op=reset&capacity=123456 命令告知 Doris BE 空间的变更。
  • 纵向缩容
    • 减少磁盘空间:同样需要执行上述reset操作。需要注意的是,当文件缓存容量减小到实际缓存数据量以下时,会发生淘汰。淘汰过程遵循上文提到的淘汰机制。
  • 扩容后的预热注意事项:因为横向扩缩容会有 tablet 均衡的操作,如果此时需要预热,需要等待迁移稳定后再执行预热命令,以确保预热的效果和效率。通过监控 fe 的doris_fe_tablet_num 这个metrics,如果曲线没有波动则说明预热结束。