Ускорение запроса по группе по дате на большой таблице в 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 ГБ и посмотреть, изменит ли это план. Если это не так, вы можете быть вне вариантов.