Эффективный подсчет строк по дате, с учетом часового пояса - PullRequest
0 голосов
/ 31 октября 2018

У меня есть таблица со схемой, которая выглядит следующим образом:

  • 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 оптимизировать запрос.
  • Я мог бы заранее рассчитать каждый из диапазонов дат и, используя некоторую магию подзапроса, запросить количество событий для каждого диапазона. Я также не знаю, приведет ли это к какому-либо улучшению.

Прежде чем я спустился в кроличью нору, я решил протянуть руку, чтобы узнать, есть ли у кого-нибудь какие-нибудь мысли.

...