Ускорение запроса по группе по дате на большой таблице в postgres

У меня есть таблица с 20 миллионами строк. Для аргументов, скажем, в таблице есть два столбца - идентификатор и временная метка. Я пытаюсь подсчитать количество предметов в день. Вот то, что у меня есть на данный момент.

  SELECT DATE(timestamp) AS day, COUNT(*)
    FROM actions
   WHERE DATE(timestamp) >= '20100101'
     AND DATE(timestamp) <  '20110101'
GROUP BY day;

Без каких-либо индексов для работы на моей машине требуется около 30 секунд. Здесь объясните результат анализа:

 GroupAggregate  (cost=675462.78..676813.42 rows=46532 width=8) (actual time=24467.404..32417.643 rows=346 loops=1)
   ->  Sort  (cost=675462.78..675680.34 rows=87021 width=8) (actual time=24466.730..29071.438 rows=17321121 loops=1)
         Sort Key: (date("timestamp"))
         Sort Method:  external merge  Disk: 372496kB
         ->  Seq Scan on actions  (cost=0.00..667133.11 rows=87021 width=8) (actual time=1.981..12368.186 rows=17321121 loops=1)
               Filter: ((date("timestamp") >= '2010-01-01'::date) AND (date("timestamp") < '2011-01-01'::date))
 Total runtime: 32447.762 ms

Так как я вижу последовательное сканирование, я пытался индексировать в агрегате даты

CREATE INDEX ON actions (DATE(timestamp));

Которая сокращает скорость примерно на 50%.

 HashAggregate  (cost=796710.64..796716.19 rows=370 width=8) (actual time=17038.503..17038.590 rows=346 loops=1)
   ->  Seq Scan on actions  (cost=0.00..710202.27 rows=17301674 width=8) (actual time=1.745..12080.877 rows=17321121 loops=1)
         Filter: ((date("timestamp") >= '2010-01-01'::date) AND (date("timestamp") < '2011-01-01'::date))
 Total runtime: 17038.663 ms

Я новичок в этом бизнесе оптимизации запросов, и я понятия не имею, что делать дальше. Любые подсказки, как я мог быстрее запустить этот запрос?

- изменить -

Похоже, я нажимаю пределы индексов. Это почти единственный запрос, который запускается в этой таблице (хотя значения дат меняются). Есть ли способ разбить таблицу? Или создать таблицу кэша со всеми значениями счетчика? Или любые другие варианты?

Ответ 1

Есть ли способ разбить таблицу?

Да:
http://www.postgresql.org/docs/current/static/ddl-partitioning.html

Или создать таблицу кэша со всеми значениями счетчика? Или любые другие варианты?

Создание таблицы "кеш", безусловно, возможно. Но это зависит от того, насколько часто вам нужен этот результат и насколько он точен.

CREATE TABLE action_report
AS
SELECT DATE(timestamp) AS day, COUNT(*)
    FROM actions
   WHERE DATE(timestamp) >= '20100101'
     AND DATE(timestamp) <  '20110101'
GROUP BY day;

Тогда a SELECT * FROM action_report даст вам то, что вы хотите своевременно. Затем вы планируете задание cron для повторного создания этой таблицы на регулярной основе.

Этот подход, конечно, не поможет, если временной диапазон изменяется с каждым запросом или если этот запрос выполняется только один раз в день.

Ответ 2

В большинстве случаев большинство баз данных будут игнорировать индексы, если ожидаемое количество возвращаемых строк будет высоким. Это связано с тем, что для каждого попадания индекса ему необходимо будет также найти строку, поэтому быстрее выполнить полное сканирование таблицы. Это число составляет от 10 000 до 100 000 человек. Вы можете поэкспериментировать с этим, сокращая диапазон дат и видя, где postgres переворачивается с использованием индекса. В этом случае postgres планирует сканировать 17 3101 674 строки, поэтому ваш стол довольно большой. Если вы сделаете его действительно маленьким, и вы по-прежнему чувствуете, что postgres делает неправильный выбор, попробуйте запустить анализ на столе, чтобы postgres правильно его приближения.

Ответ 3

Похоже, что диапазон охватывает всего около всех доступных данных.

Это может быть проблема дизайна. Если вы будете использовать это часто, вам лучше создать дополнительный столбец timestamp_date, который содержит только дату. Затем создайте индекс в этом столбце и соответствующим образом измените запрос. Столбец должен поддерживаться триггерами insert + update.

SELECT timestamp_date AS day, COUNT(*)
FROM actions
WHERE timestamp_date >= '20100101'
  AND timestamp_date <  '20110101'
GROUP BY day;

Если я ошибаюсь в отношении количества строк, которые найдут диапазон дат (и это всего лишь небольшое подмножество), вы можете попробовать индекс только для самого столбца временной метки, применяя предложение WHERE к просто столбцу (который учитывая, что диапазон работает так же хорошо)

SELECT DATE(timestamp) AS day, COUNT(*)
FROM actions
WHERE timestamp >= '20100101'
  AND timestamp <  '20110101'
GROUP BY day;

Ответ 4

Попробуйте запустить explain analyze verbose ..., чтобы узнать, использует ли агрегат временный файл. Возможно, увеличьте work_mem, чтобы сделать еще больше в памяти?

Ответ 5

То, что вы действительно хотите для таких запросов типа DSS, - это таблица дат, описывающая дни. В дизайне Lingo для базы данных он называется измерением даты. Чтобы заполнить такую ​​таблицу, вы можете использовать код, который я опубликовал в этой статье: http://www.mockbites.com/articles/tech/data_mart_temporal

Затем в каждой строке вашей таблицы действий установите соответствующую дату_key.

Затем ваш запрос будет выглядеть следующим образом:

SELECT
   d.full_date, COUNT(*)
FROM actions a 
JOIN date_dimension d 
    ON a.date_key = d.date_key
WHERE d.full_date = '2010/01/01'
GROUP BY d.full_date

Предполагая индексы на ключах и full_date, это будет очень быстро, потому что он работает с ключами INT4!

Другим преимуществом является то, что теперь вы можете нарезать и нарезать кубиками любые другие столбцы date_dimension.

Ответ 6

Установите work_mem, чтобы сказать 2 ГБ и посмотреть, изменит ли это план. Если это не так, вы можете быть вне вариантов.