pgdbtemplate — моментальное создание тестовых баз PostgreSQL в Go через шаблоны. Ускоряем тесты в 1.5 раза

от автора

Всем привет! Меня зовут Андрей, я Go-разработчик. Сегодня хочу поделиться библиотекой, которая родилась из внутренней боли и желания оптимизировать рабочий процесс.

Проблема: «Ну сколько можно ждать?»

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

func TestMyService(t *testing.T) {    // 1. Создать новую БД (CREATE DATABASE)    // 2. Применить все миграции (N запросов CREATE TABLE, INDEX, FK...)    // 3. Запустить сам тест    // 4. Удалить БД (DROP DATABASE)    // ... и так для КАЖДОГО теста.}

Шаги 1 и 2 повторяются каждый раз, съедая кучу времени. Чем сложнее ваша схема (таблицы, индексы, внешние ключи), тем дольше длится этот процесс.

Решение: Шаблоны (Templates) PostgreSQL

В PostgreSQL есть мощная, но не всегда очевидная фича — шаблонные базы данных (Template Databases). Вы можете создать одну «шаблонную» базу, применить все миграции единожды и сделать ее шаблоном. Все последующие базы создаются командой:

CREATE DATABASE my_fast_test_db TEMPLATE my_template_db;

Эта операция копирует данные на уровне файловой системы и занимает мгновение, независимо от сложности схемы.

Моя библиотека pgdbtemplate автоматизирует всю эту магию, предоставляя простой и удобный API для ваших тестов.

Начинаем работать за 5 минут

Установка стандартная:

go get github.com/andrei-polukhin/pgdbtemplate

А вот так это выглядит в коде ваших тестов:

package mainimport ("context""fmt""log""github.com/andrei-polukhin/pgdbtemplate""github.com/andrei-polukhin/pgdbtemplate-pgx")func main() {// Create a connection provider with pooling options.connStringFunc := func(dbName string) string {return fmt.Sprintf("postgres://user:pass@localhost/%s", dbName)}provider := pgdbtemplatepgx.NewConnectionProvider(connStringFunc)// Create migration runner.migrationRunner := pgdbtemplate.NewFileMigrationRunner([]string{"./migrations"}, pgdbtemplate.AlphabeticalMigrationFilesSorting,)// Create template manager.config := pgdbtemplate.Config{ConnectionProvider: provider,MigrationRunner:    migrationRunner,}tm, err := pgdbtemplate.NewTemplateManager(config)if err != nil {log.Fatal(err)}// Initialize template with migrations.ctx := context.Background()if err := tm.Initialize(ctx); err != nil {log.Fatal(err)}// Create test database (fast!).testDB, testDBName, err := tm.CreateTestDatabase(ctx)if err != nil {log.Fatal(err)}defer testDB.Close()defer tm.DropTestDatabase(ctx, testDBName)// Use testDB for testing...log.Printf("Test database %s ready!", testDBName)}

Цифры говорят сами за себя

Я провел детальные бенчмарки, сравнивая традиционный подход и подход с шаблонами. Результаты впечатляют:

🚀 Сравнение скорости (меньше — лучше)

Сложность схемы

Классический подход

Через шаблоны

Ускорение

1 таблица

28.9 мс

28.2 мс

1.03x

3 таблицы

39.5 мс

27.6 мс

1.43x

5 таблиц (+индексы)

43.1 мс

28.8 мс

1.50x

📈 Массовое создание баз

Количество баз

Классический подход

Через шаблоны

Экономия времени

20 баз

906.8 мс

613.8 мс

32%

50 баз

2.29 с

1.53 с

33%

200 баз

9.21 с

5.84 с

37%

500 баз

22.31 с

14.82 с

34%

Главный вывод: скорость подхода с шаблонами не зависит от сложности схемы. Пока классический метод будет всё больше замедляться с ростом числа таблиц и индексов, метод с шаблонами остается стабильно быстрым.

Что под капотом?

  1. Инициализация: Создается база-шаблон, на нее один раз накатываются все миграции.

  2. Тестирование: Для каждого теста создается новая база через CREATE DATABASE ... TEMPLATE — это быстрое копирование на уровне файловой системы PostgreSQL.

  3. Очистка: После всех тестов удаляются все созданные тестовые базы и сам шаблон.

Для кого этот инструмент?

  • У Вас больше 10 тестов, связанных с базой данных.

  • Ваша схема данных сложнее 2-3 таблиц.

  • Вы часто запускаете тесты во время разработки.

  • Ваш CI-пайплайн включает этап с интеграционными тестами БД.

  • Вы цените свое время и не хотите ждать лишние 10 секунд при каждом запуске.

Полезные ссылки

Буду рад вашим звёздочкам на GitHub, пул-реквестам и issue! Что думаете о таком подходе? Сталкивались ли с подобной проблемой и как решали её раньше?

Большое спасибо за прочтение поста!

ссылка на оригинал статьи https://habr.com/ru/articles/947672/