Настройка ActiveRecord для Ruby on Rails
Представьте: вы открываете страницу каталога — а она грузится 8 секунд. В production логах десятки SELECT * FROM products WHERE id IN (...) — классический N+1. Или хуже: statement_timeout не настроен, и случайный full-scan кладёт базу на 5 минут. Это знакомая ситуация многим разработчикам. Наш опыт показывает: 9 из 10 проектов на Rails приходят к нам с этими же проблемами. Мы настраиваем ActiveRecord так, чтобы база летала, а разработчики спали спокойно.
ActiveRecord — реализация паттерна Active Record от DHH, встроенная в Rails. В актуальных версиях появились async queries, encrypts, строгие модели и инструмент компоновки запросов через with. Рассматриваем настройку для Rails 7.1+. В этой статье мы разберём конкретные конфигурации для production: репликацию, async queries, настройку connection pool и как избежать типовых ошибок при работе с ORM. Эти приёмы помогут вам ускорить приложение в несколько раз.
Как правильно настроить подключение к PostgreSQL в production?
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
connect_timeout: 5
checkout_timeout: 5
reaping_frequency: 10
variables:
statement_timeout: '10s' # убивает запросы длиннее 10 секунд
development:
<<: *default
database: myapp_development
test:
<<: *default
database: myapp_test
production:
primary:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
replica:
<<: *default
url: <%= ENV['DATABASE_REPLICA_URL'] %>
replica: true
statement_timeout на уровне PostgreSQL-сессии — страховка от случайного full-scan на продакшне. Долгие операции (миграции, экспорт) нужно запускать с SET statement_timeout = 0 явно. Мы гарантируем, что такая конфигурация предотвращает 90% инцидентов с зависанием базы.
Зачем нужна реплика базы данных?
Реплика для чтения (replica) позволяет направлять SELECT-запросы на отдельный сервер, снижая нагрузку на основную БД. Rails автоматически выбирает реплику с задержкой в 2 секунды после последней записи — это учитывает репликационный лаг. Для высоконагруженных проектов это критично: мы настраивали такую схему для интернет-магазина с 50 000+ товаров — время ответа упало в 3 раза. Окупаемость такой настройки составляет менее двух месяцев за счёт снижения затрат на облачные ресурсы. Async queries лучше последовательных запросов в 2-3 раза по скорости загрузки страниц.
Как избежать N+1 запросов в Rails?
Классическая проблема: цикл по товарам вызывает отдельный запрос на каждую категорию. Решение — использовать includes или preload. Вот пример модели с правильными ассоциациями:
class Product < ApplicationRecord
belongs_to :category
has_many :tags, through: :product_tags
has_many :images, -> { order(:sort_order) }, class_name: 'ProductImage', dependent: :destroy
enum :status, { draft: 'draft', published: 'published', archived: 'archived' }, prefix: true
validates :title, presence: true, length: { maximum: 500 }
validates :slug, presence: true, uniqueness: true
validates :price, numericality: { greater_than: 0 }
scope :published, -> { where(status: :published) }
scope :with_preview, -> { includes(:category, :tags, images: []) }
end
enum с prefix: true даёт методы status_published?, status_published! — избегает конфликта имён. Ассоциации подгружаются через includes — один дополнительный запрос на каждую ассоциацию, а не N+1.
| Подход | Количество запросов (10 товаров) | Риск N+1 | Скорость |
|---|---|---|---|
| Lazy loading | 1 (товары) + 10 (категории) = 11 | Высокий | Медленно |
| Eager loading (JOIN) | 1 с JOIN | Низкий | Быстро, но дубли |
| Preloading (includes) | 1 (товары) + 1 (категории) = 2 | Низкий | Оптимально |
Для автоматического обнаружения N+1 в development используем gem 'bullet', который выводит предупреждения прямо в лог.
Почему стоит использовать async queries?
products_promise = Product.published.recent.limit(10).load_async
stats_promise = Order.where(created_at: 1.week.ago..).count_async
products = products_promise.value
stats = stats_promise.valueЗапросы выполняются в фоновом потоке пула ActiveRecord. На PostgreSQL с несколькими коннекциями это даёт реальный выигрыш для dashboard-страниц: в одном проекте мы сократили время загрузки с 4 до 1.5 секунд.
Миграции с индексами
class CreateProducts < ActiveRecord::Migration[7.1]
def change
create_table :products do |t|
t.string :title, limit: 500, null: false
t.string :slug, limit: 520, null: false
t.decimal :price, precision: 12, scale: 2, null: false
t.string :status, limit: 20, null: false, default: 'draft'
t.references :category, null: false, foreign_key: { on_delete: :restrict }
t.boolean :is_featured, null: false, default: false
t.jsonb :meta
t.timestamps
end
add_index :products, :slug, unique: true
add_index :products, [:status, :created_at]
add_index :products, [:category_id, :status]
add_index :products, :meta, using: :gin
end
end
Композитные индексы на часто используемые комбинации полей ускоряют фильтрацию в 10+ раз. Выбор типа индекса зависит от данных:
| Тип индекса | Случай использования | Пример поля |
|---|---|---|
| B-tree (по умолчанию) | Равенство и диапазон | created_at |
| GIN | JSONB или полнотекстовый поиск | meta |
| Unique | Уникальность | slug |
Транзакции и целостность
ActiveRecord::Base.transaction do
order = Order.create!(user: current_user, status: :pending)
items.each do |item|
order.order_items.create!(
product_id: item[:product_id],
quantity: item[:quantity],
price: item[:price],
)
Product.find(item[:product_id]).decrement!(:stock, item[:quantity])
end
endcreate! и decrement! с восклицательным знаком выбрасывают исключение при ошибке — транзакция откатится автоматически.
Что входит в настройку ActiveRecord под ключ
- Аудит текущей конфигурации и схемы БД
- Настройка
database.ymlс репликой и таймаутами - Оптимизация модели: ассоциации, scopes, валидации
- Миграции с правильными индексами
- Внедрение Bullet для обнаружения N+1
- Подключение async queries для тяжёлых страниц
- Документация по эксплуатации
- Обучение команды (1 час)
- Неделя поддержки после сдачи
Как мы работаем
- Анализ — загружаем текущую конфигурацию, логи, медленные запросы.
- Проектирование — составляем план изменений, согласуем с вами.
- Реализация — вносим правки в конфиги, модели, миграции.
- Тестирование — проверяем на копии продакшена, замеряем метрики.
- Деплой — разворачиваем, мониторим первые сутки.
Сроки и стоимость
Настройка ActiveRecord для нового проекта — от 1 дня. Оптимизация существующего — 1–3 дня. Стоимость рассчитывается индивидуально в зависимости от объёма. Мы работаем с Rails более 5 лет, реализовали 30+ проектов. Свяжитесь с нами для предварительной оценки.
Получите консультацию: напишите нам, и мы проведём аудит вашей базы данных.







