itmo_conspects

Лекция 13. Тестирование миграций

Презентацию можно посмотреть тут - slides.pdf

Типичный пайплайн в компаниях для разработчиков миграций состоит из:

  1. разработки миграции
  2. ее ревьювят другие разработчики
  3. она деплоится на продакшен-базу

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

Тогда появляется потребность эти миграции тестировать

  1. Простой накат

Самый простой тест - сделать миграцию up, потом откатную миграцию down и надеяться на ошибку от интерпретатора СУБД. Но, если в таком примере миграции

-- up
ALTER TYPE mood ADD VALUE 'happy' AFTER 'ok';

-- down

сделать миграцию up, миграцию down (она пуста, так как PostgreSQL не поддерживает удаление значения из перечисления), а потом снова up - то мы получим ошибку, так как такое значение уже есть

Поэтому можно сделать два вывода:

  1. Staircase-тест

Второй способ - Staircase-тест:

Если миграций четыре, то база данных пройдет через миграции так:

alt text

Такой подход проверяет идемпотентность, но не приведение к инварианту. Пример:

-- up
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1
        FROM pg_enum
        WHERE enumlabel = 'happy'
          AND enumtypid = 'mood'::regtype
    ) THEN
        ALTER TYPE mood ADD VALUE 'happy' AFTER 'ok';
    END IF;
END;
$$;

-- down

Такая миграция при откате не удалит 'happy' из перечисления

Поэтому стоит проверять сами состояния базы данных. Для этого при первом накате будем делать снапшоты схем базы данных снапшоты схем. Снапшоты схем можно делать при помощи специальных служебных таблиц, которые есть в каждой базе данных

Если снапшоты после up-down равны, то инвариант соблюдается и миграция работает. Для проведения Staircase-теста можно воспользоваться утилитой seqwall

  1. Проверка снапшотов

Пока что все тесты миграции мы проводили со схемой базы данных, но не с данными. Взглянем на этот пример:

-- up
ALTER TABLE users
  ALTER COLUMN updated_at TYPE timestamp
  USING updated_at::timestamp;

-- down
ALTER TABLE users
  ALTER COLUMN updated_at TYPE text
  USING updated_at::text;

Здесь текст, содержащий дату и время, кастится к встроенному типу timestamp. Однако, если в базе дата и время записывается как 2025-03-14 12:00:00+03, то при касте информация о часовом поясе +03 теряется, поэтому корректнее использовать каст к timestamptz

Чтобы выявлять подобные ошибки, надо наполнить тестовую базу данных какими-то данными. Можно:

Поэтому генерация используется, если:

А сэмплы, когда:

Теперь мы можем разделить миграции на:

Теперь перед каждой up-миграцией можем сделать снапшот данных (например, с помощью pg_dump), затем после up-down сравнивать снапшот. Таким образом,

  1. Проверка блокировок

Теперь заметим другое: каст делается для всех значений атрибута, поэтому нужна глобальная блокировка таблицы (Access Exclusive Lock). В продакшене такая миграция вызовет задержку всех запросов. Однако это можно отследить: все блокировки записываются в таблицу pg_locks, а из таблицы pg_stat_activity можно получить дополнительную информацию

Также блокировки можно отлавливать при помощи утилиты squawk

При написании миграций рекомендуется использовать линтеры (например, SQLFluff или Sqruff) и форматтеры


Резюме:

  1. Пишем интеграционные тесты
  2. Включаем статический анализ или линтеры для SQL-кода
  3. Тестируем изменения схемы, блокировки
  4. Тестируем на данных
  5. Тестируем многофазные миграции, отслеживаем упадки производительности и так далее