У меня есть таблица со схемой, которая выглядит следующим образом:
- id (uuid; pk)
- отметка времени (timestamp)
- категория (бпчар)
- flaged_as_spam (bool)
- flagged_as_bot (bool)
- ... (другие метаданные)
У меня есть индекс для этой таблицы, который выглядит следующим образом:
CREATE INDEX time_index ON events_table USING btree (flagged_as_bot, flagged_as_spam, category, "timestamp") WHERE ((flagged_as_bot = false) AND (flagged_as_spam = false))
Я запускаю запросы к этой таблице, чтобы сгенерировать линейные диаграммы, представляющие количество событий, которые произошли каждый день. Однако я хочу, чтобы график был скорректирован с учетом часового пояса пользователя. В настоящее время у меня есть запрос, который выглядит так:
SELECT
date_trunc('day', timestamp + INTERVAL '-5 hour') AS ts,
category,
COUNT(*) AS count
FROM
events_table
WHERE
category = 'the category'
AND flagged_as_bot = FALSE
AND flagged_as_spam = FALSE
AND timestamp >= '2018-05-04T00:00:00'::timestamp
AND timestamp < '2018-10-31T17:57:59.661664'::timestamp
GROUP BY
ts,
category
ORDER BY
1 ASC
В большинстве случаев для категорий с количеством записей менее 100 000 это довольно быстро:
GroupAggregate (cost=8908.56..8958.18 rows=1985 width=70) (actual time=752.886..753.301 rows=124 loops=1)
Group Key: (date_trunc('day'::text, ("timestamp" + '-05:00:00'::interval))), category
-> Sort (cost=8908.56..8913.52 rows=1985 width=62) (actual time=752.878..752.983 rows=797 loops=1)
Sort Key: (date_trunc('day'::text, ("timestamp" + '-05:00:00'::interval)))
Sort Method: quicksort Memory: 137kB
-> Bitmap Heap Scan on listens (cost=552.79..8799.83 rows=1985 width=62) (actual time=748.683..752.568 rows=797 loops=1)
Recheck Cond: ((category = '7248c3b8-727e-4357-a267-e9b0e3e36d4b'::bpchar) AND ("timestamp" >= '2018-05-04 00:00:00'::timestamp without time zone) AND ("timestamp" < '2018-10-31 17:57:59.661664'::timestamp without time zone))
Filter: ((NOT flagged_as_bot) AND (NOT flagged_as_spam))
Rows Removed by Filter: 1576
Heap Blocks: exact=1906
-> Bitmap Index Scan on time_index (cost=0.00..552.30 rows=2150 width=0) (actual time=748.324..748.324 rows=2373 loops=1)
Index Cond: ((category = '7248c3b8-727e-4357-a267-e9b0e3e36d4b'::bpchar) AND ("timestamp" >= '2018-05-04 00:00:00'::timestamp without time zone) AND ("timestamp" < '2018-10-31 17:57:59.661664'::timestamp without time zone))
Planning time: 0.628 ms
Execution time: 753.362 ms"
Для категорий с очень большим количеством записей (> 100 000) индекс не используется и запрос выполняется очень медленно:
GroupAggregate (cost=1232229.95..1287491.60 rows=2126204 width=70) (actual time=14649.671..17178.955 rows=181 loops=1)
Group Key: (date_trunc('day'::text, ("timestamp" + '-05:00:00'::interval))), category
-> Sort (cost=1232229.95..1238072.10 rows=2336859 width=62) (actual time=14643.887..16031.031 rows=3070695 loops=1)
Sort Key: (date_trunc('day'::text, ("timestamp" + '-05:00:00'::interval)))
Sort Method: external merge Disk: 216200kB
-> Seq Scan on listens (cost=0.00..809314.38 rows=2336859 width=62) (actual time=0.015..9572.722 rows=3070695 loops=1)
Filter: ((NOT flagged_as_bot) AND (NOT flagged_as_spam) AND ("timestamp" >= '2018-05-04 00:00:00'::timestamp without time zone) AND ("timestamp" < '2018-10-31 17:57:59.661664'::timestamp without time zone) AND (category = '3b634b32-bb82-4f56-ada4-f4b7bc4288a5'::bpchar))
Rows Removed by Filter: 8788028
Planning time: 0.239 ms
Execution time: 17228.314 ms
Я предполагаю, что этот индекс не используется, потому что издержки на его использование намного выше, чем простое сканирование таблицы. И, конечно, я думаю, что это из-за использования date_trunc
для вычисления даты для группировки по.
Я подумал, что можно сделать здесь. Вот некоторые из моих мыслей:
- Проще всего, я мог бы создать индекс выражения для каждого смещения часового пояса, который мне небезразличен (обычно GMT / EST / CST / MST / PST). Это заняло бы много места, и каждый индекс использовался бы нечасто, но теоретически это позволило бы сканировать только по индексу.
- Я мог бы создать индекс выражения, усеченный до часа. Я не знаю, поможет ли это Postgres оптимизировать запрос.
- Я мог бы заранее рассчитать каждый из диапазонов дат и, используя некоторую магию подзапроса, запросить количество событий для каждого диапазона. Я также не знаю, приведет ли это к какому-либо улучшению.
Прежде чем я спустился в кроличью нору, я решил протянуть руку, чтобы узнать, есть ли у кого-нибудь какие-нибудь мысли.