Был JOIN — и всё было хорошо. Пока таблица не превысила 1000 строк...

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


Ты живёшь с JOIN в проде, всё быстро и стабильно. Но однажды вторая таблица переваливает за 1000 записей — и внезапно запрос начинает тормозить. Почему?

🔥 Что произошло?

До порога в ~1К строк Postgres мог:

  • выбирать оптимальный план (Nested Loop, Index Scan)
  • моментально находить записи по индексу
  • делать всё в памяти (shared_buffers)

Но как только таблица выросла — Postgres пересчитал план.


📉 Как это выглядит в EXPLAIN?

Nested Loop
  -> Seq Scan on main_table
  -> Index Scan on second_table
       Index Cond: ...

Проблема: внешняя таблица большая, а Index Scan выполняется для каждой строки.


🚨 Постепенный рост — внезапная боль

Postgres выбирает план, исходя из статистики (ANALYZE), и думает:

«Да тут всего 999 строк, нормально!»

Но на 1001-й — Nested Loop превращается в ад: сотни тысяч обращений к индексу. Прод “висит”, CPU забито, Postgres не успевает.


💡 Как это исправить?

  1. Проверь план через EXPLAIN (ANALYZE, BUFFERS)
  2. Сравни actual rows и estimated rows — если они сильно различаются, это сигнал
  3. Запусти ANALYZE или VACUUM ANALYZE вручную
  4. Укажи JOIN явно (вместо WHERE ... IN)
  5. Попробуй форсировать план:
SET enable_nestloop = off;

(не в проде! Только чтобы проверить, поможет ли Hash Join)


🛡️ Профилактика

  • Регулярный ANALYZE
  • Мониторинг прироста строк в таблицах
  • Логгирование медленных запросов (log_min_duration_statement)
  • Тестирование SQL-запросов при объёмах, в 10 раз превышающих текущие

📌 Вывод: не доверяй малым таблицам. Они вырастают — и в самый плохой момент рушат тебе прод. Следи за ростом и будь готов поменять стратегию.

🗓 Дата публикации: 25.01.2025, но это не точно...

Ruby PostgreSQL Rails базы данных производительность DevOps