GaussDB(DWS)磁盘缓存最佳实践
GaussDB(DWS) 磁盘缓存最佳实践
1 前言
- 适用版本:【9.1.0及以上集群版本,TTL队列仅9.1.1及以上集群版本支持】
云计算时代下的存算分离架构将计算和存储解耦,存、算、元数据分离成为趋势,将大量用户数据存放在云端存储。在此背景下,需要频繁地同OBS云端存储进行读写交互,为了尽可能的提升云上数据的处理速度,GaussDB(DWS)通过利用本地硬盘上的缓存来加速数据访问,并采用了一种先进的多队列 LRU(Least Recently Used)策略来高效管理缓存空间。针对跨VW的应用场景,DWS 还提供了缓存预热功能,以便在弹性VW建立时,能够迅速加载特定数据(如表或分区)到缓存中,从而提升查询性能。
2 多队列LRU
2.1 LRU
- LRU 通过维护一个数据访问队列来管理缓存。当数据被访问时,该数据会被移动到队列的前端。新加入缓存的数据同样会被置于队列前端,以防止其过早被淘汰。当缓存空间达到上限时,队列尾部的数据将优先被移除。
- GaussDB(DWS) 通过引入LRU-2Q算法,优化了LRU的缓存污染问题,确保了核心业务的热数据不会因为访问大量历史数据而被冲刷出缓存。
2.2 TTL (Time-To-Live)
- TTL 策略确保新导入的数据在缓存中保留一段时间不被淘汰。在这段时间内,数据具有最高优先级,且所有 TTL 数据之间地位平等,不会因为某些数据即将过期而被提前淘汰。当TTL缓存空间不足时,会尝试抢占LRU队列的空间,以确保 TTL 数据能够被写入,TTL队列最大可使用的空间由guc参数disk_cache_ttl_max_ratio 控制,默认值是0.5,即整个缓存空间的一半。当TTL队列的缓存空间到达上限后,如果仍然有新的数据要进入TTL队列,此时会触发TTL队列自身的LRU淘汰,被淘汰的数据将进入到LRU空间中。
- 应用场景:TTL 策略特别适用于希望在本地持久化的小规模数据表。对于常驻表,可以设置较长的 TTL 值来保护其数据;对于实时表,可以根据热数据的活跃时间设定相应的 TTL 值。
2.3 多队列
-
GaussDB(DWS) 采用基于 LRU 的多队列策略,根据 TTL 属性及冷热属性将数据分为三类,分别置于 TTL 队列、LRU - 2Q队列、A0 队列中。设置了 TTL 属性的热数据被放置到 TTL 队列,没有设置 TTL 属性的热数据被放置到 LRU - 2Q队列,冷数据都放置到A0队列中。
-
在数据读取和写入过程中,GaussDB(DWS) 会地选择填充和读取的队列,以最大化缓存利用率。具体机制如下:
操作 未命中时填充的队列 写入数据时填充的队列 导入(开启缓存双写) TTL / LRU - 2Q / A0 TTL / LRU - 2Q / A0 查询 TTL / LRU - 2Q / A0 N/A 预热 N/A TTL / LRU - 2Q / A0
2.4 淘汰
A0队列可使用的缓存空间固定为1GB, TTL 队列和LRU - 2Q队列共同使用由GUC参数 disk_cache_max_size 设置的缓存空间,初始状态时缓存空间都给LRU - 2Q使用,在后续访问到设置TTL属性的数据或者TTL中有大量数据过期时,会动态调整TTL队列与LRU - 2Q队列的比例关系,以充分利用缓存空间,TTL所使用缓存空间的最大比例不超过 disk_cache_ttl_max_ratio。
2.4.1 TTL队列的淘汰
- TTL过期淘汰
- 当有新的数据进入TTL队列时,会将部分已经过期的数据淘汰至LRU - 2Q队列。
- 后台线程会定期清理TTL中已过期的数据,并淘汰至LRU - 2Q队列。
- TTL自身LRU淘汰
- 当TTL空间使用达到上限,且没有过期的数据时,会触发自身的LRU淘汰策略,被淘汰出去的数据会进入LRU - 2Q队列。
- TTL主动淘汰
- 当用户删除设置了过期时间的表数据时,后台线程会定期清理TTL中无效的数据。
2.4.2 LRU - 2Q队列的淘汰
- LRU - 2Q自身LRU淘汰
- 当有新的数据插入且当前LRU空间使用达到上限,会触发自身的LRU淘汰策略,被淘汰出去的数据会被清除缓存。
- 当TTL队列空间不足,但未到达上限,会抢占LRU - 2Q队列的空间。如果LRU - 2Q的空间已满,也会触发自身的LRU淘汰。
- LRU - 2Q主动淘汰
- 当用户删除设置了过期时间的表数据时,后台线程会定期清理LRU - 2Q中无效的数据。
3 多盘缓存
对于云上环境,对不同云盘的访问会走不同的网卡,因此不同云盘的带宽是可以叠加的。在有多块云盘(EVS)的情况下,GaussDB(DWS)支持设置多块盘作为缓存的路径,尽可能将 cache 文件分散在不同云盘上以提升缓存访问的性能。为了提升集群正常时主DN的性能并充分利用节点上的磁盘空间,目前会默认使用主备两块硬盘作为当前节点上主DN缓存介质,通过查询以下参数查看相关信息:
-
通过 disk_cache_base_paths 参数查看和增减缓存硬盘路径,举例某个节点上的目录配置,实例名为h10dn1:
主DN多盘缓存路径默认如下:
- 路径1:/DWS/data1/h10dn1/primary0/disk_cache
- 路径2:/DWS/data2/h9dn1/primary0_disk_cache
备DN多盘缓存路径默认如下:
- 路径:/DWS/data2/h9dn1/secondry/disk_cache
备机的多盘缓存路径与当前节点上主DN的缓存路径2在同一块盘上,因此限制备机升主后的磁盘缓存空间最大为1GB,性能对比切换前会有较大劣化,需要尽快修复集群至均衡状态。
4 缓存预热
在存算分离模式下,GaussDB(DWS)支持搭建跨VW集群,各VW间共享数据但不共享缓存。新的VW创建时,其缓存为空,可能影响查询性能。为此,GaussDB(DWS) 提供缓存预热功能,允许用户从远端存储主动拉取数据至本地缓存。该功能支持以下两种模式:
-
表数据预热:预热指定表 A 的数据。
/* 将表A的数据预热至A1in队列中 */ explain warmup select * from A; /* 将表A的数据预热至Am队列中 */ explain warmup hot select * from A;
-
分区数据预热:预热指定表 A 的分区
p1
的数据。/* 将表A p1分区的数据预热至A1in队列中 */ explain warmup select * from A partition(p1); /* 将表A p1分区的数据预热至Am队列中 */ explain warmup hot select * from A partition(p1);
使用示例:
warmup信息解读
-
Read Cache Size : 从磁盘缓存中读取的数据size
-
Write Cache Size : 从OBS读到本地缓存的数据size
-
Avg Write Cache time : OBS请求的平均执行时间
5 缓存观测
5.1Cache 空间以及命中率
用户可通过查看disk cache视图pgxc_disk_cache_all_stats查看disk cache当前缓存状态,使用方式为:
select * from pgxc_disk_cache_all_stats;
其中各个字段含义如下:
名称 | 类型 | 描述 |
---|---|---|
node_name | text | 节点名称。 |
total_read | bigint | 访问disk cache的总次数。 |
local_read | bigint | disk cache访问本地磁盘的总次数。 |
remote_read | bigint | disk cache访问远端存储的总次数。 |
hit_rate | numeric(5,2) | disk cache的命中率。 |
cache_size | bigint | disk cache保存的数据总大小,单位kbytes。 |
fill_rate | numeric(5,2) | disk cache的填充率。 |
ttl_rate | numeric(5,2) | ttl使用空间占整个diskcache空间的比率。该字段仅9.1.1及以上集群版本支持。 |
temp_file_size | bigint | 临时/冷缓存文件的总大小(kbytes)。 |
a1in_size | bigint | disk cache中a1in队列保存的数据的总大小,单位kbytes。 |
a1out_size | bigint | disk cache中a1out队列保存的数据的总大小,单位kbytes。 |
am_size | bigint | disk cache中am队列保存的数据的总大小,单位kbytes。 |
ttl_size | bigint | disk cache中ttl队列保存的数据的总大小,单位kbytes。该字段仅9.1.1及以上集群版本支持。 |
a1in_fill_rate | numeric(5,2) | disk cache中a1in队列的填充率。 |
a1out_fill_rate | numeric(5,2) | disk cache中a1out队列的填充率。 |
am_fill_rate | numeric(5,2) | disk cache中am队列的填充率。 |
ttl_fill_rate | numeric(5,2) | disk cache中ttl队列的填充率。该字段仅9.1.1及以上集群版本支持。 |
fd | integer | disk cache正在使用的文件描述符数量。 |
pin_block_count | bigint | disk cache中被pin住block的数量。该字段仅9.1.0.100及以上集群版本支持。 |
5.2 SQLPerformance
对于3.0表,由于数据的实际存储位置位于OBS,增强了sql语句级别的对OBS读写请求统计信息的监控,辅助定位特定sql慢。可以通过topsql以及explain performance 查看特定语句的obs读写统计信息。
/* topsql */
select * from pg_catalog.pgxc_wlm_session_info;
/* explain performance */
explain performance + sql语句
其中新增字段含义如下:
新增字段名称 | 字段含义 |
---|---|
vfs_scan_bytes | OBS虚拟文件系统接收到上层请求的扫描的字节数(bytes) |
vfs_remote_read_bytes | OBS虚拟文件系统实际从OBS读取的字节数(bytes) |
preload_submit_time | 预读流程提交IO请求的总时间(microseconds) |
preload_wait_time | 预读流程等待IO请求的总时间(microseconds) |
preload_wait_count | 预读流程等待IO请求的总次数(count) |
disk_cache_load_time | 读取diskcache的总时间(microseconds) |
disk_cache_conflict_count | 读取diskcache中block产生哈希冲突的次数(count) |
disk_cache_error_count | 读取diskcache失败的次数(count) |
disk_cache_error_code | 读取diskcache失败的错误码(count) |
obs_io_req_avg_rtt | OBS IO请求的平均RRT(microseconds) |
obs_io_req_avg_latency | OBS IO请求的平均延迟(microseconds) |
obs_io_req_latency_gt_1s | OBS IO请求延迟超过1s的数量(count) |
obs_io_req_latency_gt_10s | OBS IO请求延迟超过10s的数量(count) |
obs_io_req_count | OBS IO请求的总数量(count) |
obs_io_req_retry_count | OBS IO请求重试的总次数(count) |
obs_io_req_rate_limit_count | OBS IO请求被流控的总次数(count) |
6. 使用方法
当前云上的集群的磁盘缓存默认是打开的,即对所有v3表的访问都会使用到磁盘缓存。可以通过以下方式检查当前集群的磁盘缓存是否打开?
- DN上执行"show enable_aio_scheduler; show obs_worker_pool_size";确保enable_aio_scheduler=on, obs_worker_pool_size >=4;
- CN上执行"show enable_disk_cache",确保enable_disk_cache=on
上述两点必须同时满足。
6.1 表级缓存策略指定
当前disk cache支持的缓存策略有以下四种:
-
HPN(HOT PARTITION NUMBER)
cache policy选项可以在执⾏create table或者alter table语句时设置成HPN : N,⽤户需要提供⼀个数字N(N >= -1600 && N <= 1600),之后获取表中的数据,更晚创建的N个分区中的数据会被热缓存,其余分区会被冷缓存。当N等于0时,所有分区中的数据都会被冷缓存。HPN只对V3表中的range分区表和list分区表⽣效。
CREATE TABLE IF NOT EXISTS lineitem ( L_ORDERKEY BIGINT NOT NULL , L_PARTKEY BIGINT NOT NULL , L_SUPPKEY BIGINT NOT NULL , L_LINENUMBER BIGINT NOT NULL , L_QUANTITY DECIMAL(15,2) NOT NULL , L_EXTENDEDPRICE DECIMAL(15,2) NOT NULL , L_DISCOUNT DECIMAL(15,2) NOT NULL , L_TAX DECIMAL(15,2) NOT NULL , L_RETURNFLAG CHAR(1) NOT NULL , L_LINESTATUS CHAR(1) NOT NULL , L_SHIPDATE DATE NOT NULL , L_COMMITDATE DATE NOT NULL , L_RECEIPTDATE DATE NOT NULL , L_SHIPINSTRUCT CHAR(25) NOT NULL , L_SHIPMODE CHAR(10) NOT NULL , L_COMMENT VARCHAR(44) NOT NULL ) with ( orientation = column, colversion = 3.0, cache_policy = 'HPN : 3' ) TABLESPACE cu_obs_tbs DISTRIBUTE BY HASH(L_ORDERKEY) PARTITION BY RANGE(L_SHIPDATE) ( PARTITION L_SHIPDATE_4 VALUES LESS THAN('1993-01-01 00:00:00'), -- 冷缓存 PARTITION L_SHIPDATE_5 VALUES LESS THAN('1994-01-01 00:00:00'), -- 冷缓存 PARTITION L_SHIPDATE_3 VALUES LESS THAN('1995-01-01 00:00:00'), -- 冷缓存 PARTITION L_SHIPDATE_1 VALUES LESS THAN('1996-01-01 00:00:00'), -- 冷缓存 PARTITION L_SHIPDATE_2 VALUES LESS THAN('1997-01-01 00:00:00'), -- 热缓存 PARTITION L_SHIPDATE_7 VALUES LESS THAN('1998-01-01 00:00:00'), -- 热缓存 PARTITION L_SHIPDATE_6 VALUES LESS THAN('1999-01-01 00:00:00') -- 热缓存 ); alter TABLE lineitem set(cache_policy = 'HPN : 5');
-
HPL(HOT PARTITION LIST)
cache policy选项可以在执⾏create table或者alter table语句时设置成HPL : , , …,⽤户需要提供意图指定成热分区的分区名,之后获取表中的数据,热分区中的 数据会被热缓存,⽽其余分区中的数据则会被冷缓存。HPL只对V3表中的range分区表和list分区表⽣效。HPL只对V3表中的range分区表和list分区表⽣效。
CREATE TABLE IF NOT EXISTS nation ( N_NATIONKEY INT NOT NULL , N_NAME CHAR(25) NOT NULL , N_REGIONKEY INT NOT NULL , N_COMMENT VARCHAR(152) ) WITH ( orientation = column, colversion = 3.0, cache_policy = 'HPL : ASIA, AMERICA, REST' ) TABLESPACE cu_obs_tbs DISTRIBUTE BY ROUNDROBIN PARTITION BY LIST(N_NAME) ( PARTITION ASIA VALUES ('INDIA', 'INDONESIA', 'JAPAN'), -- 热缓存 PARTITION EUROPE VALUES ('FRANCE', 'GERMANY', 'UNITED KINGDOM'), -- 冷缓存 PARTITION AFRICA VALUES ('ALGERIA', 'ETHIOPIA', 'KENYA'), -- 冷缓存 PARTITION AMERICA VALUES ('ARGENTINA', 'BRAZIL', 'CANADA'), -- 热缓存 PARTITION REST VALUES (DEFAULT) -- 热缓存 );
-
NONE
cache policy选项可以在执⾏create table或者alter table语句时设置成NONE,之后获取表中的数据,所有数据都会被冷缓存。
CREATE TABLE IF NOT EXISTS partsupp ( PS_PARTKEY BIGINT NOT NULL , PS_SUPPKEY BIGINT NOT NULL , PS_AVAILQTY BIGINT NOT NULL , PS_SUPPLYCOST DECIMAL(15,2) NOT NULL , PS_COMMENT VARCHAR(199) NOT NULL ) WITH ( orientation = column, colversion = 3.0, cache_policy = 'NONE' ) TABLESPACE cu_obs_tbs DISTRIBUTE BY HASH(PS_PARTKEY); -- 冷缓存
-
ALL
cache policy选项可以在执⾏create table或者alter table语句时设置成ALL,之后获取表中的数据,所有数据都会被热缓存。
CREATE TABLE IF NOT EXISTS orders ( O_ORDERKEY BIGINT NOT NULL , O_CUSTKEY BIGINT NOT NULL , O_ORDERSTATUS CHAR(1) NOT NULL , O_TOTALPRICE DECIMAL(15,2) NOT NULL , O_ORDERDATE DATE NOT NULL , O_ORDERPRIORITY CHAR(15) NOT NULL , O_CLERK CHAR(15) NOT NULL , O_SHIPPRIORITY BIGINT NOT NULL , O_COMMENT VARCHAR(79) NOT NULL ) WITH ( orientation = column, colversion = 3.0, cache_policy = 'ALL' ) DISTRIBUTE BY ROUNDROBIN; -- 热缓存
6.2 TTL策略设置
在建表时,设置相应的表级参数,即可将该表的数据使用 TTL 策略进行缓存。
- disk_cache_ttl:新导入的数据期望在缓存中保留的时间,参数的类型为interval,最小值为"1 second"、最大值为 “100 years”。
CREATE TABLE IF NOT EXISTS ProductDimension
(
ProductID INT PRIMARY KEY,
ProductName VARCHAR(100),
Category VARCHAR(50),
Price DECIMAL(10, 2),
Description TEXT
)
WITH (
orientation = column,
colversion = 3.0,
disk_cache_ttl = '1 day'
)
DISTRIBUTE BY ROUNDROBIN; -- 热缓存
上表中,所有新导入的数据将在缓存中被保留 1天。系统当前支持修改表的 TTL 时间,用户可以根据实际需求将 TTL 的时间延长或减短。
ALTER TABLE customer set (disk_cache_ttl = '1000 seconds');
备注:
- 修改后的 TTL 值不会对已经在缓存中的数据生效,仅对后续进入缓存的数据有效。
- 如果在建表时没有设置 TTL,用户同样可以通过执行 ALTER 语句来修改表的 TTL 属性。
6.2.1 适用场景
-
维度表,数据量较小、变动不大,但是访问频繁,可以设置较长的TTL时间,例如1年,避免查询大表数据时被替换出缓存,以确保其数据在一年内都能被快速访问。
-
【场景1】客户新建维度表设置disk_cache_ttl为1年
CREATE TABLE dimension_table(a int, b text) with (orientation = column, COLVERSION = 3.0, disk_cache_ttl = '1 year');
-
【场景2】已有维度表通过alter设置disk_cache_ttl
如果不确定表数据导入的时间,可以把disk_cache_ttl设置为10年
ALTER TABLE dimension_table set (disk_cache_ttl = '10 years');
也可以设置为1年后,对维度表做vacuum full,更新数据的导入时间。
ALTER TABLE dimension_table set (disk_cache_ttl = '1 years'); vacuum full dimension_table;
-
-
非频繁更新实时表,主要做数据导入与查询业务,可以根据客户业务设置合理的TTL时间。比如客户业务会频繁访问最近半天的数据,则可以设置TTL时间为0.5天。
ALTER TABLE dimension_table set (disk_cache_ttl = '0.5 day');
-
频繁更新实时表,upsert业务比较频繁,可以根据需要做upsert的时间范围给表增加TTL时间,提升upsert操作的性能。设置过期时间时也根据客户具体的业务确定,如果主要对近两天导入的数据进行upsert,可将过期时间设置为2天。
ALTER TABLE dimension_table set (disk_cache_ttl = '2 days');
7. 常见问题
-
为什么我通过
du
、ls
命令看到的 disk cache 目录占用的空间远大于我实际的数据量?disk cache 的磁盘占用空间代表的是历史上的最高水位,和当前实际缓存的数据量并没有关系,假设导入了100G的数据,之后进行了upsert等业务,数据量变成了200G,然后之后进行了vacuum,数据量变成了100G,那么 disk cache 的磁盘占用空间仍然会保持在最高水位200G,但是 disk cache 内部的实际缓存数据量会是100G。失效数据的去清理需要将整个disk cache用满后触发LRU淘汰策略。
-
disk cache 是否会自动淘汰?为什么我的磁盘水位一直维持在配置的最高点,不下降?
会。目前策略是达到磁盘使用上限(参数可控制,默认主备盘总空间的50%)、或当前磁盘使用率达到80%才会开始淘汰。但淘汰不会实际删除数据,只是将缓存的磁盘位置标记为空,后面的新缓存写入时会覆盖老缓存。所以即使发生淘汰,磁盘占用空间也不会下降,不会影响实际使用。
-
为什么我 drop 了表,obs也显示表文件已删除,但是我的 disk cache 实际存储的数据量没有下降?
drop 表数据不会进行 disk cache 缓存的删除,已经不存在的表的缓存会随着时间的推移,通过 disk cache 的内部 LRU 逻辑淘汰(删除),不影响实际使用。
-
为什么我各个节点之间的缓存占用差异很大?
各个节点之间的节点缓存占用是无法保证在相邻水位的,这是单机缓存天然的限制,只要对查询延迟没有影响就行。占用差异受很多因素影响:
- 不同实例加入集群的时间先后;
- 不同vw的表数量差异;
- 表数据的分布方式,以及业务实际访问的数据,可能某个业务访问的数据都在一个节点上;
- 不同节点vacuum full的时间差异,vacuum full会导致该表在缓存中占用的空间double,需要随着时间的推移,通过 disk cache 的内部 LRU 逻辑淘汰无需缓存。
- 点赞
- 收藏
- 关注作者
评论(0)