Как новая ветка получает конфигурацию, миграции и функции

Источник: Supabase•

Как новая ветка получает конфигурацию, миграции и функции

Что происходит при создании ветки: какие настройки копируются, откуда берутся миграции и функции, и в каком порядке выполняются этапы.

Ветки Supabase — это отдельные проекты, поэтому перед началом работы в них должны быть настроены три компонента: настройки, схема и Edge Functions. При создании ветки мы запускаем все три процесса практически параллельно.

Вы создаете ветку предварительного просмотра (preview branch) для тестирования изменений схемы. Примерно через 20 секунд (медианное время) ветка готова, а ваши таблицы, настройки и Edge Functions уже на месте. Если создание ветки занимает больше времени, чем ожидалось, или какой-то этап завершается с ошибкой, вам нужно знать, что происходило в это время.

В этой статье мы разберем, как работает процесс ветвления в Supabase, что копируется из основного проекта, откуда берутся миграции и функции, в каком порядке выполняются шаги и что вы увидите в случае ошибки.

Каждая ветка принадлежит основному проекту. То, как ветка получает свои данные, зависит от одного главного вопроса: привязана ли она к ветке git?

Ветки, подключенные к GitHub, привязаны к ветке git, поэтому они считывают config.toml, миграции, файлы сидов (seed files) и функции из вашего репозитория. Ветки без git не имеют привязки к репозиторию. Вы создаете их через панель управления или CLI, и они получают схему и функции из вашего основного проекта.

Вы можете узнать больше об обоих вариантах настройки в руководстве по ветвлению Branching guide.

Мы предоставляем каждой ветке отдельный проект с настройками по умолчанию. Затем выполняются следующие три части, преимущественно параллельно.

  • Конфигурация копирует настройки вашего основного проекта в ветку и применяет их.
  • Миграции обеспечивают базу данных ветки вашей схемой.
  • Развертывание функций копирует или развертывает ваши Edge Functions.

Вы можете следить за ходом выполнения в рабочем процессе ветки. Статус RUNNING означает, что этап выполняется, EXITED — успешно завершен, PAUSED — завершен с предупреждением или пропущен намеренно, DEAD — завершен с ошибкой.

Ваша ветка начинается с теми же настройками, что и основной проект. Мы копируем настройки Postgres, настройки пулера соединений (Supavisor), а также конфигурацию Auth, Data API, Realtime и Storage. Принудительное использование SSL также переносится, как и сетевые ограничения, если они есть в основном проекте. Секреты Auth не переносятся. Мы также включаем Database Webhooks для ветки.

Применение настроек с одной перезагрузкой#

Некоторые настройки Postgres вступают в силу только после перезагрузки, а перезагрузки занимают время. Поэтому задание update_config сначала записывает все настройки Postgres на диск без перезагрузки. Большинство новых веток затем включают резервное копирование с помощью задания walg_enable, которое один раз перезагружает Postgres. Эта единственная перезагрузка также применяет новые настройки.

Если ваш основной проект требует обязательного использования SSL, мы применяем это требование к ветке до того, как walg_enable создаст первую резервную копию. Принудительное использование SSL перезагружает Postgres, а перезагрузка во время создания резервной копии привела бы к созданию невосстанавливаемой копии.

Затем задание infra_update применяет конфигурацию Auth, Data API и Realtime и перезагружает Auth и Data API, чтобы они применили изменения. Когда это задание завершается, этап configure переходит в статус EXITED. Если update_config или infra_update завершаются с ошибкой, configure переходит в статус DEAD.

Параллельно с этим выполняются еще два задания: одно применяет сетевые ограничения к ветке (если они есть в основном проекте), а другое регистрирует ветку в пулере соединений. Оба, наряду с принудительным использованием SSL, автоматически повторяются при сбоях. Они не блокируют этап configure и не приводят к ошибке всей ветки.

Приоритет настроек репозитория#

Ветка, подключенная к GitHub, затем запускает свой собственный рабочий процесс, и ее этап configure применяет файл config.toml из вашего репозитория. Настройки репозитория перекрывают скопированные, поэтому приоритет остается за репозиторием. Мы дожидаемся применения скопированной конфигурации перед запуском этого рабочего процесса, чтобы они никогда не конфликтовали.

Этап configure считывает текущую конфигурацию ветки, а затем обновляет разделы Postgres, Data API, Auth и Storage, которые отличаются от config.toml. В случае ошибки он повторяет попытку, пропуская уже примененные разделы, и завершается проверкой работоспособности, чтобы убедиться, что перезапущенные службы работают.

Если в вашем репозитории нет файла config.toml, этап включает Database Webhooks и на этом останавливается. Чтобы задать долгоживущей постоянной ветке собственные настройки, добавьте блок [remotes]. Вы можете прочитать больше о конфигурации веток с помощью удаленных репозиториев.

Миграции обеспечивают вашу ветку той же схемой, что и в основном проекте, или схемой, описанной в вашем pull request.

Когда выполняются миграции#

Ветка, подключенная к GitHub, запускает этот рабочий процесс при каждом push:

_10

clone < pull < health < configure < migrate < seed < deploy

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

Ветка без git запускает более короткий рабочий процесс: pull < migrate. Мы запускаем его после последней перезагрузки Postgres из конфигурации, так как перезагрузка во время миграции привела бы к разрыву соединения.

Pull: откуда берутся миграции#

