AWS Redshift UNLOAD未遵循ORDER BY排序问题排查与解决
我通过以下DDL创建了Redshift表(示例省略部分字段):
CREATE TABLE public.my_sorting_test ( timestamp_utc timestamp without time zone ENCODE raw, filtered boolean ENCODE raw, value character varying(256) ENCODE lzo, match_type character varying(256) ENCODE lzo ) BACKUP NO DISTSTYLE EVEN SORTKEY ( timestamp_utc );
随后使用以下语句执行UNLOAD操作:
unload ('select value as value_id, filtered, extract('epoch' from timestamp_utc)::bigint as "time" from (select value, coalesce(filtered, false) as filtered, timestamp_utc, match_type from my_sorting_test) where timestamp_utc >= '2017-01-01' and (match_type in ('a', 'b', 'c', 'd')) order by timestamp_utc asc') to 's3://my-stuff/summaries/' iam_role '<REDACTED>' delimiter ',' gzip header escape parallel off cleanpath partition by (filtered, value_id)
当我从s3://my-stuff/summaries/filtered=0/value_id=xyz/000.gz下载文件时,预期数据会按time列排序,但实际文件多数部分呈现为两个有序列表交织的状态,类似合并排序出错的结果:
$ aws s3 cp s3://my-stuff/summaries/filtered=1/value_id=xyz/000.gz --profile my-profile /dev/stdout --quiet | gunzip | head time 1659042813 1659589862 1659043229 1659590159 1659043236 1659591292 1659043431 1659593400 1659043756
其他文件片段:
1704843791 1705124901 1704844127 1705127776 1704844264 1705128555 1704844318 1705129255 1704844472 1705129351
1683216804 1683918134 1683216842 1683918171 1683216871 1683918222 1683216915 1683918256 1683216985 1683918386
我原本认为指定分区内的数据会按照SELECT语句中的ORDER BY排序,但实际查看filtered=1/value_id=xyz分区的文件时,数据并未按预期排序。请问这是什么原因?如何修改表结构和UNLOAD语句以实现预期的排序效果?
Redshift的UNLOAD操作在使用partition by时,排序逻辑会被分区处理干扰。即使在SELECT语句中指定了ORDER BY timestamp_utc,UNLOAD仍会先按分区键(filtered, value_id)分组,再在每个分区内部处理排序。加上表使用DISTSTYLE EVEN,数据会均匀分布在各个节点上,UNLOAD处理分区时,每个节点独立处理自身分区内的数据排序,之后将结果合并写入S3文件。由于节点间的数据是独立排序后合并的,就会出现多个有序子列表交织的情况,也就是你看到的类似合并排序出错的现象。
另外,虽然表的排序键是timestamp_utc,但DISTSTYLE EVEN导致数据并未按排序键分布,每个节点上的timestamp_utc数据分散,进一步加剧了合并后数据无序的问题。
1. 调整表的分布策略
将表的DISTSTYLE改为KEY,并使用分区相关字段作为分布键,确保同一分区的数据集中在同一个节点上:
ALTER TABLE public.my_sorting_test ALTER DISTSTYLE KEY ALTER DISTKEY (filtered, value);
这样同一filtered和value组合的数据会被分配到同一个节点,UNLOAD处理分区时,单个节点内的数据可以完整排序,避免多节点合并导致的无序。
2. 修改UNLOAD语句
- 确保在UNLOAD的查询中,先按分区键排序,再按时间排序:这样每个分区内部的数据会严格按
time列有序排列。 - 保留
parallel off参数,避免多文件并行写入的干扰(当前已设置该参数,无需调整)。
修改后的UNLOAD语句如下:
unload ('select value as value_id, filtered, extract('epoch' from timestamp_utc)::bigint as "time" from (select value, coalesce(filtered, false) as filtered, timestamp_utc, match_type from my_sorting_test) where timestamp_utc >= '2017-01-01' and (match_type in ('a', 'b', 'c', 'd')) order by filtered, value_id, timestamp_utc asc') -- 先按分区键排序,再按时间排序 to 's3://my-stuff/summaries/' iam_role '<REDACTED>' delimiter ',' gzip header escape parallel off cleanpath partition by (filtered, value_id)
3. 可选:重新加载数据(如果表已有数据)
如果表中已存在大量数据,修改分布键后需要重新加载数据,确保数据按新的分布键正确分配到各个节点:
-- 创建临时表 CREATE TABLE public.my_sorting_test_temp (LIKE public.my_sorting_test) BACKUP NO DISTSTYLE KEY DISTKEY (filtered, value) SORTKEY (timestamp_utc); -- 插入数据 INSERT INTO public.my_sorting_test_temp SELECT * FROM public.my_sorting_test; -- 替换原表 ALTER TABLE public.my_sorting_test RENAME TO public.my_sorting_test_old; ALTER TABLE public.my_sorting_test_temp RENAME TO public.my_sorting_test; -- 清理旧表(可选) DROP TABLE public.my_sorting_test_old;
内容的提问来源于stack exchange,提问作者PunDefeated

