Что такое миграции БД и как ими управлять?ruby-97

Миграции — это система контроля версий для структуры базы данных, позволяющая безопасно вносить и откатывать изменения. Рассмотрим их реализацию в Ruby-экосистеме.

1. Основные концепции миграций

Что такое миграция?

Миграция представляет собой:

  • Набор инструкций для изменения схемы БД
  • Механизм последовательного применения изменений
  • Способ синхронизации структуры БД между средами

Пример базовой миграции

class CreateProducts < ActiveRecord::Migration[7.0]
  def change
    create_table :products do |t|
      t.string :name
      t.text :description
      t.decimal :price, precision: 8, scale: 2

      t.timestamps
    end
  end
end

2. Управление миграциями в Rails

Генерация миграций

rails generate migration AddCategoryToProducts category:references

Основные команды

КомандаДействие
rails db:migrateПрименяет все новые миграции
rails db:rollbackОткатывает последнюю миграцию
rails db:migrate:statusПоказывает статус миграций
rails db:migrate:redoПерезапускает последнюю миграцию
rails db:schema:loadЗагружает схему из schema.rb

3. Структура миграции

Методы изменения схемы

def change
  add_column :users, :age, :integer
  add_index :users, :email, unique: true
  change_column_null :posts, :title, false
end

Отдельные up/down методы

def up
  create_table :categories do |t|
    t.string :name
  end
end

def down
  drop_table :categories
end

4. Продвинутые техники

Условное выполнение миграций

def change
  if table_exists?(:old_products)
    rename_table :old_products, :products
  end
end

Пакетные изменения

def change
  reversible do |dir|
    dir.up do
      # Код для применения миграции
    end
    dir.down do
      # Код для отката миграции
    end
  end
end

5. Безопасные миграции для больших таблиц

Постепенное обновление

def up
  add_column :users, :new_flag, :boolean, default: false

  User.in_batches do |batch|
    batch.update_all(new_flag: true)
  end
end

Фоновые миграции

class BackfillUserStatus < ActiveRecord::Migration[7.0]
  def change
    # Только добавляет колонку
    add_column :users, :status, :string

    # Фактическое заполнение выносится в фоновую задачу
    reversible do |dir|
      dir.up { BackfillUserStatusJob.perform_later }
    end
  end
end

6. Работа с данными в миграциях

Безопасное изменение данных

def change
  User.where(role: nil).update_all(role: 'guest')

  # Лучше через find_each для больших таблиц
  User.where(active: nil).find_each do |user|
    user.update(active: true)
  end
end

Использование моделей в миграциях

class AddDefaultStatus < ActiveRecord::Migration[7.0]
  class TemporaryUser < ApplicationRecord
    self.table_name = 'users'
  end

  def change
    TemporaryUser.where(status: nil).update_all(status: 'pending')
  end
end

7. Лучшие практики

  1. Идемпотентность: Миграции должны быть безопасны для повторного запуска
  2. Обратная совместимость: Изменения не должны ломать работающий код
  3. Атомарность: Каждая миграция должна делать одну логическую вещь
  4. Безопасность данных: Всегда делайте бэкап перед массовыми изменениями
  5. Тестирование: Проверяйте миграции на staging перед production

Пример безопасной миграции

class AddIndexToUsersEmail < ActiveRecord::Migration[7.0]
  disable_ddl_transaction! # Для Postgres позволяет избежать блокировок

  def change
    add_index :users, :email, algorithm: :concurrently, unique: true
  rescue ActiveRecord::RecordNotUnique
    # Обработка случая, когда есть дубликаты email
    remove_index :users, :email, algorithm: :concurrently
    execute "CREATE INDEX CONCURRENTLY index_users_on_email ON users (lower(email))"
  end
end

8. Инструменты мониторинга

Проверка длительных миграций

ActiveRecord::Migration.verbose = true
ActiveRecord::Base.logger = Logger.new(STDOUT)

Анализ блокировок

Для Postgres можно добавить:

execute "SELECT pg_sleep(0.1)" if Rails.env.development?

Резюмируем

Миграции БД в Ruby-приложениях:

  • ActiveRecord Migrations - стандартный инструмент в Rails
  • Sequel Migrations - альтернатива для не-Rails проектов
  • Основные принципы: идемпотентность, обратимость
  • Для больших таблиц используйте безопасные подходы
  • Всегда проверяйте миграции на staging-окружении
  • Документируйте неочевидные изменения в миграциях

Правильное использование миграций позволяет:

  • Безопасно изменять структуру БД
  • Синхронизировать изменения между средами
  • Отслеживать историю изменений схемы
  • Легко откатывать ошибочные изменения