You need to enable JavaScript to run this app.
导航
预加载 Preload
最近更新时间:2024.09.23 17:50:22首次发布时间:2024.09.23 17:50:22

预加载 Preload 主要用于加速实时表写入。在数据通过 INSERT 写入和 MERGE 写入时,新写入的远程 PART 会被立即加载到表绑定的读 VW 中,并以本地缓存的形式存在。这样,当用户访问相关数据时,能够直接读取热数据,从而避免冷读导致的性能抖动。

功能参数

若要开启某个表的 Preload,需要进行以下参数设置:

  • 全局参数:parts_preload_level = 1
  • 表参数:enable_local_disk_cache = 1
  • 表参数:parts_preload_level > 0 或者 enable_preload_parts = 1

更多参数含义如下:

类别

名称

默认值

说明

system

parts_preload_level

1

  1. 用于控制是否开启auto-preload:

0:关闭,所有表生效,即使表级别配置了开启参数

0:开启,所有表生效,具体PreloadLeve由各个表控制

  1. 用于控制manual-preload(使用)的级别

0:关闭,原则上manual-prelaod时不应指定为0
1:开启且仅preload meta信息,具体包含:checksum、primary_index、mark
2:开启且仅preload data信息
3:开启且Preload meta&data的信息

disk_cache_mode

auto

控制查询时候的DiskCache模式:
auto:自动模式,以enable_local_disk_cache值为准
use_disk_cache:使用DiskCache,即使enable_local_disk_cache = 0
force_disk_cache:强制使用DiskCache,如果不存在则抛出异常,往往用于DEBUG测试DiskCache生效,实际生产环境忽略即可
skip_disk_cache:跳过DiskCache,即使enable_local_disk_cache = 1。当配置全局配置时(user.xml)则所有查询会自动跳过缓存

drop_vw_disk_cache

0

用于drop disk cache时控制是否清空表所在VW上的所有表cache:
0:表示仅清空选定表的Cache
1:清空对应VW节点上所有Cache

cnch_parallel_preloading

0

用于控制reader节点缓存Part的并发:
0:等于节点的CPU核数

0: 指定具体的并发数

preload_send_rpc_max_ms

3000

用于控制writer节点和server节点发送rpc的超时时间

part_allocation_algorithm

2

PART分发的一致性HASH规则,在不设置表级别的该参数时,以此参数为准。

table

parts_preload_level

0

用于控制当前表开启Preload的级别:
0:关闭Preload,该状态将会使得手动Preload失效
1:开启且仅preload meta信息,具体包含:checksum、primary_index、mark
2:开启且仅preload data信息
3:开启且Preload meta&data的信息

enable_parts_sync_preload

0

用于控制auto-preload任务执行模式:
0:使用异步模式,和GlobalDiskCache共用一套异步线程,有概率失败
1:使用同步模式,独立线程,确保Preload成功

enable_preload_parts

0

旧配置,用于兼容表旧有的设置。如果之前没有使用过该配置,则忽略即可。
否则:
0: 以parts_preload_level为准
1:忽略parts_preload_level配置,开启Preload并缓存Meta&Data数据

enable_gc_evict_disk_cache

0

开启后,Part被清理后对应缓存会被一并清理

cnch_part_allocation_algorithm

-1

开启后,查询将优先按照表级别分配算法分配part

使用说明

Preload 分为两种方式并包含清空操作:

  • 手动模式:需要手动执行SQL以触发,主要用于加载存量数据。
# 手动Preload Cache:当表关闭Preload设置后,该操作将无效
## -[partition p]:要手动加载的分区名字,如果不填写则默认加载全表
## -[SYNC|ASYNC]:手动加载任务的执行方式,同步使用独立线程;异步则与全局Cache共用一个线程,有概率部分任务失败;不指定的话默认为ASYNC
## -[SETTINGS parts_preload_level = 1]:指定preload的粒度,参见上面表格叙述
## -[SETTINGS virtual_warehouse = '']:指定preload data加载到的目标VW,若不指定则使用表绑定的VW
alter disk cache preload table [db.]table_name [partition p] [SYNC|ASYNC] [SETTINGS parts_preload_level = 1, virtual_warehouse = 'vw_name']
  • 自动模式:开启表级别参数后,随后新增数据(包括INSERT和MERGE)都会自动加载,但存量数据无法加载。
