Налаштування 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
end
create! та decrement! зі знаком оклику викидають виняток при помилці — транзакція відкотиться автоматично.
Що входить у налаштування ActiveRecord під ключ
- Аудит поточної конфігурації та схеми БД
- Налаштування
database.ymlз реплікою та таймаутами - Оптимізація моделі: асоціації, scopes, валідації
- Міграції з правильними індексами
- Впровадження Bullet для виявлення N+1
- Підключення async queries для важких сторінок
- Документація з експлуатації
- Навчання команди (1 година)
- Тиждень підтримки після здачі
Як ми працюємо
- Аналіз — завантажуємо поточну конфігурацію, логи, повільні запити.
- Проектування — складаємо план змін, погоджуємо з вами.
- Реалізація — вносимо правки в конфіги, моделі, міграції.
- Тестування — перевіряємо на копії продакшну, заміряємо метрики.
- Деплой — розгортаємо, моніторимо першу добу.
Терміни та вартість
Налаштування ActiveRecord для нового проєкту — від 1 дня. Оптимізація існуючого — 1–3 дні. Вартість розраховується індивідуально залежно від обсягу. Ми працюємо з Rails більше 5 років, реалізували 30+ проєктів. Зв'яжіться з нами для попередньої оцінки.
Отримайте консультацію: напишіть нам, і ми проведемо безкоштовний аудит вашої бази даних.