Этап pull собирает файлы миграций для применения. Источник зависит от типа ветки. В ветке, подключенной к GitHub, этап clone извлекает вашу ветку git, поэтому файлы в supabase/migrations уже присутствуют. Ветка без git считывает таблицу supabase_migrations.schema_migrations из вашего основного проекта. Каждая строка содержит версию, имя и выполненные SQL-инструкции, а этап pull превращает каждую строку обратно в файл миграции. Он также считывает собственную историю ветки, поэтому миграции, уже примененные в ветке, остаются в наборе.

Если в вашем основном проекте нет истории миграций, pull делает дамп схемы и использует его как первую миграцию. Он также записывает эту миграцию в таблицу истории вашего основного проекта, что означает, что последующие ветки и команда supabase db pull будут начинаться с нее.

Migrate: выполнение каждого файла как одной транзакции#

Этап migrate подключается к базе данных ветки и сравнивает ваши файлы миграций с таблицей supabase_migrations.schema_migrations в ветке. Любой файл без соответствующей записи считается ожидающим. Перед выполнением чего-либо, migrate сохраняет секреты Vault из config.toml в вашу ветку. Мы рекомендуем шифровать секреты с помощью dotenvx, если вы планируете коммитить их в репозиторий. Вы можете отключить миграции с помощью [db.migrations].

Каждый файл разбивается на отдельные инструкции. Символ ; завершает инструкцию только вне строковых литералов, кавычек, тел функций в формате dollar-quoted или BEGIN ATOMIC, комментариев и скобок. Ожидается, что файл миграции содержит только корректный SQL. Мы не поддерживаем мета-команды psql, такие как COPY FROM STDIN.

Разделитель написан на чистом Go и стремится к 100% совместимости с собственным лексером psql: файл, который psql разбивает определенным образом, должен разбиваться так же и здесь. Наша реализация проверена на PG 16 с использованием корпуса, включающего 121 написанный вручную граничный случай, 199 SQL-файлов из регрессионных тестов Postgres и около 8000 случайных входных данных для фаззинга с автоматическим сокращением.

Затем каждый файл выполняется в режиме конвейера. Мы отправляем каждую инструкцию из файла, плюс строку для таблицы истории, в Postgres за один сетевой запрос. Пакет выполняется как одна транзакция. Файл либо применяется полностью и получает свою строку истории, либо откатывается назад, не оставляя следов.

Когда миграция завершается с ошибкой, сообщение об ошибке показывает, какая инструкция не удалась, и указывает кареткой на позицию ошибки. Файл миграции, который старше последней примененной, все равно выполняется, но с предупреждением. Версия в таблице истории без соответствующего файла приводит к ошибке этапа.

Seed#

После миграции этап заполнения (seed) создает бакеты Storage, объявленные в config.toml, и загружает в них файлы. Если заполнение включено в [db.seed], он также выполняет ваши файлы заполнения. Прочитайте о заполнении веток.

Ваша ветка получает те же Edge Functions, что и основной проект, или те, что находятся в вашем репозитории.

При создании ветки#

Задача copy_functions копирует каждую активную функцию из вашего основного проекта в ветку. Копирование выполняется параллельно с конфигурацией, так как функции не находятся в базе данных Postgres ветки. Ветка не считается готовой, пока копирование не завершится. Этап развертывания (deploy) переходит в состояние EXITED при успехе и DEAD при сбое.

Копирование можно безопасно повторить. Функция, повторно развернутая в основном проекте во время копирования, никогда не будет зарегистрирована в ветке с отсутствующим пакетом.

При каждом пуше#

Ветка, подключенная к GitHub, выполняет deploy как последний этап своего рабочего процесса. Если в вашем репозитории есть каталог supabase/functions, deploy развертывает функции, объявленные в config.toml в разделе [functions.<slug>]. Если в вашем репозитории нет каталога функций, deploy копирует функции, развернутые в основном проекте, при этом config.toml по-прежнему может отключить функцию или изменить ее проверку JWT. Секреты функций в config.toml сохраняются в первую очередь. Если в вашем репозитории есть функции, но ни одна из них не объявлена в config.toml, этап выдаст предупреждение и ничего не развернет.

Пропуск неизмененных функций#

Повторно загружаются только измененные функции. Для каждой функции deploy создает пакет и сжимает его с помощью Brotli, затем вычисляет контрольную сумму SHA-256 сжатого пакета. Он сравнивает контрольную сумму и настройку JWT со значениями, уже сохраненными для функции в ветке. Если оба значения совпадают, функция пропускается. В противном случае загружается новый пакет.

Каждое развертывание использует одинаковые настройки сжатия, поэтому неизмененная функция всегда дает одинаковую контрольную сумму. Функция, скопированная из основного проекта, повторно использует сжатый пакет основного проекта, если он существует, поэтому применяется та же проверка.

Большинство сбоев устраняются сами собой, так как задачи настройки повторяются перед тем, как завершиться с ошибкой. Когда этап все же завершается неудачей, этап DEAD останавливает зависящие от него этапы. Если migrate завершается сбоем, этапы seed и deploy не выполняются. Этап PAUSED завершился с предупреждением, и последующие этапы продолжают выполняться. Неудачная проверка работоспособности — это предупреждение, а не сбой. Если перезапуск Postgres во время настройки окончательно завершается неудачей, этап migrate ветки без git остается в состоянии CREATED, так как миграция не выполнялась.

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

Подключите свой репозиторий GitHub и откройте pull request, чтобы увидеть полный рабочий процесс. Начните с руководства по интеграции с GitHub или создайте свою первую ветку из панели управления с помощью функции ветвления через панель управления.

О чём эта статья