# DWS 3.0 存算分离使用建议及性能优化
#### 场景介绍
DWS全新推出云原生数仓DWS 3.0版本，利用云基础设施提供的资源池化和海量存储能力，结合MPP数据库技术，采用计算存储分离架构，实现了极致弹性、实时入库、数据实时共享和湖仓一体等特性。
了解更多存算分离知识，请参见[什么是数据仓库服务](https://support.huaweicloud.com/productdesc-dws/dws_01_0002.html)。
本文档主要描述存算分离版本特有的性能优化和注意事项。
#### 集群购买
- **EVS磁盘空间**
  9.1.0.x版本只是实现了将列存用户数据存储到OBS上，其它数据仍然保存在本地盘。因此，即使在存算分离架构下，也并不意味着不需要额外配置EVS磁盘或者只需要配置一个很小的磁盘，后续版本存算分离能力将会持续演进。
  表1表类型的存储说明 
  | 表类型          | 存储位置      | 适用场景                   |
  |:---|:---|:---|
  | 行存表/临时表/列存索引 | 本地，无压缩 | 点查、实时小批量入库、频繁更新。        |
  | 列存表2.0          | 本地，有压缩   | 批量入库、查询、实时小批量入库、点查、更新。 |
  | 列存表3.0       | OBS，有压缩 | 批量入库、查询、低频批量更新。      |
     
  **EVS存储内容**：行存、列存元数据(min/max)、 索引、Delta、WAL、OBS数据缓存、计算中的临时下盘文件(sort/hash)，创建DWS集群时可指定大小。
  **EVS存储总大小计算公式**：
  ( 2副本 \*（行存表大小 + 索引大小 + Delta表大小） + OBS热数据缓存大小) / 0.8(预留)
  ![](https://support.huaweicloud.com/bestpractice-dws/public_sys-resources/note_3.0-zh-cn.png)
  当EVS存储总大小超过90%时会触发集群只读，需预留的10%空间保存WAL和临时文件下盘。
  - OBS热数据：1. 明确热数据的大小时，可以直接使用该数值进行配置；2. 无法确定热数据的大小时，可以按照OBS总数据量 \*30%进行配置。
  
  - 列存索引大小 = 原始未压缩数据size \* 索引列宽 \* 3(膨胀) / 总列宽
  
  - 列存数据按照3倍压缩比评估。假设一个20列的表，其中2列作为主键，索引大小约为压缩前数据的30%，与压缩后数据量相当。
  
  - Delta大小：一个表（或者一个分区） Max（10GB， 压缩后表大小 / 10）
  
  - 行存索引的大小按照压缩后表大小的30%计算。
  
  
  **EVS磁盘空间推荐**：如果按以上算法估算，实际操作比较麻烦，建议：EVS磁盘空间总大小设置为压缩后的总数据量大小（压缩比一般按5倍计算），如果表上没有索引（EVS主要做缓存使用），EVS磁盘空间总大小可以设置为总数据量(可以排除掉归档数据的大小)大小的50%或者30%，并调大磁盘缓存的大小（见下文）。
  **最小容量**：
  - 性能客户：保证每个DN主备各挂载的磁盘容量最少要500G(以达到每块盘350MB/s的带宽)，比如一个ECS上部署了2主2备，该机器要至少挂载 4 \* 500G 磁盘容量。
  
  - 成本敏感客户：每个DN主备各挂载最小200G（每块盘带宽160MB/s）。
   
- **OBS配置**
  OBS要求3AZ部署并支持并行文件系统。
  **OBS性能**：
  如需调整OBS性能指标，请联系技术支持。公有云场景，6个DN及以下节点集群，OBS指标一般不需要调整。
  

- **CPU配置**
  建议生产环境每个节点16U起步，4U/8U仅用于体验。
  - 迁移场景：与迁移对象保持一致。
  
  - 新建场景：根据总数据量/100GB，计算CPU核数（与实际场景有关，计算密集型的应该增大CPU核数）。
   
 
#### 表设计优化
**建表语句**
由于DWS默认建表类型为行存表，对于OLAP分析场景，建表需显式设置ORIENTATION 选项为列存。
更多内容请参见SQL语法的[CREATE TABLE](https://support.huaweicloud.com/sqlreference-dws/dws_06_0177.html)章节。
```
CREATE TABLE public.t1(id integer) WITH (ORIENTATION =COLUMN);
```
**表设计优化**
存算分离架构下，数据存储在OBS，使用过滤手段来避免不必要的远程CU数据的读取开销，对存储分离性能优化具有更大的价值。
**过滤手段**
DWS兼容PostgreSQL生态，行存及其B-Tree索引和PostgreSQL类似，列存及其索引为自研。建表时，选择合适的存储方式、分布列、分区键、索引，能够使SQL在执行时快速命中数据，减少IO消耗。下图是一个SQL从发起到获取数据的执行流程，通过下图，可以理解每个技术手段的作用，用于指导性能调优。
图1SQL执行流程   
![](https://support.huaweicloud.com/bestpractice-dws/figure/zh-cn_image_0000002034282018.png "点击放大")
1. SQL执行时，分区表会通过Partition Column分区裁剪，定位到所在分区。
2. Hash分布表会通过Distribute Column快速定位到数据所在的数据分片（存算一体架构下定位到DN，存算分离架构下定位到Bucket）。
3. 行存通过B-Tree快速定位到数据所在的Page；列存通过min-max索引快速定位到可能存在数据的CU数据块，PCK(Partial Cluster Key)列上的min-max索引过滤效果最好。
4. 系统自动为列存的所有列维护min-max索引，无需用户定义。min-max索引是粗过滤，满足min-max条件的CU数据块不一定真正存在满足filter条件的数据行，如果定义了bitmap column，可以通过bitmap index快速定位到CU内符合条件的数据所在的行号；对于有序的CU，也会通过二分查找快速定位数据的行号。
5. 列存也支持B-Tree和GIN索引，通过B-Tree/GIN索引也可以快速定位到满足条件的数据所在的CU及行号，但索引的维护代价比较大，除非对点查有极高的性能诉求，否则推荐使用bitmap index替代B-Tree/GIN。
**优化手段**
以一个建表语句为例，描述DWS已有的优化手段。更多内容请参见SQL语法的[CREATE TABLE](https://support.huaweicloud.com/sqlreference-dws/dws_06_0177.html)章节。
![](https://support.huaweicloud.com/bestpractice-dws/figure/zh-cn_image_0000002034125650.png "点击放大")
表2优化手段 
| 编号 | 优化手段                    | 使用建议                                                                                                                                                                                                                                                                                        | SQL示例    | 建表后是否可修改               |
|:---|:---|:---|:---|:---|
| 1   | 字符串类型                 | - 字符串类型会比定长类型慢，能用定长类型的场景不建议使用字符串类型。  - 能指定长度在16以内的尽量指定，性能会翻倍提高，如果不能指定长度16以内，该优化手段不受益。    | -       | 是，已有数据会重写。             |
| 2  | Numeric类型             | Numeric类型要求都指定精度，性能会翻倍提高，尽量不要用无精度的Numeric。                                                                                                                                                                                                                                                   | --      | 是，已有数据会重写。             |
| 3   | Partition by Column    | - 需用户定义，使用分区表，通过分区键进行剪枝，支持partition wise join，适用于等值和范围查询场景。  - 分区数不宜超过1000，分区列不宜超过2列。   | ``` SELECT * FROM t1 WHERE t1.c1='p1'; ```             | 否，如需修改请重新建表。           |
| 4    | secondary_part_column | - 需用户定义，只适用于列存表，适用于等值查询。  - 可以在最常用的一个等值过滤上指定二级分区。                                      | ``` SELECT *  FROM t1 WHERE t1.c1='p1'; ```            | 否，如需修改请重新建表。             |
| 5  | Distribute by Column  | 需用户定义，适用于频繁进行group by或者多表join的join字段，通过local join减少数据shuffle，适用于等值查询。                                                                                                                                                                                                                      | ``` SELECT * FROM t1 join t2 on t1.c3 = t2.c1; ```     | 否，如需修改请重新建表。            |
| 6   | Bitmap_columns         | 需用户定义，根据CU内的重复值，自适应创建bitmap index（基数\<=32），或者bloom filter（基数\>32），适用于varchar/text类型列的等值查询场景，建议定义到where条件涉及的列。                                                                                                                                                                                | ``` SELECT * FROM t1 WHERE t1.c4 = 'hello'; ```        | 可以修改，已有数据不重写，只影响新数据。   |
| 7   | Min-max索引              | - min-max索引无需用户定义，适用于等值和范围查询场景。  - min-max的过滤效果取决于数据的有序性，指定PCK的列，过滤效果会更好。                         | ``` SELECT * FROM t1 WHERE c3 > 100 and c3  < 200; ``` | PCK可以修改，已有数据不重写，只影响新数据。 |
| 8     | 主键（B-Tree索引）          | - upsert入库强依赖主键，需用户定义，适用于等值和范围查询场景。  - 满足业务前提下，能使用定长类型的列更好，定义时把Distinct值多的列尽可能放到前面。            | ``` SELECT * FROM t1 WHERE c3 >100 and c3 < 200; ```   | 可以修改，重建索引。               |
| 8     | Gin索引               | - 需用户定义，适用不固定的多条件等值查询，Distinct值超过100万的列不建议使用。  - 适用于过滤后数据量小于1000的场景，过滤后数据量依然较大的，不推荐使用。           | ``` SELECT * FROM t1 WHERE c1 = 200 and c2 = 105; ```  | 可以修改，重建索引。             |
| 9   | Orientation=column/row | 指定表为行存或者列存，行存无压缩，适合点查和频繁更新场景；列存有压缩，适合分析场景。                                                                                                                                                                                                                                                  | -      | 否，如果修改请重新建表。             |
   
#### 关于磁盘缓存
DWS会把经常访问的数据缓存到EVS本地磁盘，以减少OBS直读次数，加速查询性能。磁盘缓存只在DN计算节点上存在，协调节点CN上不存在。
**缓存大小**
集群默认的缓存大小（disk_cache_max_size，该参数需联系技术支持设置）配置为：EVS容量的1/2。
EVS容量默认划分是：1/2存储本地持久化的数据（如：列存索引，行存表，本地列存表），另外1/2给缓存用。DWS的索引会实际存储索引数据。如果列存表没有创建索引，则可适当调大缓存配置参数disk_cache_max_size。
**缓存状态**
用户查询数据时，会优先到Disk Cache中查看数据是否已存在于本地磁盘，如果不存在则再去OBS读取数据，同时将数据缓存到本地磁盘，下次再读取这段数据时，即可在本地磁盘中读取到。使用Disk Cache可显著提升OBS数据的查询速度。
Disk Cache会默认使用主备两块硬盘作为缓存介质，通过查询以下参数查看相关信息：
- 通过disk_cache_base_paths参数查看和增减缓存硬盘路径（该参数需联系技术支持设置）。
- 通过disk_cache_max_size参数来查看及调节Disk Cache的大小。
通过查询视图[pgxc_disk_cache_all_stats](https://support.huaweicloud.com/devg-dws/dws_04_1234.html)可以查看当前缓存的命中率以及各个DN磁盘的使用大小情况：
图2pgxc_disk_cache_all_stats查询结果   
![](https://support.huaweicloud.com/bestpractice-dws/figure/zh-cn_image_0000002070403445.png "点击放大")
**缓存双写**
开启**缓存双写**可以提升首次查询数据的性能，即用户在写数据到远端OBS的同时，将数据也写到本地Disk Cache上。当第一次读取数据时，可显著提升读取效率。用户可通过disk_cache_dual_write_option来设置是否开启缓存双写，参数包含三个设置选项：
- none：表示不开启缓存双写。
- hstore_only(默认值)：表示只对HStore表，在delta merge时才开启缓存双写。
- all：表示对普通v3表和HStore表都开启缓存双写。
**缓存清理**
通过函数[pgxc_clear_disk_cache()](https://support.huaweicloud.com/sqlreference-dws/dws_06_0349.html)可以将所有的Disk Cache清空。
**集群空间不足与磁盘缓存空间调整**
当集群出现资源容量不足时，对于Disk Cache中已使用了较大空间的集群来说，可通过缩小其空间来释放集群的磁盘空间，从而缓解资源不足状态；
通过调整disk_cache_max_size参数缩小Disk Cache的实际使用空间缓解集群空间不足：
具体示例如下，假设磁盘总容量为1000GB, disk_cache_max_size大小为500GB，通过视图[pgxc_disk_cache_all_stats](https://support.huaweicloud.com/devg-dws/dws_04_1234.html)查询到实际占用450GB; 磁盘空间的总占用大小为900GB触发了资源剩余容量不足ThresholdReadRisk问题，在没有可清理的列存2.0表和索引资源的情况下，可将[disk_cache_max_size](https://support.huaweicloud.com/devg-dws/dws_04_1237.html)的大小调整为300GB或者更小的数值来缓解空间不足问题，缺点是缩小Disk Cache可用规模后可能带来查询性能下降。
![](https://support.huaweicloud.com/bestpractice-dws/public_sys-resources/note_3.0-zh-cn.png)
磁盘使用率告警判断如下：
- 容量预警：磁盘空间占用或者文件描述符使用超过ThresholdReadOnly(默认80%)；日志中会出现"Disk usage on the node %u has reached the risky threshold 80%"。
- 容量不足：磁盘空间占用或者文件描述符使用超过ThresholdReadRisk(默认90%)，触发集群只读；日志中会出现"Disk usage on the node %u has reached the read-only threshold 90%"。
- 容量严重不足：磁盘空间占用或者文件描述符使用超过ThresholdReadDanger(默认95%)，会终止DN备实例和DN从备实例，重启DN主实例；日志中会出现"Disk usage on the node %u has reached the dangerous threshold 95%"。
 
#### 插入性能
**Bucket存储**
Bucket存储就是数据分片的一个手段，与分区技术类似，也就是具有相同属性值的数据存储在一起，这样带来的好处就是：存储和计算之间的映射调整比较容易，只有这样才能实现计算和存储的分层弹性，计算资源按需拉起。
比如8个Bucket，如果有2个DN节点，每个DN负责4个Bucket；如果有4个DN，每个DN负责2个Bucket。
图3Bucket存储   
![](https://support.huaweicloud.com/bestpractice-dws/figure/zh-cn_image_0000002034815922.png "点击放大")
**入库优化**
入库需要攒批，IO异步化。
攒批：是为了避免小CU，让后续的查询性能更好。
IO异步化：存算分离之后，写OBS相比写EVS时延要高约10倍，通过IO异步，优化读写性能。
- 对于分区表，2.0表只需要进行partition攒批，3.0表相比2.0表多了Bucket攒批(相当于二级分区)，可能会消耗更多的内存和磁盘。
- 只有hash分布表需要分Bucket攒批。
**入库的攒批开销与建议**
**开销**
```
分区数目：#Np
每个节点Bucket数目：#Nb
RowGroup压缩前大小：#Nr
单个Bucket内存攒批最大大小：#Mb = max(partition_max_cache_size / partition_men_batch, 16M) = 16M（默认配置）
单并发攒批消耗： #Np * #Nb * #Nr
单并发攒批内存消耗： partition_max_cache_size， 默认2GB
单并发攒批磁盘消耗：#Np * #Nb * #Nr * 1.2(膨胀) - 内存消耗
假设一次copy数据，涉及1000个分区，#Nb≈10, 单条记录大小1K，攒批大小10000行
单并发攒批消耗： 1000 * 10 * 1K * 10000 * 1.2 = 120G
```
**建议**：
1. 应用层优化：最大的影响因素是分区数目，建议用单分区入库，单并发攒批消耗 120G-\>120M，就可以直接内存攒批了。
2. 数据库内核参数优化：调整攒批大小，通过修改min_batch_rows进行控制，该参数为session级别，可以通过set语句设置当前session生效，或者通过修改配置文件让所有session生效。
 
