降低 Redshift 查询成本

问题描述 投票:0回答:1

简介

我当前正在运行一个查询,据 DataEng 团队报告,该查询非常消耗资源。 我需要一些帮助来改进它/降低其成本。

案例

我有一个表格 (

status_history_table
),其中显示了我所有客户的状态以及该状态有效的日期范围。我需要一个查询,每天返回有多少客户已更改其状态以及有多少客户未更改状态。 只需要最近 365 天的数据。

status_history_table
示例:

用户ID 状态 有效来源 有效
X '活跃' 20240401 20240405
X “不活跃” 20240406 99991231

当我们将 valid_to 设置为“99991231”时,这意味着这是客户的当前状态,即,它一直有效,直到发生某些事情/改变它为止。

dim_date_table
是包含所有日期的维度表;

id_日期 日期_
20240401 2024-04-01
20240402 2024-04-02

这是我当前使用的查询:

with
    daily_status as (
        select
            h.user_id
            , d.id_date
            , h.status
        from
            status_history_table h
            inner join dim_date_table d
                on d.id_date between h.valid_from and h.valid_to 
                and d.date_ <= current_date
                and d.date_ > current_date - 365
    )

    , lag as (
        select 
            *
            , lag(status,1)over(partition by user_id order by id_date asc) as previous_status
        from
            daily_status
    )

    select 
        id_date
        case
            when previous_status = status_braze then 'No change'
            else 'Changed'
        end as class,
        count(1) as user_qty
    from 
        lag
    group by 
        1,2

我为此报告提取了更多信息,但此查询代表了正在使用的最重要的逻辑。 任何帮助将不胜感激。

更新1:

这是我正在使用的真实查询:

with
    base as (
        select 
            d.date_,
            h.subscription_status as status,
            braze_id
        from 
            curated_data.braze_subscription_history h
            inner join business_layer.dim_date d 
                on d.pk_date between h.valid_from and h.valid_to 
                and d.date_ <= current_date
                and d.date_ > current_date - 365
        where
            h.source = 'PUSH'
            and h.country_code = 'BR'
    ),

    -- ve qual foi o status anterior de cada cliente
    lag as (
        select 
            *,
            lag(status,1)over(partition by braze_id order by date_ asc) as previous_status
        from
            base
    )
    

    select 
        date_
        , case
            when previous_status = status then 'No change'
            else 'Changed'
        end as class,
        count(1) as user_qty
    from 
        lag
    group by 
        1,2

运行“解释”的结果

XN HashAggregate  (cost=1000679035081.81..1000679075081.81 rows=8000000 width=78)
  ->  XN Subquery Scan lag  (cost=1000627708182.17..1000668036460.46 rows=1466482847 width=78)
        ->  XN Window  (cost=1000627708182.17..1000649705424.87 rows=1466482847 width=45)
              Partition: h.braze_id
              Order: d.date_
              ->  XN Sort  (cost=1000627708182.17..1000631374389.29 rows=1466482847 width=45)
                    Sort Key: h.braze_id, d.date_
                    ->  XN Network  (cost=7525.05..404438272.74 rows=1466482847 width=45)
                          Distribute
                          ->  XN Nested Loop DS_BCAST_INNER  (cost=7525.05..404438272.74 rows=1466482847 width=45)
                                Join Filter: (("inner".pk_date <= ("outer".valid_to)::bigint) AND ("inner".pk_date >= ("outer".valid_from)::bigint))
                                ->  XN Seq Scan on braze_subscription_history h  (cost=0.00..1472107.32 rows=36159851 width=49)
                                      Filter: (((source)::text = 'PUSH'::text) AND ((country_code)::text = 'BR'::text))
                                ->  XN Materialize  (cost=7525.05..7528.70 rows=365 width=12)
                                      ->  XN Seq Scan on dim_date d  (cost=0.00..224.69 rows=365 width=12)
                                            Filter: ((date_ > '2023-04-09'::date) AND (date_ <= '2024-04-08'::date))
----- Nested Loop Join in the query plan - review the join predicates to avoid Cartesian products -----

DDLS:

CREATE TABLE IF NOT EXISTS curated_data.braze_subscription_history
(
    country_code VARCHAR(2)   ENCODE zstd
    ,braze_id VARCHAR(32)   ENCODE zstd
    ,external_id VARCHAR(32)   ENCODE zstd
    ,source VARCHAR(16)   ENCODE zstd
    ,subscription_status VARCHAR(16)   ENCODE zstd
    ,valid_from INTEGER   ENCODE az64
    ,valid_to INTEGER   ENCODE az64
)
DISTSTYLE EVEN


CREATE TABLE IF NOT EXISTS business_layer.dim_date
(
    pk_date BIGINT NOT NULL  ENCODE az64
    ,iso_date VARCHAR(40)   ENCODE zstd
    ,"year" VARCHAR(16)   ENCODE zstd
    ,quarter_of_year VARCHAR(8)   ENCODE zstd
    ,month_of_year VARCHAR(8)   ENCODE zstd
    ,date_ DATE   ENCODE az64
    ,year_month VARCHAR(6)   ENCODE zstd
    ,id_date BIGINT   ENCODE az64
)
DISTSTYLE ALL
sql postgresql amazon-redshift
1个回答
-2
投票

这是一个常见但棘手的问题。 JOIN ON 子句中的 BETWEEN 导致在循环中创建大量新行。这个过程非常昂贵,并且这个膨胀的数据集上的下游操作也会受到影响。此外,数据仓库针对数据分析进行了优化,而不是数据创建,因此这种类型的查询在 Redshift 上更加昂贵。

有一种方法可以解决这个问题,使其更加高效,但它需要重新思考如何执行分析,对于大多数分析(但不是全部)而言。性能的提升是惊人的,但解决它会让你的大脑受伤。我为一位客户执行了此操作,在 Redshift 上需要 90 分钟、在大型 EMR 集群上需要 45 分钟的查询被优化为 16 秒。所以这确实有效。

我在这里写了一篇关于进行此优化的教程 - https://wadevelopment.co/sql_limits_wp.html

基本思想是用 UNION ALL 替换 INNER JOIN,同时创建一些新列来检测状态的变化。然后使用滚动总和来获取每日统计数据。我在你的查询中看到的看起来应该是这样的。

我很高兴回答有关此问题的后续问题,但更改为这种方法是重写。

© www.soinside.com 2019 - 2024. All rights reserved.