Обновление materalized представления постепенно в PostgreSQL

33

Можно ли постепенно обновлять материализованное представление в PostgreSQL, т.е. только для данных, которые являются новыми или изменились?

Рассмотрим эту таблицу и материализованное представление:

CREATE TABLE graph (
   xaxis integer NOT NULL,
   value integer NOT NULL,
);

CREATE MATERIALIZED VIEW graph_avg AS 
SELECT xaxis, AVG(value)
FROM graph
GROUP BY xaxis

Периодически добавляются новые значения graphили обновляется существующее значение. Я хочу обновлять представление graph_avgкаждые пару часов только для значений, которые были обновлены. Однако в PostgreSQL 9.3 вся таблица обновляется. Это довольно много времени. Следующая версия 9.4 позволяет CONCURRENTобновлять, но обновляет весь вид. С сотнями миллионов строк это занимает несколько минут.

Какой хороший способ отслеживать обновленные и новые значения и обновлять представление только частично?

user4150760
источник

Ответы:

22

Вы всегда можете реализовать свою собственную таблицу, выступающую в качестве «материализованного представления». Это то, что вы должны были сделать до того, как MATERIALIZED VIEWбыли реализованы в Postgres 9.3 в любом случае.

Например, вы можете создать простой VIEW:

CREATE VIEW graph_avg_view AS 
SELECT xaxis, AVG(value) AS avg_val
FROM   graph
GROUP  BY xaxis;

И материализуйте результат в целом один раз или всякий раз, когда вам нужно начать все сначала:

CREATE TABLE graph_avg AS
SELECT * FROM graph_avg_view

(Или используйте SELECTинструкцию напрямую, не создавая VIEW.)
Затем, в зависимости от нераскрытых деталей вашего варианта использования, вы можете DELETE/ UPDATE/ INSERTизменить вручную.

Основной оператор DML с КТРОМ данных изменений для вашей таблицы , как является :

Предполагая , что никто другой не пытается писать в graph_avgодновременно (чтение это не проблема):

WITH del AS (
   DELETE FROM graph_avg t
   WHERE  NOT EXISTS (SELECT 1 FROM graph_avg_view v WHERE v.xaxis = v.xaxis);
   )
, upd AS (
   UPDATE graph_avg t
   FROM   graph_avg_view v
   WHERE  t.xaxis = v.xaxis
   AND    t.avg_val <> v.avg_val
   )
INSERT INTO graph_avg t
SELECT *
FROM   graph_avg_view v
LEFT   JOIN graph_avg t USING (xaxis)
WHERE  t.xaxis IS NULL;

Но это, скорее всего, должно быть оптимизировано.

Основной рецепт:

  • Добавьте timestampстолбец по умолчанию now()в вашу базовую таблицу. Давайте назовем это ts.
    • Если у вас есть обновления, добавить триггер , чтобы установить текущую метку времени с каждым обновлением , которое изменяет либо xaxisили value.
  • Создайте крошечную таблицу, чтобы запомнить временную метку вашего последнего снимка. Давайте назовем это mv:

    CREATE TABLE mv (
       tbl text PRIMARY KEY
     , ts timestamp NOT NULL DEFAULT '-infinity'
    ); -- possibly more details
  • Создайте этот частичный многоколонный индекс:

    CREATE INDEX graph_mv_latest ON graph (xaxis, value)
    WHERE  ts >= '-infinity';
  • Используйте метку времени последнего снимка в качестве предиката в своих запросах, чтобы обновить снимок с идеальным использованием индекса.

  • В конце транзакции удалите индекс и заново создайте его с меткой времени транзакции, заменяющей метку времени в предикате индекса (изначально '-infinity'), который вы также сохраняете в своей таблице. Все в одной транзакции.

  • Обратите внимание, что частичный индекс отлично подходит для покрытия INSERTи UPDATEопераций, но нет DELETE. Чтобы покрыть это, вам нужно рассмотреть всю таблицу. Все зависит от точных требований.

Эрвин Брандштеттер
источник
Спасибо за ясность материализованных представлений и предложение альтернативного ответа.
user4150760
13

Параллельное обновление (Postgres 9.4)

Хотя Postgres 9.4 и не является инкрементным обновлением, как вы просили, он предоставляет новую функцию одновременного обновления .

Процитирую документ…

До PostgreSQL 9.4 обновление материализованного представления означало блокировку всей таблицы и, следовательно, предотвращение каких-либо запросов к ней, и, если обновление занимало много времени, чтобы получить эксклюзивную блокировку (в то время как она ожидает запросов, использующих ее для завершения), она, в свою очередь, выполняет свою функцию. задерживает последующие запросы. Теперь это можно исправить с помощью ключевого слова CONCURRENTLY:

 postgres=# REFRESH MATERIALIZED VIEW CONCURRENTLY mv_data;

Однако в материализованном представлении должен существовать уникальный индекс. Вместо того, чтобы блокировать материализованное представление вверх, он создает временную обновленную версию, сравнивает две версии, затем применяет INSERT и DELETE к материализованному представлению, чтобы применить разницу. Это означает, что запросы могут по-прежнему использовать материализованное представление во время его обновления. В отличие от своей несовпадающей формы, кортежи не заморожены, и для них требуется VACUUMing из-за вышеупомянутых DELETE, которые оставят мертвые кортежи позади.

Это параллельное обновление все еще выполняет полный новый запрос (не инкрементный). Таким образом, CONCURRENTLY не экономит общее время вычислений, а просто минимизирует время, в течение которого ваше материализованное представление недоступно для использования во время его обновления.

Базилик Бурк
источник
11
На мгновение я был взволнован, пока не прочитал внимательно. it instead creates a temporary updated version of it...compares the two versions- Это означает, что временно обновленная версия все еще полностью рассчитана, тогда она применяет разницу к существующему представлению. По сути, я все еще делаю ВСЕ вычисления, но только во временной таблице.
user4150760
5
Ах, да, CONCURRENTLYэто не экономит общее время вычислений, оно просто минимизирует время, в течение которого ваше материализованное представление недоступно для использования во время его обновления.
Василий Бурк