Проверка изменений схемы базы данных в конвейере CI перед развертыванием в производстве

Проверка изменений схемы базы данных в конвейере CI перед развертыванием в производстве

Введение

В Autoblocks мы используем Neon в качестве серверного поставщика Postgres для наших транзакционных данных. Поскольку мы постоянно работаем над улучшением нашего продукта на основе отзывов пользователей, мы часто вносим существенные изменения в схему нашей базы данных, чтобы наилучшим образом удовлетворить развивающиеся бизнес-потребности. Некоторые из этих миграций включают не только изменения схемы (добавление новой таблицы, столбца и т. д.), но также изменения данных, где мы пишем скрипты, например, для обновления столбцов, изменения схемы JSON-столбца, внесения разрушительных изменений без простоя и так далее.

Мы всегда тестируем такие изменения локально, но мы ценим дополнительное спокойствие, которое нам дает выполнение ”сухого” прогона на нашей производственной базе данных. До Neon для создания такой среды с использованием только RDS (сервиса реляционных баз данных от AWS) потребовалось бы значительные инженерные вложения. С Neon branching это удивительно легко — создать и уничтожить копии вашей производственной базы данных в среде непрерывной интеграции (CI).

В Autoblocks мы используем GitHub Actions, и мы сделали открытыми исходными кодами действия, которые мы написали для достижения этой цели.

Создание веток

Хотя действия поддерживают создание Neon веток для каждого коммита, мы решили создавать Neon ветку только в случае, когда есть миграции для выполнения. В нашем случае это происходит, когда наш каталог миграций Prisma был изменен. Наши миграции находятся в каталоге packages/db/prisma/migrations, а файл package.json в подкаталоге packages/db в нашем монорепо содержит скрипты для выполнения миграций:

"scripts": {
  "migrate:dev": "prisma migrate dev",
  "migrate:prod": "prisma migrate deploy"
}

GitHub Actions поддерживает фильтрацию по пути, но только на уровне рабочего процесса. Мы используем dorny/paths-filter, так как он позволяет нам добавлять фильтры по пути на уровне задачи. Вот сокращенный пример нашего основного рабочего процесса CI:

name: CI

on:
  push:
    branches-ignore:
      - main

jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      migrations: ${{ steps.changes.outputs.migrations }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - uses: dorny/paths-filter@v2
        id: changes
        with:
          filters: |
            migrations:
              - 'packages/db/prisma/migrations/**'

  validate-migrations:
    needs: changes
    if: needs.changes.outputs.migrations == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      # Setup node, install dependencies, etc.

      - name: Create Neon branch
        uses: autoblocksai/neon-actions/create-branch@v1
        id: neon
        with:
          api-key: ${{ secrets.NEON_API_KEY }}
          project-id: ${{ vars.NEON_PROJECT_ID }}

      - name: Run DB migrations
        run: npm run migrate:prod
        working-directory: packages/db
        env:
          DATABASE_URL: postgres://${{ secrets.PG_USERNAME }}:${{ secrets.PG_PASSWORD }}@${{ steps.neon.outputs.direct-host }}:5432/neondb

Теперь, когда я отправляю коммит в ветку GitHub, которая изменяет каталог миграций packages/db/prisma/migrations, мы:

  1. Создаем Neon ветку из последнего состояния нашей производственной базы данных.
  2. Запускаем наши миграции базы данных в этой ветке.
  3. Добавляем статус успешного/неуспешного выполнения к коммиту запроса на вливание.

Имя Neon ветки будет именем ветки GitHub плюс первыми семью символами SHA-хеша коммита: {branchName}-{commitSha}

Например, я создал ветку GitHub с именем add-new-table, изменил каталог миграций и затем отправил три коммита:

Image description

При каждом выполнении рабочего процесса CI будет создана новая Neon ветка, а затем на этой ветке будут запущены наши миграции. Ниже приведен пример вывода в консоли от действия autoblocksai/neon-actions/create-branch:

-> Run autoblocksai/neon-actions/create-branch@v1
Creating new branch with name 'add-new-table-d105eb3'
Created new branch:
{
  "branch": {
    "id": "br-empty-voice-99968957",
    "project_id": "early-silence-295820",
    "parent_id": "br-smelly-top-392940",
    "name": "add-new-table-d105eb3",
    "current_state": "init"
  },
  "endpoints": [
    {
      "id": "ep-bold-glade-26143287",
      "host": "ep-bold-glade-26143287.us-east-1.aws.neon.tech",
      "current_state": "init"
    }
  ]
}
Notice: Your Neon branch is at <https://console.neon.tech/app/projects/early-silence-295820/branches/br-empty-voice-99968957>
Wating for branch br-empty-voice-99968957 to be ready
Retrying in 0.25 seconds (5 retries left)
Branch br-empty-voice-99968957 is ready!
Wating for endpoint ep-bold-glade-26143287 to be ready
Endpoint ep-bold-glade-26143287 is ready!
Direct host: ep-bold-glade-26143287.us-east-1.aws.neon.tech
Pooled host: ep-bold-glade-26143287-pooler.us-east-1.aws.neon.tech

ы заметите, что у нас есть два хоста на выбор: либо прямой хост, либо пул-хост. Они доступны как выводы из действия с именами direct-host и pooled-host соответственно. Мы используем прямое соединение для миграций, так как Prisma требует его, но вероятно, вы захотите использовать пул-соединение для любого другого типа соединения, например, из веб-приложения или набора тестов на производительность.

Удаление веток

Когда ветка GitHub удаляется, независимо от того, удаляется ли она напрямую или в результате слияния запроса на вливание, мы хотим удалить все Neon ветки, созданные из этой ветки. Для этого мы просто запускаем действие autoblocksai/neon-actions/delete-branches при событии delete:

`name: Delete Neon Branches

on: delete

jobs:
  delete-neon-branches:
    if: github.event.ref_type == 'branch'
    runs-on: ubuntu-latest
    steps:
      - name: Delete Neon branches
        uses: autoblocksai/neon-actions/delete-branches@v1
        with:
          api-key: ${{ secrets.NEON_API_KEY }}
          project-id: ${{ vars.NEON_PROJECT_ID }}`


Это удалит все Neon ветки, имена которых начинаются с имени ветки GitHub, которая была только что удалена.

-> Run autoblocksai/neon-actions/delete-branches@v1
Found 3 branches:
  add-new-table-d105eb3 (br-empty-voice-99968957)
  add-new-table-9c62e5b (br-autumn-silence-26803864)
  add-new-table-a58c909 (br-jolly-rice-40072094)
Deleting branches with prefix 'add-new-table-':
  deleting add-new-table-d105eb3 (br-empty-voice-99968957)
  deleting add-new-table-9c62e5b (br-autumn-silence-26803864)
  deleting add-new-table-a58c909 (br-jolly-rice-40072094)


Больше примеров

Хотя мы используем Neon ветки только для проверки наших миграций, вы также можете использовать их в других контекстах, таких как тесты на производительность или среды предварительного просмотра Vercel. В таких случаях вы, вероятно, захотите отказаться от фильтрации по пути и затем использовать пул-хост в последующих шагах:

name: CI

on: push

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup node
        uses: actions/setup-node@v3

      - name: Install dependencies
        run: npm ci

      - name: Create neon branch
        id: neon
        uses: autoblocksai/neon-actions/create-branch@v1
        with:
          api-key: ${{ secrets.NEON_API_KEY }}
          project-id: ${{ vars.NEON_PROJECT_ID }}

      - name: Run prisma migrations
        run: npx prisma migrate deploy
        env:
          DATABASE_URL: postgres://${{ secrets.PG_USERNAME }}:${{ secrets.PG_PASSWORD }}@${{ steps.neon.outputs.direct-host }}:5432/neondb

      - name: Run smoke tests
        run: npm run test:smoke
        env:
          DATABASE_URL: postgres://${{ secrets.PG_USERNAME }}:${{ secrets.PG_PASSWORD }}@${{ steps.neon.outputs.pooled-host }}:5432/neondb

См. документацию для получения дополнительных сведений.