У меня есть таблица фактов t_session
, которая выглядит упрощенно так:
+------------+----------+-------------+
| start_hm | end_hm | device_id |
|------------+----------+-------------|
| 0 | 10 | 111 |
| 2 | 10 | 112 |
| 12 | 20 | 113 |
| 60 | 90 | 111 |
| 60 | 90 | 112 |
У меня также есть таблица измерений dim_time
, которая содержит 1440 записей с часами 0-23 и минутами 0-59за каждый час.Таким образом, он содержит все комбинации часов-минут за день.tk
это диапазон чисел 0-1439
+------+--------+----------+
| tk | hour | minute |
|------+--------+----------|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 2 | 0 | 2 |
............................
| 60 | 1 | 0 |
| 61 | 1 | 1 |
| 62 | 1 | 2 |
............................
| 120 | 2 | 0 |
| 121 | 2 | 1 |
| 122 | 2 | 2 |
............................
Я хочу посчитать количество активных device_id для каждой минуты.В реальном приложении есть еще одна таблица dim_date
и полдюжины других отношений, но давайте будем простыми в этом вопросе.
Устройство активно во временном интервале между start_hm
и end_hm
.И start_hm
, и end_hm
имеют значения от 0 до 1439.
select count(distinct device_id)
from t_session
join dim_time on tk between start_hm and end_hm
group by tk
order by tk;
Этот запрос выполняется медленно, как в аду.Когда я смотрю на план выполнения, он жалуется на вложенный цикл.
+--------------------------------------------------------------------------------------------------------------------------------+
| QUERY PLAN |
|--------------------------------------------------------------------------------------------------------------------------------|
| XN Limit (cost=1000002000820.94..1000002000820.97 rows=10 width=8) |
| -> XN Merge (cost=1000002000820.94..1000002000821.44 rows=200 width=8) |
| Merge Key: tk |
| -> XN Network (cost=1000002000820.94..1000002000821.44 rows=200 width=8) |
| Send to leader |
| -> XN Sort (cost=1000002000820.94..1000002000821.44 rows=200 width=8) |
| Sort Key: tk |
| -> XN HashAggregate (cost=2000812.80..2000813.30 rows=200 width=8) |
| -> XN Subquery Scan volt_dt_0 (cost=2000764.80..2000796.80 rows=3200 width=8) |
| -> XN HashAggregate (cost=2000764.80..2000764.80 rows=3200 width=8) |
| -> XN Nested Loop DS_BCAST_INNER (cost=0.00..2000748.80 rows=3200 width=8) |
| Join Filter: (("outer".tk <= "inner".end_hm) AND ("outer".tk >= "inner".start_hm)) |
| -> XN Seq Scan on dim_time (cost=0.00..28.80 rows=2880 width=4) |
| -> XN Seq Scan on t_session (cost=0.00..0.10 rows=10 width=12) |
| ----- Nested Loop Join in the query plan - review the join predicates to avoid Cartesian products ----- |
+--------------------------------------------------------------------------------------------------------------------------------+
Я понимаю, откуда берется вложенный цикл.Для каждой записи в t_session
необходимо выполнить цикл по dim_time
.
Можно ли изменить мой запрос, чтобы избежать вложенного цикла и повысить производительность?
ОБНОВЛЕНИЕ: тот же запросработает на Postgres невероятно быстро, и план выполнения не содержит декартово произведение.
+--------------------------------------------------------------------------------------------------------------+
| QUERY PLAN |
|--------------------------------------------------------------------------------------------------------------|
| Limit (cost=85822.07..85839.17 rows=10 width=12) |
| -> GroupAggregate (cost=85822.07..88284.47 rows=1440 width=12) |
| Group Key: dim_time.tk |
| -> Sort (cost=85822.07..86638.07 rows=326400 width=8) |
| Sort Key: dim_time.tk |
| -> Nested Loop (cost=0.00..51467.40 rows=326400 width=8) |
| Join Filter: ((dim_time.tk >= t_session.start_hm) AND (dim_time.tk <= t_session.end_hm)) |
| -> Seq Scan on t_session (cost=0.00..30.40 rows=2040 width=12) |
| -> Materialize (cost=0.00..32.60 rows=1440 width=4) |
| -> Seq Scan on dim_time (cost=0.00..25.40 rows=1440 width=4) |
+--------------------------------------------------------------------------------------------------------------+
UPDATE2:
Таблица t_session
имеет столбец device_id
в виде DISTKEY и столбец start_date
(не показано в упрощенном примере) как SORTKEY: сеансы естественным образом сортируются по start_date
.
Таблица dim_time
имеет tk
как SORTKEY и DISTSTYLE ALL.
Время выполнения 40000 сеансов в день составляет 5-6 минут в Redshift.И пару секунд на Postgres.
В кластере Redshift есть два узла dc2.large