跳转至

8.6 数据缓存

在存算分离的架构中,数据被存储在远程存储。 Doris 数据库通过利用本地硬盘上的缓存来加速数据访问,并采用了一种先进的多队列 LRULeast Recently Used )策略来高效管理缓存空间。这种策略特别优化了索引和元数据的访问路径,旨在最大化地缓存用户频繁访问的数据。针对多计算组( Compute Group )的应用场景, Doris 还提供了缓存预热功能,以便在新计算组建立时,能够迅速加载特定数据(如表或分区)到缓存中,从而提升查询性能。

1 多队列 LRU

1.1 LRU

  • LRU 通过维护一个数据访问队列来管理缓存。当数据被访问时,该数据会被移动到队列的前端。新加入缓存的数据同样会被置于队列前端,以防止其过早被淘汰。当缓存空间达到上限时,队列尾部的数据将优先被移除。

1.2 TTL (Time-To-Live)

  • TTL 策略确保新导入的数据在缓存中保留一段时间不被淘汰。在这段时间内,数据具有最高优先级,且所有 TTL 数据之间地位平等。当缓存空间不足时,系统会优先淘汰其它队列中的数据,以确保 TTL 数据能够被写入缓存。

  • 应用场景: TTL 策略特别适用于希望在本地持久化的小规模数据表。对于常驻表,可以设置较长的 TTL 值来保护其数据;对于动态分区的数据表,可以根据 Hot Partition 的活跃时间设定相应的 TTL 值。

  • 注意事项:目前系统不支持直接查看 TTL 数据在缓存中的占比。

1.3 多队列

  • Doris 采用基于 LRU 的多队列策略,根据 TTL 属性和数据属性将数据分为四类,并分别置于 TTL 队列、 Index 队列、 NormalData 队列和 Disposable 队列中。设置了 TTL 属性的数据被放置到 TTL 队列,没有设置 TTL 属性的索引数据被放置到 Index 队列,没有设置 TTL 属性的索引数据被放置到 NormalData 队列,临时使用的数据被放置到 Disposable 队列中。

  • 在数据读取和写入过程中, Doris 会地选择填充和读取的队列,以最大化缓存利用率。具体机制如下:

    操作 未命中时填充的队列 写入数据时填充的队列
    导入 TTL / Index / NormalData TTL / Index / NormalData
    查询 TTL / Index / NormalData N/A
    schema change Disposable TTL / Index / NormalData
    compaction Disposable TTL / Index / NormalData
    预热 N/A TTL / Index / NormalData

1.4 淘汰

上述各类型缓存共同使用总缓存空间。根据重要程度的不同我们可以为它们划分比例。比例可以在 be 配置文件中通过 file_cache_path 设置,默认为: TTL : Normal : Index : Disposable = 50% : 30% : 10% : 10%

这些比例不是硬性限制, Doris 会根据需要动态调整以充分利用空间来加速访问。例如用户如果不使用 TTL 类型的缓存,那么其它类型可以超过预设比例使用原本为 TTL 分配的空间。

缓存的淘汰有两种触发时机:垃圾清理或者缓存空间不足。当用户删除数据时,或是导入 compaction 任务结束时,会异步地对过期的缓存数据进行淘汰。写入缓存空间不足时,会按照 DisposableNormal DataIndexTTL 的顺序淘汰。例如:如果写入 Normal Data 时空间不足,那么 Doris 会依次淘汰 DisposableIndexTTL 的部分数据(按照 LRU 的顺序)。注意我们不会将淘汰目标类型的数据全部淘汰再淘汰顺序中的下一个类型,而是至少会保留上述比例的空间以让其它类型也能正常工作。如果这个过程不能成功淘汰出足够的空间,那么将会触发自身类型的 LRU 淘汰。接着上面写 Normal Data 时空间不足例子,如果不能从其它类型中淘汰出足够的空间,此时 Normal Data 将从自身按照 LRU 顺序淘汰出数据。

其中特别注意的是,对于带有过期时间的 TTL 队列,其数据过期时会被移动到 Normal Data 队列,作为 Normal Data 参与淘汰。

2 缓存预热

