与内部表相比,外部 Parquet 表的 Snowflake 查询性能出乎意料地慢
Snowflake query performance is unexpectedly slower for external Parquet tables vs. internal tables
当我 运行 查询 Snowflake 中的外部 Parquet tables 时,查询比复制到 Snowflake 或任何其他云中的相同 tables 慢几个数量级我在相同文件上测试过的数据仓库。
上下文:
我有 tables 属于 GCS 上 Parquet 格式的 10TB TPC-DS 数据集和同一地区(美国中部)的 Snowflake 帐户。我已经使用 create as select 将这些 table 加载到 Snowflake 中。我可以 运行 TPC-DS queries(这里#28)在这些内部 tables 上具有出色的性能。我还能够直接使用具有出色性能的数据湖引擎在 GCS 上查询这些文件,因为这些文件的大小“最佳”并且在内部进行了排序。但是,当我在 Snowflake 上查询相同的外部 tables 时,查询似乎没有在合理的时间内完成(> 4 分钟并且还在计算,而不是在同一个虚拟仓库上的 30 秒)。查看查询配置文件,table 扫描中读取的记录数似乎在无限增长,导致按比例溢出到磁盘。
table 恰好是分区的,但那些对感兴趣的查询无关紧要(我用其他引擎测试过)。
我的期望:
假设正确的数据“格式化”,与内部 tables 相比,我预计不会出现重大性能下降,因为设置在技术上是相同的——数据以柱状格式存储在云对象存储中——并且因为它Snowflake 如此宣传。例如,我在完全相同的实验中发现 BigQuery 没有性能下降。
除了仔细检查我的设置外,我看不出有多少东西可以尝试...
This is what the "in progress" part of the plan looks like 4 minutes into execution on the external table. All other operators are at 0% progress. You can see external bytes scanned=bytes spilled and 26G!! rows are produced. And this 是在 ~20 秒内执行的内部 table 完成执行时的样子。您可以看到最左边的 table 扫描应该产生 1.4G 行,但使用外部 table.
产生了 23G 行
这是我使用的DDL的一个示例(我也测试过没有定义分区列):
create or replace external table tpc_db.tpc_ds.store_sales (
ss_sold_date_sk bigint as
cast(split_part(split_part(metadata$filename, '/', 4), '=', 2) as bigint)
,
ss_sold_time_sk bigint as (value:ss_sold_time_sk::bigint),
ss_item_sk bigint as (value:ss_item_sk::bigint),
ss_customer_sk bigint as (value:ss_customer_sk::bigint),
ss_cdemo_sk bigint as (value:ss_cdemo_sk::bigint),
ss_hdemo_sk bigint as (value:ss_hdemo_sk::bigint),
ss_addr_sk bigint as (value:ss_addr_sk::bigint),
ss_store_sk bigint as (value:ss_store_sk::bigint),
ss_promo_sk bigint as (value:ss_promo_sk::bigint),
ss_ticket_number bigint as (value:ss_ticket_number::bigint),
ss_quantity bigint as (value:ss_quantity::bigint),
ss_wholesale_cost double as (value:ss_wholesale_cost::double),
ss_list_price double as (value:ss_list_price::double),
ss_sales_price double as (value:ss_sales_price::double),
ss_ext_discount_amt double as (value:ss_ext_discount_amt::double),
ss_ext_sales_price double as (value:ss_ext_sales_price::double),
ss_ext_wholesale_cost double as (value:ss_ext_wholesale_cost::double),
ss_ext_list_price double as (value:ss_ext_list_price::double),
ss_ext_tax double as (value:ss_ext_tax::double),
ss_coupon_amt double as (value:ss_coupon_amt::double),
ss_net_paid double as (value:ss_net_paid::double),
ss_net_paid_inc_tax double as (value:ss_net_paid_inc_tax::double),
ss_net_profit double as (value:ss_net_profit::double)
)
partition by (ss_sold_date_sk)
with location = @tpc_ds/store_sales/
file_format = (type = parquet)
auto_refresh = false
pattern = '.*sales.*[.]parquet';
可能 Snowflake 计划假设它必须读取每个 实木复合地板文件,因为它无法事先判断文件是否已排序、唯一值的数量、空值、每列的最小值和最大值等
此信息作为可选字段存储在 Parquet 中,但您需要先阅读 parquet 元数据才能找到答案。
当 Snowflake 使用内部表时,它可以完全控制存储,拥有有关索引(如果有)、列统计信息以及如何从逻辑和物理角度优化查询的信息。
Snowflake原本没有外部文件查询,也没有Parquet支持。我觉得我记得当外部查询到达时,它是将所有文件 100% 读入系统并开始处理的简单操作。这与您所看到的一致。这是一件幸事,因为之前的状态是必须将所有文件加载到暂存 table,然后 运行 对其进行过滤器,有时如果它是一次性查询(但它几乎从不最后)对原始文件执行 SQL,很有帮助。
是的,应该可以优化 parquet 文件读取,收集元数据,然后消除浪费的分区读取。但这不是进化的顺序。所以我对你的发现并不感到惊讶。
我绝不会建议使用外部数据模型作为一般的日常 Snowflake 操作,因为它目前没有为此进行优化。由于两个原因,将其存储在雪花中的磁盘成本与将其存储在 S3 中的磁盘成本相同,并且雪花可以完全控制元数据,并在节点之间 read/write 同步。所有这些都相当于性能。
据说溢出到本地存储也不错,溢出到远程是最糟糕的溢出。但看起来您正在获得完整文件导入然后处理的有效结果。
我不知道你的查询看起来如何,但你也有很小的机会遇到一个已知问题,即 Snowflake 将分区过滤器中的函数解释为动态函数,从而运行所有数据,请参阅 Joao Marques blog on Medium: Using Snowflake External Tables? You must read this!.
来自博客的如何不这样做的示例
SELECT COUNT(*)
FROM EXTERNAL_TABLE
WHERE PARTITION_KEY = dateadd(day, -1, current_date)
来自博客的操作示例
SET my_var = (select dateadd(day, -1, current_date));
SELECT COUNT(*)
FROM EXTERNAL_TABLE
WHERE PARTITION_KEY = $my_var
感谢博客作者,我自己偶然发现了这个问题并找到了他的博客。
当我 运行 查询 Snowflake 中的外部 Parquet tables 时,查询比复制到 Snowflake 或任何其他云中的相同 tables 慢几个数量级我在相同文件上测试过的数据仓库。
上下文:
我有 tables 属于 GCS 上 Parquet 格式的 10TB TPC-DS 数据集和同一地区(美国中部)的 Snowflake 帐户。我已经使用 create as select 将这些 table 加载到 Snowflake 中。我可以 运行 TPC-DS queries(这里#28)在这些内部 tables 上具有出色的性能。我还能够直接使用具有出色性能的数据湖引擎在 GCS 上查询这些文件,因为这些文件的大小“最佳”并且在内部进行了排序。但是,当我在 Snowflake 上查询相同的外部 tables 时,查询似乎没有在合理的时间内完成(> 4 分钟并且还在计算,而不是在同一个虚拟仓库上的 30 秒)。查看查询配置文件,table 扫描中读取的记录数似乎在无限增长,导致按比例溢出到磁盘。
table 恰好是分区的,但那些对感兴趣的查询无关紧要(我用其他引擎测试过)。
我的期望:
假设正确的数据“格式化”,与内部 tables 相比,我预计不会出现重大性能下降,因为设置在技术上是相同的——数据以柱状格式存储在云对象存储中——并且因为它Snowflake 如此宣传。例如,我在完全相同的实验中发现 BigQuery 没有性能下降。
除了仔细检查我的设置外,我看不出有多少东西可以尝试...
This is what the "in progress" part of the plan looks like 4 minutes into execution on the external table. All other operators are at 0% progress. You can see external bytes scanned=bytes spilled and 26G!! rows are produced. And this 是在 ~20 秒内执行的内部 table 完成执行时的样子。您可以看到最左边的 table 扫描应该产生 1.4G 行,但使用外部 table.
产生了 23G 行这是我使用的DDL的一个示例(我也测试过没有定义分区列):
create or replace external table tpc_db.tpc_ds.store_sales (
ss_sold_date_sk bigint as
cast(split_part(split_part(metadata$filename, '/', 4), '=', 2) as bigint)
,
ss_sold_time_sk bigint as (value:ss_sold_time_sk::bigint),
ss_item_sk bigint as (value:ss_item_sk::bigint),
ss_customer_sk bigint as (value:ss_customer_sk::bigint),
ss_cdemo_sk bigint as (value:ss_cdemo_sk::bigint),
ss_hdemo_sk bigint as (value:ss_hdemo_sk::bigint),
ss_addr_sk bigint as (value:ss_addr_sk::bigint),
ss_store_sk bigint as (value:ss_store_sk::bigint),
ss_promo_sk bigint as (value:ss_promo_sk::bigint),
ss_ticket_number bigint as (value:ss_ticket_number::bigint),
ss_quantity bigint as (value:ss_quantity::bigint),
ss_wholesale_cost double as (value:ss_wholesale_cost::double),
ss_list_price double as (value:ss_list_price::double),
ss_sales_price double as (value:ss_sales_price::double),
ss_ext_discount_amt double as (value:ss_ext_discount_amt::double),
ss_ext_sales_price double as (value:ss_ext_sales_price::double),
ss_ext_wholesale_cost double as (value:ss_ext_wholesale_cost::double),
ss_ext_list_price double as (value:ss_ext_list_price::double),
ss_ext_tax double as (value:ss_ext_tax::double),
ss_coupon_amt double as (value:ss_coupon_amt::double),
ss_net_paid double as (value:ss_net_paid::double),
ss_net_paid_inc_tax double as (value:ss_net_paid_inc_tax::double),
ss_net_profit double as (value:ss_net_profit::double)
)
partition by (ss_sold_date_sk)
with location = @tpc_ds/store_sales/
file_format = (type = parquet)
auto_refresh = false
pattern = '.*sales.*[.]parquet';
可能 Snowflake 计划假设它必须读取每个 实木复合地板文件,因为它无法事先判断文件是否已排序、唯一值的数量、空值、每列的最小值和最大值等
此信息作为可选字段存储在 Parquet 中,但您需要先阅读 parquet 元数据才能找到答案。
当 Snowflake 使用内部表时,它可以完全控制存储,拥有有关索引(如果有)、列统计信息以及如何从逻辑和物理角度优化查询的信息。
Snowflake原本没有外部文件查询,也没有Parquet支持。我觉得我记得当外部查询到达时,它是将所有文件 100% 读入系统并开始处理的简单操作。这与您所看到的一致。这是一件幸事,因为之前的状态是必须将所有文件加载到暂存 table,然后 运行 对其进行过滤器,有时如果它是一次性查询(但它几乎从不最后)对原始文件执行 SQL,很有帮助。
是的,应该可以优化 parquet 文件读取,收集元数据,然后消除浪费的分区读取。但这不是进化的顺序。所以我对你的发现并不感到惊讶。
我绝不会建议使用外部数据模型作为一般的日常 Snowflake 操作,因为它目前没有为此进行优化。由于两个原因,将其存储在雪花中的磁盘成本与将其存储在 S3 中的磁盘成本相同,并且雪花可以完全控制元数据,并在节点之间 read/write 同步。所有这些都相当于性能。
据说溢出到本地存储也不错,溢出到远程是最糟糕的溢出。但看起来您正在获得完整文件导入然后处理的有效结果。
我不知道你的查询看起来如何,但你也有很小的机会遇到一个已知问题,即 Snowflake 将分区过滤器中的函数解释为动态函数,从而运行所有数据,请参阅 Joao Marques blog on Medium: Using Snowflake External Tables? You must read this!.
来自博客的如何不这样做的示例
SELECT COUNT(*)
FROM EXTERNAL_TABLE
WHERE PARTITION_KEY = dateadd(day, -1, current_date)
来自博客的操作示例
SET my_var = (select dateadd(day, -1, current_date));
SELECT COUNT(*)
FROM EXTERNAL_TABLE
WHERE PARTITION_KEY = $my_var
感谢博客作者,我自己偶然发现了这个问题并找到了他的博客。