pgtestdb: клонирование шаблонов PostgreSQL для тестов проигрывает переиспользованию схем

Автор блога brandur, разработчик Go-библиотеки очередей задач River, решил проверить пакет pgtestdb Питера Даунса (Peter Downs), библиотеку для тестирования Go/PostgreSQL, построенную на встроенной функции PostgreSQL «шаблонные базы данных» (CREATE DATABASE dbname TEMPLATE template_to_copy). Копирование шаблона происходит на низком уровне: PostgreSQL перечисляет отношения шаблона и копирует их файлы кучи, индексов и каталога блоками по 8 килобайт, поэтому оно заметно быстрее, чем прогон миграций с нуля или тяжёлые Docker-контейнеры, которые используют некоторые проекты.

Чтобы сравнить подходы, автор попросил ИИ-инструмент Codex встроить pgtestdb в тестовый набор River. У River уже есть собственный механизм изоляции тестов, на основе схем PostgreSQL, а не отдельных баз данных: схема в PostgreSQL легче базы, но её нельзя клонировать, поэтому схемный подход вынужден каждый раз прогонять миграции заново, здесь, по словам автора, у pgtestdb есть явное преимущество.

Результат сравнения оказался неожиданным. Время подготовки (setup) у обоих подходов почти одинаковое, около 100 миллисекунд. Но при прогоне полного тестового набора схемный метод River работает примерно в 3,5 раза быстрее, чем pgtestdb. Причина не в самих схемах, а в оптимизации: тестовые хелперы River создают ровно столько схем, сколько нужно для параллельных горутин Go, и держат их в пуле, если свободная схема готова, тест очищает и переиспользует её вместо создания новой; исключение, упавшие тесты, их схема не переиспользуется, чтобы состояние осталось доступным для отладки. Логика учитывает версию схемы, чтобы тест не переиспользовал схему от другой миграции; по словам автора, этот код написан «до эры LLM» и потребовал нескольких дней на отладку.

Автор решил оставить River на существующем схемном методе, он уже быстрый, а изоляция по схемам заодно проверяет, что конфигурация самого River работает как задумано, но добавит в документацию рекомендацию использовать pgtestdb, особенно для end-to-end тестов (когда задача создаётся клиентом и должна быть полностью обработана воркером). Автор также отмечает, что переиспользование можно реализовать и для pgtestdb, как часть самого пакета или как надстройку в проектах, которые его используют: 100 мс на подготовку базы, это быстро, но для приложения с 10 000 тестов в идеале нужно ускорение на порядок; переиспользование доводит время до 10, 20 мс, приближая его к скорости тестовых транзакций.

Ключевые факты

  • Пакет pgtestdb (автор, Питер Даунс) клонирует шаблонные базы PostgreSQL для тестов; автор River встроил его в свой тестовый набор с помощью Codex
  • Время подготовки теста одинаковое у обоих подходов, около 100 мс
  • Но полный набор тестов на схемной изоляции River проходит примерно в 3,5 раза быстрее, чем на pgtestdb
  • Причина, не скорость клонирования, а пул переиспользуемых схем: тест очищает и переиспользует готовую схему вместо создания новой (кроме упавших тестов, их схему сохраняют для отладки)
  • С аналогичным переиспользованием время подготовки можно снизить с ~100 мс до 10, 20 мс, для приложения с 10 000 тестов автор считает желательным ускорение на порядок

Почему это важно

В экосистеме Go давно ищут баланс между скоростью тестов и надёжностью изоляции: тесты с настоящей PostgreSQL обычно самые надёжные, но и самые медленные, особенно если для каждого теста поднимать Docker-контейнер или гонять миграции с нуля. Сравнение brandur даёт конкретные цифры для двух реальных подходов, клонирования шаблонных баз (pgtestdb) и изоляции по схемам (метод River), и показывает нетривиальный итог: то, что быстрее «в моменте» (клонирование шаблона за ~100 мс), не обязательно выигрывает на полном тестовом наборе, если у альтернативы есть оптимизация переиспользования.

Кому это важно

Разработчикам на Go и PostgreSQL, которые пишут интеграционные тесты и выбирают между Docker-контейнерами, миграциями с нуля, клонированием шаблонных баз (pgtestdb) и изоляцией по схемам, особенно авторам библиотек и приложений с большими тестовыми наборами (в примере автора, гипотетические 10 000 тестов), где счёт идёт на миллисекунды подготовки каждого теста.

Как это применить

Автор рекомендует pgtestdb в первую очередь для end-to-end тестов, когда задача создаётся клиентом и должна быть полностью обработана воркером, то есть там, где нужна отдельная полноценная база, а не просто изолированная схема. Если тестов много и важна суммарная скорость прогона, стоит добавить пул переиспользуемых схем или баз (как в River): не создавать новую копию на каждый тест, а очищать и переиспользовать уже готовую, с учётом версии миграции. По расчётам автора, такое переиспользование сокращает время подготовки с ~100 мс до 10, 20 мс, почти до уровня тестовых транзакций.

Можно ли доверять

Это личный технический пост разработчика River (brandur) в его собственном блоге, с открытым описанием методики, какие два подхода сравнивались и почему, и цифрами прямо из его тестового прогона; источник первичный, обсуждение на Hacker News подтверждает интерес сообщества (27 баллов, 8 комментариев). Ограничение: сравнение сделано на одном конкретном проекте (River) и его конфигурации, а не как независимый бенчмарк, поэтому конкретные цифры (100 мс, 3,5 раза, 10, 20 мс) не обязательно повторятся в других кодовых базах.

Риски и подводные камни

Автор прямо предупреждает: логика переиспользования схем, не просто «включить и работает». Нужно учитывать версию схемы, чтобы тест не переиспользовал схему, оставшуюся от другой миграции, а на отладку этой логики у него ушло несколько дней (этот код написан до появления LLM-инструментов). Схема упавшего теста намеренно не переиспользуется, чтобы её состояние осталось доступным для отладки, это осознанный компромисс, а не побочный эффект. Сам автор признаёт, что немного лукавил раньше в тексте: время подготовки у обоих подходов похоже, но итоговую скорость всего набора определяет не факт клонирования шаблона, а именно оптимизация переиспользования.