在存算分离模式下, Doris 支持多计算组部署,各计算组间共享数据但不共享缓存。新计算组创建时,其缓存为空,可能影响查询性能。为此, Doris 提供缓存预热功能,允许用户从远端存储主动拉取数据至本地缓存。该功能支持以下三种模式:

  • 计算组间预热:将计算组 A 的缓存数据预热至计算组 BDoris 定期收集各计算组在一段时间内被访问的表或分区的热点信息,并根据这些信息选择性地预热某些表或分区。

  • 表数据预热:指定将表 A 的数据预热至新计算组。

  • 分区数据预热:指定将表 A 的分区 p1 的数据预热至新计算组。

3 Compute Group 扩缩容

Compute Group 扩缩容时,为了避免 Cache 波动, Doris 会首先对受影响的 Tablet 重新映射并预热数据。

4 缓存观测

4.1 热点信息

Doris10 分钟收集各个计算组的缓存热点信息到内部系统表,您可以通过 SHOW CACHE HOTSPOT '/' 命令查看热点信息。

4.2 Cache 空间以及命中率

Doris BE 节点通过 curl {be_ip}:{brpc_port}/vars ( brpc_port 默认为 8060 ) 获取 cache 统计信息,指标项的名称开始为磁盘路径。

上述例子中指标前缀为 File Cache 的路径,例如前缀 "mnt_disk1_gavinchou_debug_doris_cloud_be0_storage_file_cache" 表示 "/mnt/disk1/gavinchou/debug/doris-cloud/be0_storage_file_cache/" 去掉前缀的部分为统计指标,比如 "file_cache_cache_size" 表示当前路径的 File Cache 大小为 26111 字节

下表为全部的指标意义(以下表示 Size 大小单位均为字节)

指标名称 (不包含路径前缀) 语义
file_cache_cache_size 当前 File Cache 的总大小
file_cache_disposable_queue_cache_size 当前 disposable 队列的大小
file_cache_disposable_queue_element_count 当前 disposable 队列里的元素个数
file_cache_disposable_queue_evict_size 从启动到当前 disposable 队列总共淘汰的数据量大小
file_cache_index_queue_cache_size 当前 index 队列的大小
file_cache_index_queue_element_count 当前 index 队列里的元素个数
file_cache_index_queue_evict_size 从启动到当前 index 队列总共淘汰的数据量大小
file_cache_normal_queue_cache_size 当前 normal 队列的大小
file_cache_normal_queue_element_count 当前 normal 队列里的元素个数
file_cache_normal_queue_evict_size 从启动到当前 normal 队列总共淘汰的数据量大小
file_cache_total_evict_size 从启动到当前,整个 File Cache 总共淘汰的数据量大小
file_cache_ttl_cache_evict_size 从启动到当前 TTL 队列总共淘汰的数据量大小
file_cache_ttl_cache_lru_queue_element_count 当前 TTL 队列里的元素个数
file_cache_ttl_cache_size 当前 TTL 队列的大小
file_cache_evict_by_heat_[A]to[B] 为了写入 B 缓存类型的数据而淘汰的 A 缓存类型的数据量(基于过期时间的淘汰方式)
file_cache_evict_by_size_[A]to[B] 为了写入 B 缓存类型的数据而淘汰的 A 缓存类型的数据量(基于空间的淘汰方式)
file_cache_evict_by_self_lru_[A] A 缓存类型的数据为了写入新数据而淘汰自身的数据量(基于 LRU 的淘汰方式)

4.3 SQL profile

SQL profilecache 相关的指标在 SegmentIterator 下,包括

指标名称 语义
BytesScannedFromCache 从 File Cache 读取的数据量
BytesScannedFromRemote 从远程存储读取的数据量
BytesWriteIntoCache 写入 File Cache 的数据量
LocalIOUseTimer 读取 File Cache 的耗时
NumLocalIOTotal 读取 File Cache 的次数
NumRemoteIOTotal 读取远程存储的次数
NumSkipCacheIOTotal 从远程存储读取并没有进入 File Cache 的次数
RemoteIOUseTimer 读取远程存储的耗时
WriteCacheIOUseTimer 写 File Cache 的耗时

您可以通过查询性能分析查看查询性能分析。

5 使用方法

5.1 设置 TTL 策略

