Налаштування 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+ проєктів. Зв'яжіться з нами для попередньої оцінки.
Отримайте консультацію: напишіть нам, і ми проведемо безкоштовний аудит вашої бази даних.







