> Какие оптимизации запросов с PostgreSQL вы делали (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: BrightPattern

Стек: Go, PostgreSQL

> Пример ответа

Короткий ответ

Основные оптимизации: анализ планов через EXPLAIN ANALYZE, создание и настройка индексов (B-tree, GIN, partial, covering), оптимизация JOIN-запросов, борьба с N+1 через batch fetching, настройка work_mem и shared_buffers, использование pg_stat_statements для поиска медленных запросов, рефакторинг запросов с подзапросами в CTE или JOIN, и работа с partitioning для больших таблиц.

Подробное объяснение

Оптимизация PostgreSQL - это итеративный процесс, который начинается с измерения. Основной инструмент - EXPLAIN (ANALYZE, BUFFERS), который показывает реальное время выполнения, количество строк и buffer usage. Ключевые направления:

  1. Индексы: не просто создание, а выбор правильного типа. Для точных совпадений - B-tree, для полнотекстового поиска - GIN, для JSONB - GIN с jsonb_path_ops. Partial index для часто фильтруемых подмножеств, covering index для index-only scans.

  2. Планировщик: настройка random_page_cost, effective_cache_size, work_mem. Частая проблема - недооценка количества строк из-за устаревшей статистики, решается ANALYZE или повышением default_statistics_target.

  3. JOIN-стратегии: выбор между nested loop, hash join и merge join. Для больших таблиц hash join часто оптимальнее. Важно избегать nested loop при больших объёмах данных.

  4. N+1 проблема: в Go с database/sql или pgx это частая проблема. Решение - JOIN с последующей агрегацией в коде, или batch запросы с WHERE id = ANY($1).

  5. Настройка конфигурации: shared_buffers обычно 25% от RAM, work_mem для сортировок и hash join, maintenance_work_mem для индексов.

  6. Мониторинг: pg_stat_statements показывает частоту и время выполнения запросов, pg_stat_user_indexes - неиспользуемые индексы.

На практике

В Go-проектах с PostgreSQL я обычно начинаю с EXPLAIN ANALYZE на проблемных эндпоинтах. Часто оказывается, что проблема не в SQL, а в количестве запросов - например, цикл с отдельным SELECT для каждого элемента. Тогда переписываю на один запрос с JOIN и агрегацией в Go.

Для высоконагруженных чтений использую materialized views для тяжёлых агрегатов, которые обновляются по расписанию. Для write-heavy сценариев - batch insert через pgx.Batch или COPY.

Также важно следить за ростом таблиц - когда таблица превышает размер shared_buffers, производительность индексов может деградировать, и тогда рассматриваю partitioning по диапазону или хешу.

Пример кода

GO
// Плохо: N+1
for _, userID := range userIDs {
var profile Profile
db.QueryRow("SELECT * FROM profiles WHERE user_id = $1", userID).Scan(&profile)
profiles = append(profiles, profile)
}
// Хорошо: один запрос
rows, err := db.Query(`
SELECT p.*, u.name
FROM profiles p
JOIN users u ON u.id = p.user_id
WHERE p.user_id = ANY($1)
`, pq.Array(userIDs))
SQL
-- Плохо: неиспользуемый индекс
CREATE INDEX idx_users_email ON users(email);
-- Хорошо: partial index для активных пользователей
CREATE INDEX idx_users_active_email ON users(email) WHERE is_active = true;
-- Проверка плана
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders
WHERE created_at > now() - interval '1 day'
ORDER BY total DESC
LIMIT 10;

Как отвечать на собеседовании

Начните с конкретного примера из практики: опишите симптом (медленный эндпоинт), диагностику (EXPLAIN ANALYZE), найденную причину (например, seq scan на большой таблице) и решение (индекс или рефакторинг). Покажите понимание trade-off: индекс ускоряет чтение, но замедляет запись и занимает место. Упомяните, что оптимизация - это не только SQL, но и настройка PostgreSQL и паттерны в Go.

Что проверяет интервьюер

  • Понимание внутренностей PostgreSQL: как работает планировщик, статистика, buffer cache.
  • Умение читать EXPLAIN и находить узкие места.
  • Знание типов индексов и когда их применять.
  • Понимание N+1 и способов борьбы с ним в Go.
  • Опыт настройки конфигурации PostgreSQL.
  • Системный подход: от измерения до решения, а не "я создал индекс и всё".

Типичные ошибки

  • Создание индексов без анализа плана - "индекс ради индекса".
  • Игнорирование EXPLAIN ANALYZE в пользу EXPLAIN без выполнения.
  • Неучёт стоимости записи при добавлении индексов на hot path.
  • Использование SELECT * вместо необходимых колонок - увеличивает IO.
  • Забывают про work_mem для сортировок - PostgreSQL сбрасывает на диск.
  • Не проверяют, что индекс реально используется планировщиком после ANALYZE.
  • В Go - использование ORM без понимания генерируемых запросов.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью