> Как разделить большую таблицу по студентам для оптимизации запросов (Go)

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

Компании: Wildberries

Стек: Go

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

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

Основной подход - горизонтальное партиционирование (sharding) по ключу студента, например по student_id через hash или range. В PostgreSQL/MySQL - это native partitioning, в распределённых системах - sharding на уровне приложения или через middleware. Критично выбрать ключ шардирования так, чтобы данные одного студента всегда попадали в один шард - это сохраняет возможность эффективных запросов по конкретному студенту без cross-shard операций. Также стоит рассмотреть индексы, покрывающие запросы, и архивацию старых данных.

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

Проблема: большая таблица с данными студентов замедляет запросы из-за роста объёма сканирования, увеличения размера индексов и cache miss.

Варианты оптимизации:

  1. Партиционирование по диапазону (range partitioning) - например, по student_id или дате. Подходит, если есть естественный порядок и запросы часто идут по диапазону. Минус - возможен дисбаланс данных.

  2. Hash-партиционирование - равномерное распределение по шардам, но теряется возможность range-сканирования по ключу.

  3. Sharding на уровне приложения - маршрутизация запросов через lookup-сервис или вычисление шарда по student_id % N. Требует управления миграцией данных и обработки cross-shard запросов.

  4. Индексы - композитный индекс (student_id, ...) покрывает большинство запросов по конкретному студенту. Если запросы всегда фильтруют по student_id, это почти всегда решает проблему без партиционирования.

  5. Архивация - вынос старых данных в отдельную таблицу или хранилище (например, cold storage), оставляя в горячей таблице только актуальные записи.

Ключевой trade-off: партиционирование усложняет операции с данными (JOIN по шардам, транзакции, миграции), поэтому сначала стоит измерить, действительно ли проблема в объёме, а не в отсутствии индексов или плохих запросах.

На практике

Для Go-приложения с PostgreSQL:

  • Начни с анализа EXPLAIN ANALYZE - часто проблема не в размере таблицы, а в seq scan.
  • Если таблица > 100 млн строк и запросы всегда по student_id - используй native partitioning по student_id (hash или range).
  • В Go-коде не нужно менять логику - драйвер работает с партиционированной таблицей как с обычной.
  • Если используешь sharding на уровне приложения - добавь слой репозитория, который вычисляет шард и выбирает нужное подключение. Для этого удобно использовать пул соединений с маршрутизацией.
  • Для миграции данных между шардами используй фоновые задачи с батчами и проверкой консистентности.

Пример кода

Пример маршрутизации шардов в Go:

GO
type ShardRouter struct {
shards []*sql.DB
}
func (r *ShardRouter) GetDB(studentID int64) *sql.DB {
idx := studentID % int64(len(r.shards))
return r.shards[idx]
}
func (r *ShardRouter) GetStudent(ctx context.Context, studentID int64) (*Student, error) {
db := r.GetDB(studentID)
row := db.QueryRowContext(ctx, "SELECT id, name FROM students WHERE id = $1", studentID)
// ...
}

Пример партиционирования в PostgreSQL:

SQL
CREATE TABLE students (
id bigint NOT NULL,
name text,
created_at timestamptz
) PARTITION BY HASH (id);
CREATE TABLE students_0 PARTITION OF students FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE students_1 PARTITION OF students FOR VALUES WITH (MODULUS 4, REMAINDER 1);
-- ...

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

Начни с уточнения: какой объём данных, какие запросы тормозят, есть ли уже индексы. Это покажет, что ты не предлагаешь решение вслепую. Затем опиши иерархию решений: сначала индексы и оптимизация запросов, потом партиционирование, потом шардирование. Объясни trade-off каждого подхода. Приведи конкретный пример с ключом шардирования и объясни, почему student_id - хороший выбор. Упомяни, что в Go-приложении важно не смешивать логику шардирования с бизнес-логикой - вынеси её в отдельный слой.

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

  • Понимание разницы между партиционированием и шардированием.
  • Умение выбирать ключ шардирования с учётом паттернов доступа.
  • Знание trade-off: сложность транзакций, JOIN, миграции.
  • Практический опыт: как измерить проблему, когда хватает индекса.
  • Понимание влияния на Go-код: маршрутизация, пулы соединений, обработка ошибок.

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

  • Предлагать шардирование сразу, без анализа индексов и планов запросов.
  • Выбирать ключ шардирования не по паттерну запросов, а по удобству (например, по id вместо student_id).
  • Игнорировать проблему cross-shard запросов и транзакций.
  • Не учитывать перебалансировку при добавлении шардов - hash-модуль ломается при изменении числа шардов.
  • Забывать про архивацию и retention policy - часто это дешевле и проще, чем шардирование.

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

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