Суть Читайте реальный SQL — настройку REFRESH CONCURRENTLY, создание immv через pg_ivm и проверку окна staleness — и выбирайте поведение или фикс с наибольшим рычагом, который сделает senior.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Сам оператор refresh и SQL вокруг него — там, где рождаются проблемы materialized view. Читайте каждый сниппет, предсказывайте, что сделает база, затем выбирайте фикс, к которому senior тянется первым.
Цель
Отработайте цикл, который вы запускаете при ревью MV: читайте DDL и путь refresh, предсказывайте поведение блокировок и staleness, и тянитесь к структурному фиксу раньше, чем к ручке тюнинга.
Сниппет 1 — предусловие CONCURRENTLY
CREATE MATERIALIZED VIEW daily_revenue ASSELECT date_trunc('day', created_at) AS day, region, sum(amount) AS revenueFROM ordersGROUP BY 1, 2;-- ночная задача:REFRESH MATERIALIZED VIEW CONCURRENTLY daily_revenue;
Викторина
Completed
Ночная задача сразу падает с ошибкой 'cannot refresh materialized view concurrently' / ошибкой уникального индекса. Чего не хватает и каким именно оно должно быть?
Heads-up Требуется уникальный индекс на самом materialized view, а не на базовой таблице. CONCURRENTLY использует уникальный индекс view, чтобы идентифицировать и обновлять изменённые строки при swap.
Heads-up Даже после первого обычного refresh CONCURRENTLY всё равно падает без уникального индекса на view. Жёсткое предусловие — уникальный индекс, а не предыдущий refresh.
Heads-up Требуемый для CONCURRENTLY уникальный индекс должен покрывать все строки, без WHERE и без выражений — частичный или индекс по выражению его не удовлетворяет.
Сниппет 2 — incremental maintenance через pg_ivm
CREATE EXTENSION IF NOT EXISTS pg_ivm;-- превращаем агрегат в инкрементально поддерживаемое представлениеSELECT create_immv('daily_revenue_ivm', $$ SELECT date_trunc('day', created_at) AS day, region, sum(amount) AS revenue FROM orders GROUP BY 1, 2 $$);
Викторина
Completed
После create_immv коллега замечает, что каждый INSERT в orders стал ощутимо медленнее. Это баг и что происходит?
Heads-up pg_ivm поддерживает view синхронно внутри пишущей транзакции by design; именно это даёт почти нулевой staleness. Замедление на каждой записи присуще механизму, а не ошибке конфигурации.
Heads-up Immv хранит результат как таблицу; чтения не перевыполняют GROUP BY. Добавленная стоимость — на пути записи, из триггеров поддержки.
Heads-up Добавление индексов на базовые таблицы может помочь поддержке, но структурное замедление — синхронная поддержка дельты на каждой записи. При высокой частоте записи эти издержки доминируют в любом случае.
Сниппет 3 — обнаружение staleness
-- ops хочет алерт, если MV давно не обновлялся.-- они запрашивают данные самого view на свежий день:SELECT max(day) AS newest_day FROM daily_revenue;-- алерт срабатывает, только если newest_day < current_date;
Викторина
Completed
Почему алерт на max(day) внутри view — ненадёжная проверка staleness, и что отслеживать вместо этого?
Heads-up Дело не в частоте. Сама метрика смешивает «есть свежие данные» и «недавно обновлялись» — они расходятся именно тогда, когда refresh молча падает, а это и есть случай, который важнее всего поймать.
Heads-up Число строк может не измениться при успешном refresh (те же группы, обновлённые суммы) и остаться замороженным при сбое refresh — то же смешение. Надёжный сигнал — успех и таймстамп самой refresh-задачи.
Heads-up CONCURRENTLY управляет refresh, а не чтениями; этот SELECT уже видит согласованный снапшот. Изъян — использование содержимого view как прокси свежести refresh.
Сниппет 4 — refresh, который не завершается
-- открыта долгая аналитическая сессия:BEGIN;SELECT ... FROM orders JOIN ... ; -- работает 20+ минут, всё ещё открыта-- тем временем срабатывает планировщик:REFRESH MATERIALIZED VIEW CONCURRENTLY daily_revenue; -- как будто висит
Викторина
Completed
Refresh CONCURRENTLY стоит и ничего не делает, пока открыта аналитическая сессия. Каков механизм и каков надёжный фикс?
Heads-up Это не deadlock — refresh ждёт завершения транзакций, нормальное предусловие для согласованного swap. Детектор взаимоблокировок не сработает; refresh просто ждёт.
Heads-up Обычный refresh берёт блокировку ACCESS EXCLUSIVE и полностью заблокировал бы читателей, что хуже для постоянно читаемого дашборда. Фикс — ограничить долгие транзакции, а не возвращать блокировку чтения.
Heads-up Одного уникального индекса уже достаточно; лишние индексы не меняют ожидание завершения транзакций. Стоп — про долгие открытые транзакции, а не про число индексов.
Итог
Путь refresh — там, где живут проблемы MV: CONCURRENTLY требует уникальный индекс на обычных колонках view, иначе падает; create_immv из pg_ivm переносит стоимость на каждую запись через триггеры, поэтому замедление записи — это дизайн, а не баг; staleness нужно измерять по успеху и таймстампу самой refresh-задачи, а не по содержимому данных view; а refresh CONCURRENTLY застревает за долгими открытыми транзакциями, поэтому ограничьте их, иначе он не догонит. Читайте DDL и refresh, предсказывайте поведение блокировок и staleness, затем чините структуру раньше, чем тянуться к ручке.