如果我告诉你“唯一不变的就是变化”,你多半会觉得这是老生常谈。 当下的挑战在于:你的数据仓库能以多快的速度适应这种变化。传统数仓系统往往因为资源申请与交付的前置周期而难以及时应对变化;而在 Amazon Redshift 中,无论是存储需求还是计算需求的变化,适配都变得容易。你不必担心做出“昂贵且错误的决定”,因为可以根据负载的增长或下降快速伸缩。
扩容(Scaling)的目标,是在工作负载发生变化时,仍然维持当前的性能水平与相应的 SLA。如果你在数仓中新增了工作负载,既有工作负载的 SLA 可能会受到影响——这正是需要扩容的场景。又或者,你分析的数据规模比以往更大,从而对 SLA 产生了明显冲击。在 Amazon Redshift 上要实现扩容目标,通常要从两个方面入手:一是确保数仓容量规模正确;二是确保各项工作负载经过性能调优。
在 Amazon Redshift 中,你既可以“纵向”扩大计算能力,也可以“横向”扩展(见图 5-1)。纵向扩展(Scale Up)指为单个查询提供更强的计算资源,使总 vCPU 或内存增加。如果你需要在保留既有工作负载 SLA 的前提下再承接更多工作负载,通常会选择向上扩容。向上扩容适用于可预测的负载变化,能更好地支撑拉取大量行的大查询、更多的并发连接以及更长的事务。横向扩展(Scale Out)指增加更多副本以承载额外负载。横向扩展适用于不可预测的负载变化:每条查询都获得同等计算,但系统整体能处理更多并发。
本章将展示:当你使用 Serverless 或 RA3 预置数仓时,Amazon Redshift 如何自动“扩展存储(Scale Storage)”。此外,我们会看到:对于 Serverless 数仓,Redshift 会根据工作负载自动“弹性伸缩 Serverless 数仓”;而对于预置集群,你可以选择在何时以及朝哪个方向“扩展你的预置数仓”。
图 5-1. 纵向 vs. 横向扩展
在确保数仓规模合适的同时,对工作负载进行性能调优同样重要。两者结合能确保资源使用最优、获得最佳性价比。为调优性能,Redshift 在 Serverless 与预置两种形态下都提供了众多能力。本章将覆盖若干最佳实践:解释仅适用于预置集群的 WLM、队列与 QMR;展示 物化视图 如何支撑不同访问模式;说明 自治优化(Autonomics) 如何保持表健康;以及 工作负载隔离 如何为混合负载提供所需算力。随后我们将详细讲解查询的执行机制与 查询调优 思路,最后给出若干 额外优化 以获得更佳的价格与性能。
扩展存储(Scale Storage)
第二章“快速上手 Amazon Redshift”中提到,当你使用 Serverless 或 RA3 预置数仓时,底层存储由 RMS(托管存储)提供。其优势是存储弹性:无需仅为容纳更多历史数据而去调整计算规模。设想你的数仓主要在分析最近 12 个月的数据——每天都有新数据进入,但计算需求主要集中在最近 12 个月。此时,无论仓中是 2 年还是 5 年的数据,你的计算成本都保持不变;随着存储需求增长,你只需为额外存储付费。这非常常见:数仓长期沉淀全量数据,但分析主要访问热点的近期数据。
假设 4 月前 15 天在 RA3 托管存储中保存 100 GB,后 15 天保存 100 TB。
计算 4 月 GB-hours 用量:
- 前 15 天:100 GB × 15 天 × 24 小时/天 = 36,000 GB-Hours
- 后 15 天:100 TB × 1024 GB/TB × 15 天 × 24 小时/天 = 36,864,000 GB-Hours
- 合计:36,000 + 36,864,000 = 36,900,000 GB-Hours
- 换算为 GB-months:36,900,000 / 720(4 月按 720 小时计)= 51,250 GB-months
- 以 us-east-1 区域托管存储 0.024 = $1,230
因此 4 月 RMS 费用 = $1,230。
此处未展示计算费用,但无论数据增长与否,计算费用不变。若你暂停集群且无查询执行,则仅产生 RMS 费用。注意:即使无查询在跑,除非删除集群,仍会产生存储计费。
自动伸缩你的 Serverless 数仓
Amazon Redshift Serverless 会根据需要自动向上或向外伸缩算力。无活动时后台计算资源会自动关闭;当有数据装载或查询进入时自动恢复。在 Serverless 模式下,你无需预估负载或预配算力,系统会自适应调整。很多场景下,整体计算消耗还会下降:此前因算力不足而发生磁盘分页的查询会更快完成,原本排队等待的查询也不再等待。
算力以 RPU 计量,按 RPU-hours 计费,按秒计费。为控制成本,你可设定使用上限与触达后的自动动作。上限可按日/周/月检查。更高的上限有助于在高并发场景下保持稳定高性能。Serverless 价格示例见第二章。
扩展你的预置数仓
当你创建预置集群时,需要选择节点类型与节点数量。如何“向上”增加单节点能力或“向外”并行扩展,由你掌控。一般而言:
- 演进中的计算需求(Evolving Compute Demand) 或 可预测的负载变化:倾向于向上扩容;
- 不可预测的负载变化:可借助并发伸缩(Concurrency Scaling) 实现“向外”。
演进中的计算需求
假设你的数仓项目非常成功,正在不断增加新的分析负载;但仓内数据量保持不变(旧数据已下沉到数据湖)。也就是说,查询者变多、分析更频繁,计算需求上升。为了维持相同的用户体验与 SLA,你可以通过加节点或切换更大节点规格来向上扩容。
向上扩容不改变存储成本(数据量不变)。
加节点或改节点类型都可通过控制台、CLI 或 API 快速完成。比如,从 2 节点 ra3.4xlarge 切到 2 节点 ra3.16xlarge,相当于从 24 vCPU 提升到 96 vCPU,计算与内存翻四倍。或者把 2×ra3.4xl 扩到 8×ra3.4xl,同样获得 96 vCPU。当当前节点类型接近极限时,切换到更大节点类型更合适,例如从 64×ra3.4xl 变为 16×ra3.16xl,可获得相当的总算力但拥有更强的 Leader 节点。
注意:更换节点类型涉及数据在不同计算类型之间的物理搬迁,需要规划停机窗口、团队协同与变更通知,以尽量减少影响。
价格示例:假设你以 2 节点 ra3.4xlarge 起步,月中(15 号)扩到 5 节点。
- 前 15 天:489**
- 后 15 天:2,445**
- 4 月计算费合计 = $2,934
CLI 示例(例 5-1) :
# 扩到 4 个节点
aws redshift modify-cluster \
--cluster-identifier mycluster01 \
--node-type ra3.4xlarge \
--number-of-nodes 4
# 升级到 2 节点 ra3.16xlarge
aws redshift modify-cluster \
--cluster-identifier mycluster01 \
--node-type ra3.16xlarge \
--number-of-nodes 2
可预测的负载变化
可预测的变化意味着你知道会发生、大致时间可控、可以提前制定计划。变化可以是一次性,也可能周期性发生。比如,日常负载是每天处理增量文件;但每月 1 日还需要处理上月对账文件。你已按日常增量规模好集群,但每月 1 日需要额外算力以确保当日增量与对账同时按时完成。
此时可以:
- 将集群定时扩容(最高可放大 4 倍)至月初 1 日,月初 3 日缩回原节点数;或
- 直接在月度作业编排中插入“扩容/缩容”步骤。
Redshift 调度器的 Cron 表达式格式为:分 时 日 月 周 年
0 0 1 * ? * # 每月1日 00:00 扩容
0 0 3 * ? * # 每月3日 00:00 缩回
你也可以在控制台用内置调度器设置(图 5-2)。
Redshift 提供两种 Resize:Elastic resize(适合周期性临时扩缩)与 Classic resize(适合永久性变更;Elastic 也可用于永久)。
你可以查询未公开系统表 stv_xrestore_alter_queue_state 以监控大规模(>5TB)变更进度(例 5-2):
SELECT db_id, status, count(*)
FROM stv_xrestore_alter_queue_state
GROUP BY 1, 2
ORDER BY 3 DESC;
示例输出:
db_id status count
654321 Waiting 456
654321 Finished 23
654321 Applying 1
不可预测的负载变化
对可预测变化,可通过 Resize 解决;而间歇性、不可预测的突发更具挑战:若按峰值预配,会在低谷浪费资源;若按常态预配,突发时关键查询会等待。此时可启用 Concurrency Scaling(并发伸缩,CS) 。
Redshift 会自动拉起额外的并发伸缩集群应对突发(图 5-3)。所有节点类型都支持对读查询的 CS;RA3 节点还支持对写查询的 CS。如何启用与使用由 工作负载管理(WLM)队列控制(详见“WLM、队列与 QMR”)。当CS 启用的队列中所有等待查询的总等待时长超过1 分钟时触发 CS(此阈值可通过 AWS Support 调整以更激进)。一旦 CS 集群启动,后续进入该队列的新查询将直接被发送到 CS 集群执行而不再排队。
图 5-3. Amazon Redshift 并发伸缩
你可以在控制台“Workload management”中设置 CS(图 5-4)。虽然默认参数组 default.redshift-1.0 不能编辑,但你可以创建新的参数组,修改 max_concurrency_scaling_clusters 来限制可拉起的 CS 集群数量(最大 10,可提配额工单)。参数组细节见后文“参数组”。
CS 集群独立执行被分配的查询。每个查询在执行前需要编译;主集群与 CS 集群的本地编译缓存彼此独立。执行时,Redshift 会将查询切分为段,先查本地缓存;未命中则查全局编译缓存(external code compilation cache)。若全局命中,则下载到本地后执行;若不命中,便提交到外部编译集群进行并行编译,编译产物会写入全局与本地缓存。因此,即便 CS 集群冷启动没有本地段缓存,也可利用全局缓存;若无则需重新编译。
图 5-4. 最大并发伸缩集群数设置
无论查询在主集群还是 CS 集群上跑,都针对最新数据执行。Redshift 会在 CS 期间持续同步最新数据。CS 具备成本,但 Redshift 提供每 24 小时主集群运行时间赠送 1 小时 CS 额度。
CS 集群在数分钟内就绪,按秒计费,且仅对实际执行查询的时间计费(不含创建/释放过程)。释放后,其所用 EC2 资源会被彻底清理并回收至 CS 资源池,确保无残留。
CS 按按需价计费,且支持 Reserved Instances(RI) 折扣。若你长期高比例使用 CS,应评估直接加主集群节点是否更划算。
你可以在控制台设置 CS 成本控制:达到上限后,Redshift 可仅记录日志或直接关闭 CS。当达到上限时正在 CS 上执行的查询会跑完,后续查询回到主集群排队执行。
CS 上限可动态调整,无需重启集群。截至撰写时,CS 仅在商用区域可用,AWS GovCloud 暂不支持。
WLM、队列与 QMR
一个典型的组织里,会有多种类型的用户,且对性能的期望不同。Amazon Redshift(预置集群)的工作负载管理(WLM)功能,提供了基于业务优先级来运行工作负载的能力(见图 5-5)。WLM 为你提供必要的控制,以最大化数仓吞吐量(即在给定时间内处理的查询数量)。你最多可以定义 8 个队列,用于对正在执行的查询进行逻辑隔离。每个队列都有唯一的服务类(service class)标识符。标识符 1~4 保留给系统使用,5 是超级用户队列(superuser queue),15 用于 Amazon Redshift 的日常维护活动。更多细节参见 WLM 系统表与视图。
图 5-5. Amazon Redshift WLM 队列
在图 5-5 中,你可以看到定义了三个队列;WLM 会根据队列分配规则,将左侧进入的查询分配到右侧的特定 WLM 队列。
队列分配(Queue Assignment)
Amazon Redshift 的默认配置只有一个队列:默认队列(default queue)。除非根据分配规则被路由到其他队列,否则所有查询都在该队列中执行。
WLM(工作负载管理)按照如下匹配逻辑将查询分配到队列:
- 若具有 superuser 权限的用户提交查询,且 query_group 被设置为 superuser,则分配到 superuser 队列。
- 若普通用户提交查询并且 user group(用户组) 匹配,则分配到匹配的队列。
- 若普通用户提交查询并且 query group(查询组) 匹配,则分配到匹配的队列。
- 如果没有匹配项,则分配到 默认队列。
可参考 WLM 队列分配规则 获取队列分配的流程图与示例。
如果一个查询同时匹配多个队列,则将其分配到第一个匹配到的队列。
每个查询在执行时都会被分配一个槽位(slot) 。槽位代表集群内存(RAM)的一部分。superuser 队列的并发度始终为 1(无论是手动 WLM 还是自动 WLM)。并且你必须手动将 query group 设为 superuser 才能在 superuser 队列中运行查询(见示例 5-3)。
示例 5-3. Superuser 队列
SET query_group TO 'superuser';
RESET query_group;
每个队列都可以映射到 user group 或 query group 之一。
- User group:对用户的逻辑分组,例如把各个 ETL 应用用户(如
app_1_etl_usr,app_2_etl_usr)归到etl_group。 - Query group:在运行时设置的文本标签(见示例 5-4)。这通常用于 BI 工具:即便只使用单一的数据库用户 ID,也希望把某些仪表盘查询相对其他查询优先化。
示例 5-4. 设置 query group
SET query_group TO 'c_level_dashboard';
默认情况下,每个查询只分配一个槽位;如果查询能在该内存配额内完成,不发生落盘(spill),通常会比发生落盘时更快。
使用示例 5-5 的 SQL 可检查发生**磁盘溢写(disk spill)**的查询。
示例 5-5. 检查磁盘溢写
WITH q_spill AS
(
SELECT
starttime,
q.query,
round(nvl(query_temp_blocks_to_disk,0)::decimal/1000,2) spill
FROM stl_query q
LEFT JOIN svl_query_metrics_summary m
USING (query)
WHERE q.userid >= 100
)
SELECT
date_trunc('d',starttime) AS day,
count(query) AS queries,
sum(CASE WHEN spill = 0 THEN 1 ELSE 0 END) AS no_spill,
sum(CASE WHEN spill > 0 AND spill < 5 THEN 1 ELSE 0 END) AS "<5GB",
sum(CASE WHEN spill BETWEEN 5 AND 200 THEN 1 ELSE 0 END) AS "5-200GB",
sum(CASE WHEN spill BETWEEN 201 AND 500 THEN 1 ELSE 0 END) AS "201-500GB",
sum(CASE WHEN spill BETWEEN 501 AND 1000 THEN 1 ELSE 0 END) AS "501GB-1TB",
sum(CASE WHEN spill > 1000 THEN 1 ELSE 0 END) AS ">1TB",
round(max(spill),2) AS max_spill_gb
FROM q_spill
GROUP BY 1
ORDER BY 1;
槽位数量与每槽内存大小对查询执行性能至关重要。
你可以通过 wlm_query_slot_count 参数为大查询(如 VACUUM)临时分配更多槽位以提速;这样做会在你重置该参数之前降低集群并发度。该方式在手动 WLM与自动 WLM模式下均适用。
你可以选择在手动 WLM中自行设置槽位,或交由 自动 WLM 由 Amazon Redshift 进行管理。
短查询加速(Short Query Acceleration,SQA)
系统还提供专门针对短时查询的特殊队列:SQA 队列。Amazon Redshift 会估算每个查询的执行时间,如果符合条件就将其发送到 SQA。若实际运行时间超过 SQA 上限,则把查询迁出到某个匹配的 WLM 队列。只有只读查询有资格进入 SQA。SQA 的 service class 标识为 14。
在手动 WLM中,你可以指定符合 SQA 的最大运行时长(秒) ;在自动 WLM中,系统会根据你的查询模式自动确定。
若使用手动 WLM,可用示例 5-6 的 SQL 分析各工作负载队列并选择 70–90 百分位来设定 SQA 阈值。
示例 5-6. 计算 SQA 阈值
SELECT
service_class AS QUEUE,
count(1) AS queries,
avg(total_queue_time)/1000000 AS avg_q_sec,
min(total_queue_time)/1000000 AS min_q_sec,
max(total_queue_time)/1000000 AS max_q_sec,
round(percentile_cont(0.7) WITHIN group (ORDER BY total_queue_time)/1000000,0) AS p70_sec,
round(percentile_cont(0.9) WITHIN group (ORDER BY total_queue_time)/1000000,0) AS p90_sec
FROM stl_wlm_query
WHERE userid >= 100
GROUP BY service_class
ORDER BY service_class;
示例输出:
queue queries avg_q_sec min_q_sec max_q_sec p70_sec p90_sec
5 20103 23 0 95 15 19
6 3421 42 15 32 18 23
7 42 178 109 466 176 261
8 196 398 99 1399 108 206
在上述示例中,将 SQA 阈值设置在 15–18 秒之间,可使大多数查询受益于 SQA。
查询监控规则(Query Monitoring Rules,QMR)
在两种 WLM 模式下,Amazon Redshift 都提供 QMR,可基于特定规则控制集群对查询执行的行为。
- 你可以使用 16 个系统定义的指标 来编写 QMR 条件;同时提供 5 个系统模板 便于快速上手。
- 当正在执行的查询越过定义阈值时,触发相应动作:abort(中止)、log(记录日志)、change query priority(仅自动 WLM)、或 hop 到其它匹配队列(仅手动 WLM)。
- 你最多可在所有队列中定义 25 条 QMR,每条规则可包含最多 3 个条件。当规则的所有条件满足时,WLM 会向
STL_WLM_RULE_ACTION系统表写入一行,记录触发该规则的查询与结果动作。
Redshift Serverless 没有 WLM,但提供 Query Limits(查询上限) ,其规则逻辑与 QMR 类似。可用它来防止用户提交“失控”的查询。
QMR 指标每 10 秒评估一次,因此某些规则触发可能会有延迟。
你可以通过 QMR 在问题发生前获知“坏查询”,而不是在用户抱怨变慢后再被动处理。还可以借助 QMR 记录低质量 SQL,定期组织讨论/培训,帮助用户改进查询技巧。
为设置阈值,利用系统表与视图:
- 正在运行查询指标:
STV_QUERY_METRICS - 已完成查询指标(表):
STL_QUERY_METRICS - 已完成查询指标(视图):
SVL_QUERY_METRICS - 已完成查询最大值汇总(视图):
SVL_QUERY_METRICS_SUMMARY
典型建议:
- BI 队列:对 嵌套循环连接(nested loop join) 建立 QMR(常因缺失连接谓词造成大笛卡尔积);用较低的行数阈值尽早发现潜在失控查询。若专门为简单短时查询设置队列,再加一条“返回异常高行数”的规则;需要大量返回时用 UNLOAD 替代直接返回数十亿行。
- 分析师队列:针对“连接后行数很高”(提示需要更严格过滤)或“写入中间结果的高磁盘使用”(可指示异常查询)建立规则。
- 严重违规场景(如仪表盘队列通常 <10 秒完成):对“执行时间 >20 秒”设 QMR 并直接中止。
- 数据科学队列(长时查询常态):对排队时间设限,限制进入队列的查询数量。
Redshift 提供了现成的 QMR 模板,你只需自定义阈值即可。建议起步阶段将规则动作设为 log,每周与用户一起复盘被记录的查询并提供调优方法;若长期无改善,再预先告知后将动作改为 abort。
使用下述 示例 5-7 的 SQL,可查看过去 7 天数据仓库的使用模式,按工作负载队列(service class)与小时维度细分 SELECT / UPDATE / INSERT / DELETE / CURSOR / CACHED / COPY / UNLOAD / VACUUM / SYSTEM 等查询类型,帮助确定最佳 WLM 配置。
示例 5-7. 按工作负载队列的明细分解
SELECT
service_class,
query_hour,
TO_CHAR(MAX(CASE WHEN query_type = 'SELECT' THEN qry_cnt ELSE NULL END),'999,999') AS "select_query_count",
TO_CHAR(MAX(CASE WHEN query_type = 'SELECT' THEN exec_s ELSE NULL END),'999,999') AS "select_exec_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'SELECT' THEN queue_s ELSE NULL END),'999,999') AS "select_queue_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'INSERT' THEN qry_cnt ELSE NULL END),'999,999') AS "insert_count",
TO_CHAR(MAX(CASE WHEN query_type = 'INSERT' THEN exec_s ELSE NULL END),'999,999') AS "insert_exec_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'INSERT' THEN queue_s ELSE NULL END),'999,999') AS "insert_queue_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'UPDATE' THEN qry_cnt ELSE NULL END),'999,999') AS "update_count",
TO_CHAR(MAX(CASE WHEN query_type = 'UPDATE' THEN exec_s ELSE NULL END),'999,999') AS "update_exec_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'UPDATE' THEN queue_s ELSE NULL END),'999,999') AS "update_queue_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'DELETE' THEN qry_cnt ELSE NULL END),'999,999') AS "delete_count",
TO_CHAR(MAX(CASE WHEN query_type = 'DELETE' THEN exec_s ELSE NULL END),'999,999') AS "delete_exec_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'DELETE' THEN queue_s ELSE NULL END),'999,999') AS "delete_queue_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'CURSOR' THEN qry_cnt ELSE NULL END),'999,999') AS "cursor_count",
TO_CHAR(MAX(CASE WHEN query_type = 'CURSOR' THEN exec_s ELSE NULL END),'999,999') AS "cursor_exec_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'CURSOR' THEN queue_s ELSE NULL END),'999,999') AS "cursor_queue_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'CACHED' THEN qry_cnt ELSE NULL END),'999,999') AS "cached_query_count",
TO_CHAR(MAX(CASE WHEN query_type = 'COPY' THEN qry_cnt ELSE NULL END),'999,999') AS "copy_count",
TO_CHAR(MAX(CASE WHEN query_type = 'COPY' THEN exec_s ELSE NULL END),'999,999') AS "copy_exec_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'COPY' THEN queue_s ELSE NULL END),'999,999') AS "copy_queue_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'UNLOAD' THEN qry_cnt ELSE NULL END),'999,999') AS "unload_count",
TO_CHAR(MAX(CASE WHEN query_type = 'UNLOAD' THEN exec_s ELSE NULL END),'999,999') AS "unload_exec_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'UNLOAD' THEN queue_s ELSE NULL END),'999,999') AS "unload_queue_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'VACUUM' THEN qry_cnt ELSE NULL END),'999,999') AS "vacuum_count",
TO_CHAR(MAX(CASE WHEN query_type = 'VACUUM' THEN exec_s ELSE NULL END),'999,999') AS "vacuum_exec_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'VACUUM' THEN queue_s ELSE NULL END),'999,999') AS "vacuum_queue_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'OTHER' THEN qry_cnt ELSE NULL END),'999,999') AS "system_query_count",
TO_CHAR(MAX(CASE WHEN query_type = 'OTHER' THEN exec_s ELSE NULL END),'999,999') AS "system_exec_seconds",
TO_CHAR(MAX(CASE WHEN query_type = 'OTHER' THEN queue_s ELSE NULL END),'999,999') AS "system_queue_seconds"
FROM
(
SELECT
NVL(w.service_class,14) service_class,
CASE
WHEN w.query IS NULL THEN 'CACHED'
WHEN q.userid = 1 THEN 'OTHER'
WHEN REGEXP_INSTR("querytxt", '(padb_|pg_internal)') THEN 'OTHER'
WHEN REGEXP_INSTR("querytxt", '[uU][nN][dD][oO][iI][nN][gG]') THEN 'OTHER'
WHEN REGEXP_INSTR("querytxt", '[uU][nN][lL][oO][aA][dD] ') THEN 'UNLOAD'
WHEN REGEXP_INSTR("querytxt", '[cC][uU][rR][sS][oO][rR] ') THEN 'CURSOR'
WHEN REGEXP_INSTR("querytxt", '[fF][eE][tT][cC][hH] ') THEN 'CURSOR'
WHEN REGEXP_INSTR("querytxt", '[dD][eE][lL][eE][tT][eE] ') THEN 'DELETE'
WHEN REGEXP_INSTR("querytxt", '[cC][oO][pP][yY] ') THEN 'COPY'
WHEN REGEXP_INSTR("querytxt", '[uU][pP][dD][aA][tT][eE] ') THEN 'UPDATE'
WHEN REGEXP_INSTR("querytxt", '[iI][nN][sS][eE][rR][tT] ') THEN 'INSERT'
WHEN REGEXP_INSTR("querytxt", '[vV][aA][cC][uU][uU][mM][ :]') THEN 'VACUUM'
WHEN REGEXP_INSTR("querytxt", '[sS][eE][lL][eE][cC][tT] ') THEN 'SELECT'
ELSE 'OTHER'
END AS query_type,
DATEPART(hour, q.starttime) AS query_hour,
COUNT(1) AS qry_cnt,
ROUND(SUM(w.total_exec_time)::NUMERIC/1000000,0) AS exec_s,
ROUND(SUM(w.total_queue_time)::NUMERIC/1000000,0) AS queue_s
FROM stl_query q
LEFT JOIN stl_wlm_query w
ON q.userid = w.userid
AND q.query = w.query
WHERE
q.endtime >= DATEADD(day, -7, CURRENT_DATE)
AND q.userid > 1
AND NVL(w.service_class,14) > 4
GROUP BY service_class, query_type, query_hour
)
GROUP BY service_class, query_hour
ORDER BY service_class, query_hour;
自动 WLM(Automatic WLM)
当你启动 Amazon Redshift 时,默认启用的是 自动 WLM(auto WLM) 。在 auto WLM 下,你将总槽位数与每个槽位的内存分配交由 Amazon Redshift 动态自动决定。你可以创建最多 8 个队列(其 service class 标识符为 100–107)。你需要为每个队列关联一个从低到高的优先级;当多个队列中的查询争用资源时,高优先级队列中的查询会优先获得资源,以确保关键查询不会被低重要性的查询“饿死”。当执行资源密集型查询时,你希望并发更低、单查询内存更大;相反,当执行轻量查询时,你希望并发更高以同时运行更多查询。auto WLM 的关键目标就是在任意时刻尽可能容纳并执行更多查询。
虽然在 AWS 控制台中没有直接呈现,Serverless 数据仓库也使用 auto WLM 来确保每条查询都被分配到与其需求相匹配的资源。
auto WLM 会遵从你为队列设定的优先级,并据此优先处理高优先级查询。因此,你需要把业务优先级与队列优先级对应起来。比如,如果业务最关注“获取最新数据”,你可以将 ETL 队列设为高于报表队列的优先级。Amazon Redshift 会根据正在执行的查询相对规模来调节集群并发:当执行重型 ETL 查询时,系统检测到每条查询所需资源更高,就会降低并发;当执行较轻的仪表盘查询时,系统检测到每条查询需求更低,就会提高并发。你也可以为每个队列启用 Concurrency Scaling(并发扩展) 来横向扩容集群以获得更高吞吐。如果你的主集群正在执行所有高优先级查询,这时又有新查询到来;未启用并发扩展的情况下,这些新查询就只能等待当前查询完成。
Amazon Redshift 提供六档优先级:
- Lowest(最低)
- Low(较低)
- Normal(普通)
- High(较高)
- Highest(最高)
- Critical(关键)
前五档可以分配给 WLM 队列;Critical 只能由 superuser 在查询或会话级设置,且同一时间只能有一条以 Critical 优先级执行。
auto WLM 默认起始定义了 5 个 heavy 槽位与 5 个 light 槽位:heavy 槽位分到95% 的集群内存,light 槽位分到剩余 5% 。这些槽位会随着工作负载到来被占用,必要时会创建更多槽位。槽位更少 ⇒ 单槽内存更大(适合重查询) ;槽位更多 ⇒ 单槽内存更小(适合轻查询) 。其中 5 个 light 槽位保留给短时查询,重查询不能占用这些槽位;而5 个 heavy 槽位若空闲,则可被轻查询使用。
你可以查询未公开文档的视图 stl_wlm_auto_concurrency 来了解 auto WLM 的并发表现。
示例 5-8. 查询 auto WLM 并发
select
now,
heavy_slots AS hs,
heavy_queries_finished AS hqf,
heavy_queries_exec_time AS hqe,
heavy_queries_queued AS hqq,
light_slots AS ls,
light_queries_finished AS lqf,
light_queries_exec_time_sec AS lqe,
light_queries_queued AS lqq
FROM
stl_wlm_auto_concurrency
WHERE
NOW BETWEEN '2023-01-16 21:08:00' AND '2023-01-16 21:33:00'
ORDER BY 1;
(后附样例输出略)
当没有可用资源而又有更高优先级的查询需要资源时,auto WLM 可能会终止尚未开始返回数据的低优先级查询。
当你的工作负载具有动态性,并且你希望按业务优先级来驱动资源分配时,建议使用 auto WLM。auto WLM 会最大化集群资源利用率,让尽可能多的查询并发执行。强烈建议在使用 auto WLM 时配置 QMR(查询监控规则) (见“Query Monitoring Rules”章节),以防“流氓查询”耗尽资源。
手动 WLM(Manual WLM)
使用 手动 WLM 时,你可以对每条查询的并发与内存分配进行更细粒度的控制。若你的工作负载需要全天候的固定资源保证,可以使用手动 WLM。例如:你整天都有稳定的 ETL 采集作业,可以为其设置一个队列并分配一定比例的内存。与 auto WLM 类似,手动 WLM 也可创建最多 8 个队列,其 service class 标识符为 6–13。关键差异在于:你可以手动控制每个队列的槽位数与内存百分比。建议所有手动 WLM 队列的总槽位数为 15。
手动 WLM 仅适用于 预置(provisioned) 数据仓库;Serverless 数据仓库使用 auto WLM。
如果你的仓库有 300 GiB 内存,并给 ETL 队列分配 40% 内存且设置 3 个槽位,那么每条查询将获得 300 × 0.40 / 3 = 40 GiB 内存。若查询需要超过 40 GiB,就会发生落盘并且执行更久。在手动 WLM 模式下,每个队列槽位的内存是预留的;如果进入的查询数量超过槽位数,超出的查询会排队等待。若要突破每个队列的固定内存上限,你可以启用 Concurrency Scaling。并发扩展是对每个队列单独启用的,且只有当该队列已满时,查询才会被路由到扩展集群。
使用手动 WLM 时,一旦某个队列的槽位全满,后续进入该队列的查询必须等待,即使其他队列还有空闲槽位。
当你非常熟悉自己的工作负载并希望最大控制权时,建议使用手动 WLM。但它可能导致资源未被充分利用。对于非常稳定、可重复的工作负载,手动 WLM 是候选方案;但每当引入新负载且该负载有 SLA 时,你都应重新评估槽位与内存分配。若新负载没有严格 SLA,可让其进入默认队列,按队列轮转完成。
当你观察到(用前文示例 5-5 的查询)auto WLM 经常大量落盘,或虽不落盘但排队很多且系统还能承载更多槽位时,可以考虑手动 WLM。注意手动 WLM 需要更多管理精力,但可能带来更好的成本优化。在“落盘很多”的场景里,更贵但更省心的办法是纵向扩容主集群;在“从不落盘但排队很多”的场景里,可以用更多 Concurrency Scaling,但这可能更贵,此时改用手动 WLM可能更省钱。
如果你在定义手动 WLM 时没有分配 100% 的总内存,那么 Amazon Redshift 会把未分配的那部分动态补给任何需要更多内存的队列。这可视为一种混合 WLM:先给各队列设置最小内存,再让 Redshift 在你的手工配置之上做部分自动内存分配。
表 5-1:auto WLM 与 manual WLM 对比
| 特性 | Auto WLM | Manual WLM | 备注 |
|---|---|---|---|
| 定义队列优先级 | 是 | 否 | 队列优先级仅在 auto WLM 可用。 |
| 定义队列并发(槽位数) | 否 | 是 | 槽位数仅在手动 WLM 可设。 |
| 定义队列内存分配 | 否 | 是 | 队列内存比例仅在手动 WLM 可设。 |
| 单查询超配内存 | 否 | 是 | 若手动 WLM 槽位过少则单查询内存偏大。 |
| 单查询内存不足 | 否 | 是 | 若手动 WLM 槽位过多则单查询内存偏小。 |
| 最大并发查询数 | 动态 | 固定 | 手动 WLM 下,各队列槽位总和=最大并发数。 |
| 由队列定义查询优先级 | 是 | 否 | 手动 WLM 中“槽位多≈优先级低”,“槽位少≈优先级高”。 |
| 查询落盘概率 | 更低 | 更高 | auto WLM 通过降并发为单查询分配更多内存;手动 WLM 需你自行给足每查询内存。 |
| 短查询阈值定义 | 自动 | 可设 | 手动 WLM 可在 1–20 秒间设置。 |
| QMR 动作:Hop | 否 | 是 | 手动 WLM 会按分配规则尝试路由到下个匹配队列;若找不到匹配则取消而非回默认队列。 |
| 并发扩展(CS) | CS 集群使用 auto WLM | CS 集群槽位上限 5 | 若启多套 CS 集群,主集群可再创建额外集群。 |
WLM 的目标是为仓库中的查询进行资源管理。你可以查询 Redshift 系统表来分析工作负载并据此配置 WLM。
示例 5-9. 按小时的查询执行统计
SELECT DATE_TRUNC('hour',starttime) AS start_hour
, MIN(starttime) AS first_query_start_time
, MAX(starttime) AS last_query_start_time
, COUNT(*) AS query_count
FROM stl_query
WHERE userid > 1
GROUP BY start_hour
ORDER BY start_hour DESC
;
示例 5-10. 各队列(或 service class)的峰值内存估计
(也可按 service_class_start_time 过滤某个时间段)
SELECT
w.service_class,
COUNT(*) query_count,
ROUND(PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY est_peak_mem)/1024^2,0)::INT AS prcntl25_mb,
ROUND(PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY est_peak_mem)/1024^2,0)::INT AS prcntl50_mb,
ROUND(PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY est_peak_mem)/1024^2,0)::INT AS prcntl75_mb,
ROUND(PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY est_peak_mem)/1024^2,0)::INT AS prcntl90_mb,
ROUND(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY est_peak_mem)/1024^2,0)::INT AS prcntl95_mb,
ROUND(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY est_peak_mem)/1024^2,0)::INT AS prcntl99_mb,
ROUND(MAX(est_peak_mem)/1024^2,0)::INT AS p100_mb
FROM stl_wlm_query w
WHERE w.userid > 1
GROUP BY w.service_class
ORDER BY w.service_class
;
如果你需要同时确保多类负载并行执行,又无法为这些队列设置不同优先级,有两个选择:
- 使用手动 WLM;
- 拆分为两个独立集群(各自使用 auto WLM),再通过**数据共享(data sharing)**服务各自的工作负载。第二种方式可让各自的隔离集群获得最大吞吐,同时保持资源的充分利用。(详见第 7 章)
参数组(Parameter Group)
参数是某项设置的取值,例如 auto_mv(布尔)。参数组是这类参数的集合(参见图 5-4)。参数组仅适用于预置集群,并作用于关联该参数组的集群上的所有数据库。Redshift 自带一个默认参数组,不可编辑;你可以新建参数组并自定义数据库设置。
你可以创建多个参数组,为不同的数据库参数设置不同取值;可将同一参数组关联到一个或多个集群。修改某个参数组,即可变更所有使用该参数组的集群配置。部分 WLM 属性为静态(需要重启),其余为动态(立即生效)。
示例 5-11. 通过 AWS CLI 修改集群参数组
aws redshift modify-cluster \
--cluster-identifier mycluster01 \
--cluster-parameter-group-name pg_bi
你可以为写入密集的 ETL与读取密集的 BI分别准备一个参数组;若只挑选动态属性配置在这两个参数组中,你就能在无需重启的情况下在两类配置间切换(按 ETL/BI 时间窗切换)。当然,auto WLM 会为你动态完成这类资源调度。
WLM 动态内存分配(Dynamic Memory Allocation)
在每个队列中,WLM 会依据该队列设定的并发限制创建相应数量的槽位;分配给队列的内存会均分到每个槽位。当你更改内存分配或并发设置时,Amazon Redshift 会动态管理到新配置的过渡:活动查询会继续使用已分配的内存直至完成。
过渡步骤如下:
- 计算每个槽位的新内存分配。
- 空闲槽位释放其原先的内存。
- 活动槽位继续执行,直至查询完成,随后释放内存。
- 一旦可用内存足够,添加新槽位。
- 当所有先前运行中的查询都完成后,过渡结束,槽位数与新的并发级别一致。
因此,变更发生时正在运行的查询继续使用原内存;排队中的查询会在新槽位可用时被调度。由于 Redshift 的动态内存分配,你可以在无需重启的情况下,通过参数组修改 WLM 队列的内存百分比。
物化视图(Materialized Views)
物化视图(MV)是加速高资源消耗且重复/可预测的连接查询的强大工具,典型用例如带有昂贵连接与聚合的仪表盘查询。物化视图存储的是基表查询的结果。这不同于普通视图:普通视图只存储查询定义,每次访问都会重新执行查询。
Amazon Redshift 的查询优化器会自动识别何时可用已有的物化视图来满足请求,并透明地重写请求以使用该 MV。查询会直接命中 MV,而不是命中底层明细表。因此,应用侧的查询无需改写即可享受新建 MV 带来的加速(自动查询改写)。
MV 可以构建在其他 MV 之上,从而为不同聚合层级创建不同 MV,使终端查询可通过合适的 MV 更快得到结果。注意:刷新 MV 不会级联,因此应从最底层的 MV 开始刷新,然后自下而上依次刷新。
MV 保存的是上次刷新时间点的数据。Amazon Redshift 支持快速增量刷新:跟踪基表变化,仅拉取受影响记录。若某个 MV 不具备增量刷新资格,则刷新时会全量重算。你可以在基表装载完成后立即刷新 MV,确保 MV 始终拥有最新结果,并可被自动查询改写使用。
以下系统对象提供有关 MV 的关键信息:
STV_MV_INFO表:每个 MV 一行,包含其数据是否陈旧以及状态信息。STL_MV_STATE视图:记录物化视图的每一次状态转换。SVL_MV_REFRESH_STATUS视图:记录物化视图的刷新活动。
在数据湖的外部表查询中,大表连接成本高。一个优化技巧是:在 Redshift 中对外部表的聚合结果创建 MV。若后续进行行级深挖,可通过外部表查询直接访问原始文件。
自主优化(Autonomics)
Amazon Redshift 的自动调优由机器学习驱动:基于最佳实践为众多架构设置建立智能默认值,并依据启发式自动优化物理数据布局。本节将这些能力统称为Autonomics。
SVL_AUTO_WORKER_ACTION 表记录了 Redshift 执行的所有自动优化活动。
自动表优化与智能默认值(Auto Table Optimizer & Smart Defaults)
自动表优化(ATO)会在表的 分布键 与 排序键 定义为 AUTO 时,自动选择最优的分布样式与排序键,无需管理员干预。通过自动化调优,你可以更容易地起步并更快获得高性能,而无需投入大量人工做表设计调优。
通常,表上定义的主键(PK)具备高基数且无重复,因此非常适合作为分布键候选;智能默认值算法会将其应用为Auto Distribution Key。此外,即便该 PK 尚未参与连接,Redshift Advisor 也会基于启发式使用主键给出建议,以更早产出可行建议;待有更多工作负载数据后,Advisor 会给出更优建议。对于复合主键(常见于事实表),不会仅凭启发式给出建议,因为事实表的外键通常引用多个维表的主键;当可分析实际工作负载中的连接模式时,ATO 才会选择最常用于连接的列作为分布键。
若选择了 Auto DK,ATO 还可能将基于 PK 的分布迁移为ALL 分布(针对小表)。维表通常行数远小于事实表,且被频繁连接;将其设为 ALL 分布可确保连接共址、提升性能(如日历维表)。
自动 VACUUM(Auto Vacuum)
后台的自动 vacuum 进程有两项任务:
- auto vacuum delete:回收被先前
UPDATE/DELETE标记删除的行所占空间; - auto vacuum sort:按表定义的排序键对物理数据块进行排序。若已有**>95%** 的数据已排序,auto vacuum 将跳过该表的排序。
示例 5-12:表排序收益
SELECT
"table",
unsorted,
vacuum_sort_benefit,
tbl_rows,
estimated_visible_rows
FROM
svv_table_info
ORDER BY 1;
示例输出(节选):
table | unsorted | vacuum_sort_benefit | tbl_rows | estimated_visible_rows
-------+----------+---------------------+----------+----------------------
sales | 85.71 | 5 | 17686548 | 17601203
event | 35.24 | 67 | 27586582 | 27486080
解释:sales 未排序比例高(86%),但排序收益低(5%);event 未排序较低(35%),但排序收益高(67%)。
Redshift 每小时都会寻找机会执行 vacuum。auto vacuum 是否启动,取决于用户查询占用的 WLM 槽位数量:只要超过一半槽位空闲,auto vacuum 就会为自己分配 100 MB 内存、加载100 个块并开始处理;每轮迭代结束评估 WLM 状态再决定继续。若用户查询到来并占用≥50% 槽位,auto vacuum 会终止本轮,部分未完成工作会被丢弃(已完成部分保留),下次再申请 100 MB 重启。相对地,若用户手动执行 VACUUM,同一进程会不再受 50% 槽位门限限制,而是一口气执行到完成。
VACUUM RECLUSTER 仅对未排序的分段进行排序,已排序区域保持不变;它不会把新排序的数据与旧排序区域合并,也不会完全回收删除标记的空间。若你频繁摄入数据且查询只访问最新数据,可优先考虑 RECLUSTER。
在自动或手动 vacuum 过程中,查询仍可访问这些表:可执行 SELECT/INSERT;若在 VACUUM 期间执行 UPDATE/DELETE,则两者都可能变慢。
若希望优先处理某表且算力富余,可使用 BOOST 选项,为 vacuum 分配多个槽位以更快完成。
当大表高度未排序时,深拷贝(deep copy)可能比 vacuum 更快,因为 deep copy 一次性处理整表,而 vacuum 按块处理。这意味着 deep copy 期间存储会暂时翻倍。
示例 5-13:深拷贝
CREATE TABLE new_tbl (LIKE my_tbl);
INSERT INTO new_tbl (SELECT * FROM my_tbl);
DROP TABLE my_tbl;
ALTER TABLE new_tbl RENAME TO my_tbl;
注意:INSERT 步时对 my_tbl 的数据变更不会反映到 deep copy 中。因此应在低活动窗口执行,或自行记录并补写变化。
自动 Vacuum Sort
auto vacuum sort 会按表的排序键保持数据有序。它同样要求≥50% 的 WLM 槽位空闲,满足时会从 WLM 借用 3 GB 内存,对数据块进行物理排序,并与 auto vacuum worker 协同工作。其会用机器学习分析查询模式来优先排序收益更高的块。若明确运行 vacuum sort 有益,Redshift Advisor 会给出建议(可结合上文示例 5-12 中的 vacuum_sort_benefit)。
自动 Analyze(Auto Analyze)
auto analyze 生成/更新统计信息元数据,以便优化器选择更优执行计划。当仓库空闲且超过 10 分钟无活动时运行,并且是增量工作的;用户手动执行 ANALYZE 时,已最新的表会自动跳过。
数仓表通常很多列用于 SELECT,但用于 JOIN/FILTER 的列相对较少。使用 ANALYZE ... PREDICATE COLUMNS 仅分析谓词列(如分布键、排序键、以及用于 JOIN/FILTER/GROUP BY 的列),能以最小代价收集对性能影响最大的列统计。若没有列标记为谓词列(例如表尚未被查询),则分析所有列——因此该选项默认很安全。
Redshift 会自动对通过 CREATE [TEMP] TABLE AS 或 SELECT INTO 创建的表执行 analyze。默认 analyze 门限是行更改 ≥10% 。你也可以在会话级调小/调大该门限。
当后续 ETL 步骤依赖最新统计信息,或仓库持续繁忙导致后台 auto analyze 无暇运行时,应将 ANALYZE 显式纳入 ETL。
自动物化视图(AutoMV)
前文已介绍 MV 的用例;AutoMV 会基于内置 ML 算法识别将受益于 MV 的查询模式,自动创建、刷新与删除 MV。终端查询无须改动,Redshift 会无缝从 MV 返回更快的结果。
Redshift 采用谓词上提(predicate elevation) 技术:把用户查询中的过滤列上提到 AutoMV 的 GROUP BY 中,从而在 MV 中存储更宽的值域,让相似查询也能复用同一个 MV。这特别适合仪表盘类负载:查询文本一致、仅过滤条件不同。
系统会用 AI 评估候选 MV 的收益与维护成本,并综合进行系统级优化。若已存在覆盖同范围的手工 MV,则不会再创建 AutoMV。手工 MV 的自动刷新优先级更高;且与高优先级队列相关的 AutoMV 会优先于低优先级队列的 AutoMV 创建。后台进程会监控 AutoMV 的使用/刷新频率;若发现不再被使用或无法刷新(如基表结构变化/查询模式改变),则会自动删除该 AutoMV。
AutoMV 的刷新在后台自动进行。若某次无法刷新,AutoMV 会被标记为陈旧(stale) ,并不再参与查询改写;待其刷新成功后,会重新启用。
Amazon Redshift Advisor
Redshift Advisor 好比一个7×24 小时的 DBA,持续监控你的工作负载,并给出具体的操作与配置建议,以提升吞吐与节约成本;同时对建议按性能影响进行排序。
建议基于你实际负载的性能统计与运维数据:系统会对可疑指标进行测试,若超出最佳区间,Advisor 会产出建议并提供可操作步骤(包括实现建议的 SQL,或列出受影响对象的查询)。仅当收益显著时才会显示建议;一旦你采纳并达标,相关建议会移除。
参阅 Amazon Redshift Advisor 最佳实践 获取使用指导。当前覆盖主题包括(节选):
- 压缩通过
COPY装载的 S3 对象 - 隔离多个活跃数据库
- 重新分配 WLM 内存
COPY时跳过压缩分析- 拆分通过
COPY装载的 S3 对象 - 更新表统计信息
- 启用短查询加速(SQA)
- 调整表的分布键/排序键
- 调整列的压缩编码
- 数据类型建议
每条建议会给出分析过程、分析时间范围,以及实施 SQL或列出受影响对象的 SQL。若没看到建议,并不代表当前设计就是最优:可能是数据不足或预期收益较小。
Advisor 的运行时机不可控:当仓库不忙、系统可借用空闲资源时才会启动。这与当仓库很忙时自动调优(如 auto vacuum、ATO、auto analyze)可能不运行的情况类似。自 2022 年起,Advisor 至少需要对某张表执行过 100 条查询才能给出建议;这一门槛会逐步降低,未来你会更尽早看到建议。
工作负载隔离(Workload Isolation)
工作负载是某个团队、部门或群组运行的一组查询。要在单个 Amazon Redshift 集群上同时支持多个工作负载,你需要为工作负载隔离进行设计。工作负载隔离的目标,是确保某个工作负载不会以影响其他工作负载执行的方式过度消耗资源。你已在前文看到,WLM 队列与相应的 QMR 能提供控制与隔离手段。在这些场景下,一个集群的资源被多个用户工作负载共享。这种设计可能带来冲突,尤其当每个工作负载的归属团队不同、却被迫共享一个集群资源时。你最多只能定义八个队列,对于复杂的多用户工作负载来说,八个队列可能不足以实现充分隔离。
借助 Data Sharing(数据共享) 功能,可以把不同工作负载拆分到各自的 Amazon Redshift 数据仓库中,以获得更强的隔离与更精细的资源分配控制。每个仓库都能独立设置自己的 WLM 队列,并进一步分配计算资源。你甚至可以根据工作负载时长,将预置与无服务器(serverless)数据仓库混用,并让多个消费者共享同一生产者提供的数据。关于如何用数据共享实现工作负载隔离,详见第 7 章。
追求最佳性价比的额外优化(Additional Optimizations for Best Price/Performance)
当今分析环境要求严苛,工作负载不断演进、数据量只增不减。前文“Autonomics”介绍了 Amazon Redshift 如何通过自动化简化许多任务。下面是一些帮助你在价格与性能之间取得最佳平衡的考量点。
数据库 vs. 数据仓库(Database Versus Data Warehouse)
结合数据量进行选择:若数据量仅为 GB 级,即使是分析型负载也可以评估 Amazon Aurora。Amazon Redshift 擅长处理海量数据,但对于小型负载成本相对更高。若查询时延要求宽松,可考虑把最终的转置分析结果下沉到 Amazon S3 数据湖,采用 Parquet 等开放格式,并用 Amazon Athena 进行查询。
Amazon Redshift Serverless
如果尚未使用 Redshift Serverless,它非常适合难以预估算力、工作负载波动大、周期性且存在空闲时段,或在稳定负载中偶有流量尖峰的场景。Serverless 会随并发用户数与新工作负载的到来自动扩缩。计费仅在用户查询访问用户数据时发生;对元数据或系统表的查询不计费。
初次分析新数据集时,Serverless 能快速、易上手,无需事先选择集群规格。其计算以 RPU(Redshift Processing Units) 计量,对应特定的处理器与内存配置。Serverless 提供默认 RPU,你可按需调整;系统会根据进入的工作负载自动扩缩 RPU。与其长期维护一套精心调参的预置集群,自动扩缩带来的收益往往更高。
多仓环境(Multi-Warehouse Environment)
与其维护一个“巨无霸”集群,不如把环境拆分为多个小型数据仓库。除了更好的工作负载隔离,还能实现预算/核算/归属的细粒度管理。各团队/部门/事业部可按自身诉求独立定尺与付费,同时仍可通过数据共享实现数据普惠。
Amazon Redshift 通过数据共享支持由预置与无服务器数据仓库组成的多仓架构;生产者与消费者都可以是预置或无服务器。再配合 AWS Lake Formation 对数据目录、访问与治理的整合,你的组织可以更轻松地在全局搭建数据网格(data mesh) (详见 AWS 博客)。
AWS Data Exchange(ADX)
AWS Data Exchange 让你更容易获取所需数据并实现统一计费。你可以在目录中按行业、供应商、交付方式筛选产品。ADX 提供免费与付费订阅,支持公开价格与私有议价;订阅到期后会自动撤销访问,保障供需双方的授权可控。你也可以将自己的 Redshift 数据通过 ADX Data Share 上架到 AWS Marketplace 实现数据变现。
若工作负载需要第三方数据,可利用 ADX 简化数据采购。数据可以以 S3 文件、Redshift datashare,或API 实时数据的形式交付。详见 AWS Data Exchange 文档。
表设计(Table Design)
从其他数据库迁移到 Redshift 时,请注意技术差异。
选择Key 分布(KEY distribution)时只能指定单列。若表间需要按多列连接,可拼接这些连接列生成新列作为分布键;若原列包含变长字符串,可先做哈希得到定长分布键。这样能确保连接共址、最小化跨节点数据移动、提升性能。
Redshift 在数据架构上已高度自动化,通常无需为表设计过度操心。但自动化依赖历史负载数据做推断,系统需要时间收敛。因此在初期仍应遵循表设计最佳实践,以尽早获得良好性能。
索引 vs. 区域映射(Indexes Versus Zone Maps)
Redshift 不提供索引或表分区。它使用 Zone Maps(每个数据块的最小/最大列值)来定位数据。Zone Maps 在数据有序时最有效。
- 小表:可不定义排序键。
- 大表且均匀分布:将最常用过滤列设为排序键。
- Key 分布时:可将连接列同时设为排序键与分布键,以便更快的排序归并连接。
驱动(Drivers)
始终使用 最新的 Redshift 驱动以获得新功能支持。若第三方软件自带 Redshift 驱动,优先使用其内置版本。仅当你的工具明确要求特定版本时,才回退到旧版驱动。
简化 ETL(Simplify ETL)
利用 Redshift Federated Query 直接联邦查询业务库或将其复制到仓库中,采用 ELT 模式。为减少网络数据移动并提升性能,Redshift 会将联邦查询的部分计算下推到远端业务数据库。
Amazon Redshift 集成 Apache Spark 让你在 Spark 中读写 Redshift,同时借助下推优化保证事务一致性与性能。
若需把 Amazon Aurora 的全量交易数据加载到 Redshift 进行分析,可利用 Aurora Zero-ETL 到 Redshift 的集成。它基于存储层复制,比导出/拷贝行更高效。
Query Editor V2
Amazon Redshift Query Editor V2 是浏览器内的全托管 SQL 编辑器。它会随用户群体增长自动扩展,无需安装桌面软件,并减少运行首条查询的步骤。它还通过保存查询与共享结果/分析增强团队协作能力。
在本章中,你学习了如何扩展 Amazon Redshift 数据仓库、如何设置工作负载管理(WLM) ,以及如何优化工作负载以获得最佳性价比。
查询调优(Query Tuning)
Amazon Redshift 使用结构化查询语言(SQL)与系统中的数据与对象交互。DML(数据操作语言)是 SQL 的子集,用于查看、添加、修改和删除数据;DDL(数据定义语言)是 SQL 的子集,用于添加、修改和删除数据库对象(如表与视图)。在进入“查询编写最佳实践”与“性能调优”之前,先了解 Redshift 是如何处理查询的。
查询处理(Query Processing)
所有提交到 Amazon Redshift 的查询都会先被解析,随后优化器生成查询执行计划(见图 5-6)。
每条查询的三个阶段:
- 查询规划(Query planning)
- 查询编译(Query compilation)
- 查询执行(Query execution)
图 5-6. Amazon Redshift 查询处理
查询规划与执行流程:
- Leader 节点接收并解析 SQL。若存在语法错误会返回;解析成功则生成该查询的逻辑表示(初始查询树)并将其发送给查询优化器。
- 查询优化器评估查询、分析表统计信息以决定连接次序与谓词选择性,并为提高效率而重写查询。优化器生成的查询计划描述了执行次序与将执行的网络操作,例如连接类型、连接顺序、聚合方式与数据分布要求。优化后的计划作为输入提交给执行引擎。
- 执行引擎首先检查编译缓存中是否有匹配的查询计划;若无,则把查询计划翻译成步骤(steps) 、分段(segments)与流(streams) 。执行引擎基于这些 steps/segments/streams 生成已编译的 C++ 代码(见图 5-7),将其加入缓存,并广播到计算节点(compute nodes) 。
图 5-7. Amazon Redshift 查询步骤
计算节点的 slice 并行执行查询分段。完成后,计算节点将结果返回给 Leader 节点进行最后处理;Leader 节点把来自各计算节点的结果合并为单一结果集,在需要时执行最终排序或聚合,然后把结果返回给提交查询的客户端。
查询阶段与系统表(Query stages and system tables)
下面分阶段说明细节与相关系统表:
阶段 1:解析与校验(Parsing and validation)
查询规划时先解析 SQL 并校验语法;若有效则生成查询树。此时会在 STV_RECENTS 表中写入一条记录。若 Redshift 识别到该查询可被结果缓存,会检查缓存并(若命中)直接由 Leader 节点把缓存结果返回客户端,后续阶段将被跳过。缓存相关信息可在 SVL_QLOG 表中查看。
阶段 2:请求锁(Query requests locks)
查询树随后进入加锁阶段。当前集群的锁状态可在 STV_LOCKS 表中查看。
阶段 3:规划器与优化器(Planner and optimizer)
成功获得锁后,优化器将查询重写为最适合 Redshift 的形式。本阶段输出为计划树(plan tree) ,与 PostgreSQL 兼容。此时计划会记录在 STL_EXPLAIN,查询文本记录在 STL_QUERYTEXT。
阶段 4:WLM 调度器(WLM scheduler)
计划生成成功后,查询进入 WLM 调度。查询等待队列槽位;当槽位可用时,将按队列分配进入执行。本阶段查询会在 STV_WLM_QUERY_STATE 中记录队列状态,也可查看 STV_WLM_QUERY_QUEUE_STATE。
阶段 5:代码生成/编译(Code generator / compiler)
获得执行槽位后,会在 STV_INFLIGHT 中写入记录。执行规划器将计划树转换为符合 Redshift 分布式架构的形式(划分为streams / segments / steps),随后分段进入编译;编译耗时与位置可查 SVL_COMPILE。
阶段 6:分发(Distribution)
Leader 节点将编译后的代码分发到计算节点。由于数据按 slices 分布,一个 stream 的所有 segments 会在所有 slices 上执行;待第一个 stream 的 segments 在所有 slices 上完成后,第二个 stream 才会继续执行。
阶段 7:查询执行(Query execution)
计算节点执行计算并处理查询。执行过程中可在 STV_EXEC_STATE(Running 状态)查看信息;执行结束后可查 SVL_QUERY_REPORT 与 SVL_QUERY_SUMMARY。
阶段 8:最终计算/聚合(Final computation / aggregation)
计算节点将结果发回 Leader 节点;Leader 节点执行最终计算并把结果返回客户端。此后查询信息可在 STL_QUERY 与 STL_WLM_QUERY 中查看。
阶段 9:提交队列(Commit queue,若需要)
需要提交(commit)的查询(如 DDL)会进入提交队列,完成后再返回结果。本信息记录于 STL_COMMIT_STATS。当所有计算节点完成提交后,Leader 节点把结果返回给客户端。
要获取查询执行运行时概览,请使用 SYS_QUERY_HISTORY。
已编译代码在会话间共享,因此后续相同查询(即便参数不同)会更快:每个 stream 的“计划编译”与“已编译代码执行”只进行一次。
理解查询计划(Understanding the query plan)
可用 EXPLAIN 查看执行计划(示例 5-14)。它展示执行引擎要做的操作。阅读顺序自下而上(最内层步骤到最外层)。你可以看到每一步用到了哪些表与列、处理了多少数据(行数与行宽(字节) ),以及每步的成本。注意:成本并非实际耗时或内存消耗,优化器只是在不同执行计划间比较该成本并选择最佳者;某步的成本可以指示最可能耗资源的操作。
示例 5-14. 查询执行计划
EXPLAIN
SELECT AVG(DATEDIFF(day, listtime, saletime)) AS avgwait
FROM sales, listing
WHERE sales.listid = listing.listid
;
QUERY PLAN
----------------------------------------------------------------
XN Aggregate (cost=6350.30..6350.31 rows=1 width=16)
-> XN Hash Join DS_DIST_NONE (cost=47.08..6340.89 rows=3766 width=16)
Hash Cond: ("outer".listid = "inner".listid)
-> XN Seq Scan on listing (cost=0.00..1924.97 rows=192497 width=12)
-> XN Hash (cost=37.66..37.66 rows=3766 width=12)
-> XN Seq Scan on sales (cost=0.00..37.66 rows=3766 width=12)
XN Seq Scan:按顺序扫描表的所选列,从头到尾,对每行评估WHERE谓词。
成本示例:(cost=0.00..37.66 rows=3766 width=12),前者为取首行成本,后者为取末行成本;同时给出估计行数与行宽。XN Hash:为连接中的内表创建哈希表。
XN Hash Join:用于内连接与左右外连接。算子读取外表、对连接列做哈希,并在内表哈希表中匹配。
例:XN Hash Join DS_DIST_NONE (cost=47.08..6340.89 rows=3766 width=16)中rows=3766为连接后估计行数。
若该值明显偏大,可能意味着统计信息缺失或缺少 PK/FK 约束,会影响规划器估行准确性。- 嵌套循环连接(nested loop join) :主要用于笛卡尔积与部分不等值连接,是 Redshift 中最不优的连接方式。
- 归并连接(merge join) :用于内连接与外连接(不用于全连接)。当连接列同时是分布键与排序键、且未排序行占比 < 20% 时,通常是最快的连接。
XN Aggregate:用于聚合函数与GROUP BY。示例 5-15 展示聚合算子的执行计划。
示例 5-15. 聚合算子的查询执行计划
EXPLAIN
SELECT eventname, sum(pricepaid) FROM sales, event
WHERE sales.eventid = event.eventid
GROUP BY eventname
ORDER BY 2 DESC;
QUERY PLAN
----------------------------------------------------------------
XN Merge (cost=1002815366604.92..1002815366606.36 rows=576 width=27)
Merge Key: sum(sales.pricepaid)
-> XN Network (cost=... rows=576 width=27) Send to leader
-> XN Sort (cost=... rows=576 width=27)
Sort Key: sum(sales.pricepaid)
-> XN HashAggregate (cost=... rows=576 width=27)
-> XN Hash Join DS_BCAST_INNER (cost=... rows=172456 width=27)
Hash Cond: ("outer".eventid = "inner".eventid)
-> XN Seq Scan on sales (cost=... rows=172456 width=14)
-> XN Hash (cost=... rows=8798 width=21)
-> XN Seq Scan on event (cost=... rows=8798 width=21)
图 5-8. Amazon Redshift 查询流(streams)
优化器按如下方式运行该计划:
- Stream 0:Segment 0 对
event表做顺序扫描;Segment 1 构建内表哈希表。 - Stream 1:Segment 2 扫描
sales;继续在 Segment 2 做哈希连接(连接列非同时为分布键与排序键的情形),再做哈希聚合;随后 Segment 3 做无序分组聚合与ORDER BY所需的排序。 - Stream 2:Segment 4 与 Segment 5 执行网络操作,将中间结果发送到 Leader 节点进一步处理。
查询的最后一个分段负责返回数据。若需要聚合或排序,各计算节点会把各自的中间结果发给 Leader 节点,由 Leader 合并后返回最终结果。
影响查询性能的因素(Factors affecting query performance)
决定查询执行快慢的关键方面包括:
- 节点数量(Number of nodes)
节点越多 ⇒ 处理器与 slices 越多 ⇒ 可把查询的不同部分并行到更多切片上,但需在性能与成本间权衡。 - 节点类型(Node types)
预置集群提供多种节点类型(规格与限制各异),决定存储容量、内存、CPU 与单价。 - 数据分布(Data distribution)
查询时优化器会在需要时重新分布数据以完成连接/聚合。为表选择合适的分布样式可最小化重分布,尽量让数据在连接之前就“在该在的位置”。 - 排序顺序(Sort order)
优化器与执行器会利用数据位置减少需扫描的数据块数量,从而提升速度。 - 数据量(Dataset volume)
数据量越大,需要扫描与重分布的行越多。定期 VACUUM、把不常查询的数据归档、以及用谓词收窄数据集都能提升性能。 - WLM 配置(WLM setup)
每个查询操作会占用队列中的一个或多个槽位以及相应内存。若同时有其他操作运行,可用槽位可能不足,查询就得等待。 - 代码编译(Code compilation)
Redshift 会为每个执行计划生成并编译代码,并缓存以供后续调用。首次编译会有开销;已编译段既缓存在本地集群,也缓存在一个几乎无限的缓存中,并在重启后依然存在。后续相同查询可跳过编译阶段,因此更快。
查询分析(Analyzing Queries)
当某条查询耗时超出预期时,你需要定位并纠正可能影响性能的问题。有时一条本应很快的查询被迫等待另一条长查询完成。在这种情况下,可为不同类型的查询划分并使用查询队列,以提升整体系统性能。
审阅查询告警(Reviewing query alerts)
使用系统表 STL_ALERT_EVENT_LOG 来识别并修复查询的潜在性能问题。该表既记录了潜在问题,也给出相应的修复建议。
分析查询计划(Analyzing the query plan)
使用 EXPLAIN 查看并分析执行计划,重点优化成本最高的步骤。关注连接方式:Merge 通常最佳;Hash 很常见,作为内表通常没问题;Nested Loop 应尽量避免(除非行数/循环次数很少)。
Redshift 会选择较小表作为内表、较大表作为外表;若非如此,可能是表统计信息未更新。
在连接步骤中,某个 slice 可能需要处理非本地数据;网络传输往往是查询中代价最高的操作。
表 5-2. 分布连接类型(Distribution joins)
| 连接类型 | 说明 |
|---|---|
| DS_DIST_NONE | 理想情形,参与连接的数据在同一 slice 共址;无需网络传输,效率最高。 |
| DS_DIST_ALL_NONE | 连接发生在 DISTSTYLE ALL 的表与另一表之间,无需网络传输。 |
| DS_DIST_ALL_INNER | 因内表为 DISTSTYLE ALL,被发送到单个节点上执行连接;通常较慢。 |
| DS_DIST_INNER / DS_DIST_OUTER | 外连接场景下指明哪张表被重新分布(内/外表)。若其中一表更小或很少更新,可考虑将其改为 DISTSTYLE ALL。 |
| DS_BCAST_INNER | 内表被广播到所有节点。 |
| DS_BCAST_BOTH | 两张表都被广播到所有节点;这是最差的情形。 |
识别需要调优的查询(Identifying Queries for Performance Tuning)
表 5-2 中前三种连接通常性能劣化较小;若你的高频或有业务 SLA的查询常落入后三种,它们就是优先调优对象。
以某交易公司的数仓为例,有两张主事实表:
fact_executions:成交事实fact_allocation:分配事实(见图 5-9,粗线表示因分布键匹配而共址连接)
你通常按**证券维度(security)分析成交、按账户维度(account)**分析分配。注意分配事实也会与证券维度相连(每笔分配对应某只证券)。
基于此设计:
- 多数成交报表与证券维度连接,共址,表现良好;
- 多数分配报表与账户维度连接,共址,表现良好;
- 若要按证券分析分配,因非共址会发生重分布而变慢。若此分析不常用,可容忍;若属关键业务且有严格 SLA,可把证券维度改为 DISTSTYLE ALL,使“分配按证券”和“成交按证券”都成为共址连接。为达性能目标,你可能需要额外存储成本。
并非所有查询都需要或可以被调快。应依据业务优先级权衡:调优所需时间与为达成性能而付出的成本。
另外,可参考如下方向确定优先级与改进点:
- Top candidates for tuning:找出最该调优的查询;
- 数据倾斜或未排序行的表:考虑更换分布策略与手动 VACUUM;
- Nested Loop 的查询:可能是写得不佳的 SQL,可在特定队列上配置 QMR;
- 队列等待时间:评估 WLM 调整与使用 Concurrency Scaling 减少等待;
- 查询告警:识别表级告警并按建议修复查询;
- 缺失统计信息的表:加入手动收集统计。
总结(Summary)
本章我们既讨论了如何针对可预测的需求进行扩展,也介绍了如何应对不可预测的突发性工作负载;讲解了如何设置工作负载管理(WLM)与查询监控规则(QMR) ,以尽量缩小“坏查询”的影响范围,保护你的 Amazon Redshift 数据仓库。我们还介绍了如何利用物化视图、Redshift 提供的各类自动化能力,以及如何通过数据共享(Data Sharing)实现工作负载隔离。最后,我们讨论了在价格—性能之间求取平衡的优化思路、查询的性能调优技术,以及如何识别并调优问题查询。
在下一章,我们将进入机器学习主题:描述数据科学家需要解决的不同问题类型及其常用算法;展示 Amazon Redshift 如何为数据科学家、数据工程师,甚至数据分析师提供工具,在 Redshift 上构建、训练并运行预测,从而让预测分析不再神秘。我们会演示用户如何直接用 SQL 命令跑通完整的机器学习生命周期,以及高级用户如何将 Amazon Redshift 与 Amazon SageMaker 集成。