在建表时,设置相应的 PROPERTY ,即可将该表的数据使用 TTL 策略进行缓存。

  • file_cache_ttl_seconds :新导入的数据期望在缓存中保留的时间,单位为秒。

    SQL
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    CREATE TABLE IF NOT EXISTS customer (
    C_CUSTKEY     INTEGER NOT NULL,
    C_NAME        VARCHAR(25) NOT NULL,
    C_ADDRESS     VARCHAR(40) NOT NULL,
    C_NATIONKEY   INTEGER NOT NULL,
    C_PHONE       CHAR(15) NOT NULL,
    C_ACCTBAL     DECIMAL(15,2)   NOT NULL,
    C_MKTSEGMENT  CHAR(10) NOT NULL,
    C_COMMENT     VARCHAR(117) NOT NULL
    )
    DUPLICATE KEY(C_CUSTKEY, C_NAME)
    DISTRIBUTED BY HASH(C_CUSTKEY) BUCKETS 32
    PROPERTIES(
        "file_cache_ttl_seconds"="300"
    )
    

    上表中,所有新导入的数据将在缓存中被保留 300 秒。系统当前支持修改表的 TTL 时间,用户可以根据实际需求将 TTL 的时间延长或减短。

    SQL
    1
    ALTER TABLE customer set ("file_cache_ttl_seconds"="3000");
    

    Tip

    修改后的 TTL 值并不会立即生效,而会存在一定的延迟。

    如果在建表时没有设置 TTL ,用户同样可以通过执行 ALTER 语句来修改表的 TTL 属性。

5.2 缓存预热

目前支持三种缓存预热模式:

  • compute_group_name0 的缓存数据预热到 compute_group_name1

    当执行以下 SQL 时, compute_group_name1 计算组会获取 compute_group_name0 计算组的访问信息,来尽可能还原出与 compute_group_name0 计算组一致的缓存。

    SQL
    1
    WARM UP COMPUTE GROUP compute_group_name1 WITH COMPUTE GROUP compute_group_name0
    

    查看当前所有计算组中最频繁访问的表。

    SQL
    1
    SHOW CACHE HOTSPOT '/';
    

    查看 compute_group_name0 下的所有表中最频繁访问的 Partition

    SQL
    1
    SHOW CACHE HOTSPOT '/compute_group_name0';
    

    查看 compute_group_name0 下,表 regression_test_cloud_load_copy_into_tpch_sf1_p1.customer 的访问信息。

    SQL
    1
    SHOW CACHE HOTSPOT '/compute_group_name0/regression_test_cloud_load_copy_into_tpch_sf1_p1.customer';
    
  • 将表 customer 的数据预热到 compute_group_name1 。执行以下 SQL ,可以将该表在远端存储上的数据全部拉取到本地。

    SQL
    1
    WARM UP COMPUTE GROUP compute_group_name1 WITH TABLE customer
    
  • 将表 customer 的分区 p1 的数据预热到 compute_group_name1 。执行以下 SQL ,可以将该分区在远端存储上的数据全部拉取到本地。

    SQL
    1
    WARM UP COMPUTE GROUP compute_group_name1 with TABLE customer PARTITION p1
    

上述三条缓存预热 SQL 均会返回一个 JobID 结果。例如:

SQL
1
WARM UP COMPUTE GROUP cloud_warm_up WITH TABLE test_warm_up;

然后可以通过以下 SQL 查看缓存预热进度。

SQL
1
SHOW WARM UP JOB WHERE ID = 13418;

可根据 FinishBatchAllBatch 判断当前任务进度,每个 Batch 的数据大小约为 10GB 。目前,一个计算组中,同一时间内只支持执行一个预热 Job 。用户可以停止正在进行的预热 Job

SQL
1
CANCEL WARM UP JOB WHERE id = 13418;

6 实践案例

某用户拥有一系列数据表,总数据量超过 3TB ,而可用缓存容量仅为 1.2TB 。其中,访问频率较高的表有两张:一张是大小为 200MB 的维度表( dimension_table ),另一张是大小为 100GB 的事实表( fact_table ),后者每日都有新数据导入,并需要执行 T+1 查询操作。此外,其他大表访问频率不高。

LRU 缓存策略下,大表数据如果被查询访问,可能会替换掉需要常驻缓存的小表数据,造成性能波动。为了解决这个问题,用户采取 TTL 缓存策略,将两张表的 TTL 时间分别设置为 1 年和 1 天。

SQL
1
2
3
ALTER TABLE dimension_table set ("file_cache_ttl_seconds"="31536000");

ALTER TABLE fact_table set ("file_cache_ttl_seconds"="86400");

对于维度表,由于其数据量较小且变动不大,用户设置 1 年的 TTL 时间,以确保其数据在一年内都能被快速访问;对于事实表,用户每天需要进行一次表备份,然后进行全量导入,因此将其 TTL 时间设置为 1 天。