# 根据加载级别设置parts_preload_level值 
alter table [db.]table_name modify setting parts_preload_level = 1;
  • 清空缓存:清空某个表的所有缓存。
# 手动 Drop Cache 
## -[table [db.]table_name] [partition p] [SYNC|ASYNC]:与Preload定义相同
## -[SETTINGS drop_vw_disk_cache=1]:当指定1时,将清理该表所在VW上的所有表的缓存
alter disk cache drop [table [db.]table_name] [partition p] [SYNC|ASYNC] [SETTINGS drop_vw_disk_cache = 1]

常见问题
  1. 什么时候开启 Preload ?

当发现某个表的缓存命中率比较低时:

  • 对于实时表(即导入数据没多久就会查询),此时需要开启。
  • 对于非实时表:
    • 磁盘容量基本可以缓存整表目标天数数据,建议开启,此时相当于该部分数据预加载到磁盘。
    • 磁盘容量远小于目标天数数据,不建议开启,因为缓存命中率严重受限于磁盘容量。
  1. 如何调优取得最佳效果?
  • 如果磁盘空间较小,建议增大磁盘空间,以预加载足够的目标查询数据。
  • 如果磁盘空间合适:Preload的效果和PART分发策略cnch_part_allocation_algorithmcnch_hybrid_part_allocation_algorithm会存在相关性。
  • 当cnch_part_allocation_algorithm > 0时,SQL对应的PARTS会可能会通过rebalance策略重新调整一致性HASH结果,以保证PART在节点的分布尽量均衡。如果Preload的PARTS在分发时不需要rebalance,则将导致同一个PART对于Preload和Query来说会落到不同的节点上,最终的结果即Preload的PART不能被后续的Query查询命中。
  • cnch_hybrid_part_allocation_algorithm > 0时,与part_allocation_algorithm类似,会重新调整PART分发的策略,导致与Preload不一致。

产生上述不一致的根本原因在于,当一个PART在不同的分发组时,不论是rebalance策略还是hybrid策略都会重新进行节点分配。而Preload的PARTS组与Query的PARTS组往往都不一致,因此在做节点调整时很容易引发不一致。

  • 默认情况一下,cnch_part_allocation_algorithm > 0,你仅需要设置表级别参数parts_preload_level > 0即可。
  • 当发现缓存效果不理想时,可以关闭cnch_hybrid_part_allocation_algorithm和设置表级别参数cnch_part_allocation_algorithm = 0以规避PART重分布带来的PreloadMiss。
  • 如果对cnch_hybrid_part_allocation_algorithm和cnch_part_allocation_algorithm > 0 有强需求,请开启DiskCacheStealing保证Preload的缓存被查询一定命中。然而,DiskCacheStealing会带来额外的RPC等开销,开启后进谨慎观察是否带来负优化。
  1. 如何查看Preload日志记录?

每次的Preload任务都会记录在READER节点上的system.part_log系统表中,注意:必须写table like {table%}

SELECT * FROM cnch('{vw_name}', system.part_log) where database = xxx and table like xxx ORDER BY event_time DESC LIMIT 5

一个典型的日志记录如下:

  1. 如何查看开启前后的缓存命中率?

以2.0为例,查看每个Query的缓存命中率使用如下SQL:

SELECT
    any(event_time) AS time,
    formatReadableSize(sum(ProfileEvents['ReadBufferFromS3ReadBytes'] + ProfileEvents['ReadBufferFromHdfsReadBytes'])) AS remote,
    formatReadableSize(sum(ProfileEvents['ReadBufferFromFileDescriptorReadBytes'])) AS disk,
    max(query_duration_ms) AS ms,
    max(round((MaxIOThreadProfileEvents['RemoteFSAsynchronousReadWaitMicroseconds'] + MaxIOThreadProfileEvents['RemoteFSSynchronousReadWaitMicroseconds']) / 1000)) AS remote_ms,
    max(round((MaxIOThreadProfileEvents['DiskReadElapsedMicroseconds']) / 1000)) AS disk_read_ms,
    round(sum(ProfileEvents['ReadBufferFromFileDescriptorReadBytes']) / (sum(ProfileEvents['ReadBufferFromS3ReadBytes'] + ProfileEvents['ReadBufferFromHdfsReadBytes'] + ProfileEvents['ReadBufferFromFileDescriptorReadBytes'])), 2) AS hit_rate
FROM cnch('vw-851725483243-xxl-16', system.query_log) # 换成对应的VW名称
WHERE (event_date = today()) and 
GROUP BY initial_query_id
ORDER BY time DESC
LIMIT 100

如果想查看整体天级别的缓存命中率变化,请使用如下SQL:

select
event_date,
round(quantile(0.90)(query_duration_ms)) as p90,
round(quantile(0.95)(query_duration_ms)) as p95,
count(*) as total, 
round(sum(hit_rate) / total, 2) as average_hit_rate,
round(sum(if (hit_rate >= 0.99, 1, 0)) / total, 2) as _99_total,
round(sum(if (0.99 > hit_rate and hit_rate >= 0.95, 1, 0)) / total, 2) as _95_total,
round(sum(if (0.95 >=  hit_rate and hit_rate > 0.90, 1, 0)) / total, 2) as _90_total,
round(sum(if (0.90 >= hit_rate and hit_rate > 0.50, 1, 0)) / total, 2) as _50_total,
round(sum(if (hit_rate <= 0.50, 1, 0)) / total, 2) as _00_total
from
(
select event_date, query_duration_ms, initial_query_id from cnch(server, system.query_log)
WHERE tables[1] like '%seamless_app%' AND (event_date >= today() - 7) AND (query_duration_ms > 0) # 换成对应的表名
) as server
INNER JOIN 
(
select 
initial_query_id,
query_id, 
ProfileEvents['ReadBufferFromS3ReadBytes'] + ProfileEvents['ReadBufferFromHdfsReadBytes'] AS remote_bytes,
ProfileEvents['ReadBufferFromFileDescriptorReadBytes'] AS disk_bytes, 
query_duration_ms,
(MaxIOThreadProfileEvents['RemoteFSAsynchronousReadWaitMicroseconds'] + MaxIOThreadProfileEvents['RemoteFSSynchronousReadWaitMicroseconds']) / 1000 AS s3_read_ms,
disk_bytes / (remote_bytes + disk_bytes) as hit_rate
from cnch('vw-851725483243-xxl-16', system.query_log) # 换成对应的VW名称
where query_duration_ms > 0 and remote_bytes + disk_bytes > 0 and  (event_date >= today() - 7)
) 
as worker on server.initial_query_id = worker.initial_query_id 
group by event_date 
  1. 如何确定Preload的缓存没被命中的原因?

一般情况下,开启Preload后如果缓存命中率有明显提升,则说明Preload的缓存发挥了作用,但是如果效果不明显,请按照调优步骤进行操作。详细的排查手段如下:

  • 开启READER任意一个节点的日志级别为trace。
  • 在随后的时间内过滤如下日志(关键词cached, 2.0中为preload_level: 0, 1.4 中为is_preload = 0):该日志表明:对应PART没有被Preload,而是再查询的时候才触发的缓存。
  • 在Server节点上通过查询所有VW上system.part_log(参考常见问题3的操作)对应MISS的PART是否触发过预加载:
    • 如果没有触发:
      • 请检查对应配置是否正确,以确保的确开启了 Preload(一般在 writer,server, reader 节点过了关键词 preload 也可以看是否有对应的任务)。
      • 对应的 PART 还没开始加载,可能机器负载较高或者 cnch_parallel_preloading 值较小导致 Preload 任务调度过慢,解决办法就是:加机器和增大 cnch_parallel_preloading。
    • 如果有触发:
      • 加载完成的时间在查询之后,说明查询的时候该 Part 还没预加载,可能机器负载较高或者 cnch_parallel_preloading 值较小导致 Preload 任务调度过慢,解决办法就是:加机器和增大 cnch_parallel_preloading。
        • 加载完成的时间在查询之前,看看 reader 节点的名称和 system.part_log 显示的节点名称是否一致。
          • 如果一致:说明磁盘空间较小,虽然预加载了但是被后续的缓存进来后淘汰了,此时增加磁盘空间。
          • 如果不一致:说明设置了 part_allocation_algorithm > 0 和 cnch_hybrid_part_allocation_algorithm,导致 PART 分发不一致,请关闭后重新观察。