> Что такое паттерн разделения на чтение и запись (Go)
Уровень: junior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Employcity
Стек: Go
> Пример ответа
Короткий ответ
Паттерн разделения на чтение и запись (read-write split) - это архитектурный подход, при котором операции чтения и записи направляются на разные реплики базы данных. Основная запись идёт на primary (master), а чтение распределяется между read replicas. Это позволяет горизонтально масштабировать нагрузку на чтение, снижая давление на основную базу. В Go это часто реализуется через пул соединений с разными DSN и middleware для маршрутизации запросов.
Подробное объяснение
Суть паттерна в том, что база данных с одной primary-нодой имеет ограниченную пропускную способность. Если приложение преимущественно читает данные (типичный сценарий - 80/20), то primary быстро упирается в瓶颈. Реплики решают эту проблему: они асинхронно синхронизируются с primary и обслуживают read-запросы.
Ключевые моменты:
- Primary принимает все write-операции (INSERT, UPDATE, DELETE) и транзакции.
- Replicas обслуживают SELECT-запросы, не требующие строгой актуальности данных.
- Репликация обычно асинхронная, поэтому возможна небольшая задержка данных (replication lag).
В Go реализация обычно выглядит так:
- Два пула соединений: один для primary, другой для реплик.
- Middleware или обёртка над
database/sql, которая анализирует тип запроса (SELECT или нет) и выбирает нужный пул. - Для транзакций всегда используется primary, так как внутри транзакции могут быть и чтения, и записи.
Важно понимать trade-off: вы получаете масштабируемость чтения, но платите согласованностью. Если бизнес-логика требует чтения сразу после записи (read-your-writes), нужно либо использовать primary для таких запросов, либо применять дополнительные механизмы (например, запоминать timestamp последней записи и направлять запросы на primary, пока реплика не догонит).
На практике
Для junior-позиции достаточно понимать базовую схему и уметь её описать. На практике в Go это выглядит так:
- Создаётся интерфейс
DBс методамиExec,Query,QueryRow. - Реализация
readWriteSplitDBсодержит два*sql.DB-primaryиreplica. - Метод
Queryвыбирает replica,Exec- primary. - Для транзакций используется только primary.
Также важно упомянуть, что в реальных проектах часто используют готовые библиотеки (например, go-sql-driver/mysql с поддержкой multi-source) или прокси-слой (ProxySQL, pgpool), который сам решает, куда направить запрос. Но для собеседования достаточно показать понимание принципа.
Пример кода
GOtype DB struct {primary *sql.DBreplica *sql.DB}func (d *DB) Exec(query string, args ...any) (sql.Result, error) {return d.primary.Exec(query, args...)}func (d *DB) Query(query string, args ...any) (*sql.Rows, error) {return d.replica.Query(query, args...)}func (d *DB) QueryRow(query string, args ...any) *sql.Row {return d.replica.QueryRow(query, args...)}func (d *DB) BeginTx(ctx context.Context) (*sql.Tx, error) {return d.primary.BeginTx(ctx, nil)}
Как отвечать на собеседовании
Начните с определения: "это разделение запросов на запись (идут на primary) и чтение (идут на реплики)". Затем объясните зачем: "чтобы масштабировать нагрузку на чтение и не упираться в ресурсы одной базы". Упомяните про replication lag и что транзакции всегда идут на primary. Если спросят про Go - скажите про два пула соединений и выбор пула в зависимости от типа запроса. Не углубляйтесь в детали реализации репликации, если не спрашивают.
Что проверяет интервьюер
Интервьюер проверяет:
- Понимаете ли вы базовые принципы работы баз данных (репликация, согласованность).
- Умеете ли вы видеть trade-off между производительностью и актуальностью данных.
- Знаете ли вы, как это реализуется в коде на Go (пулы соединений, интерфейсы).
- Понимаете ли вы, когда этот паттерн применим, а когда нет (например, при высокой доле записей он не поможет).
Типичные ошибки
- Говорить, что реплики синхронные - обычно это не так, и это важно для понимания задержек.
- Предлагать отправлять все запросы на реплики, включая транзакции - это ломает консистентность.
- Не упоминать проблему read-your-writes - если приложение пишет и сразу читает, данные могут быть не видны на реплике.
- Путать паттерн с шардированием - это разные вещи: шардирование делит данные по ключу, а read-write split дублирует данные на репликах.
> Похожие задачи по Go
Когда логирование хорошо использовать, а когда лучше не использовать
Почему нельзя заменить WITH (CTE) на подзапрос в SQL
Какие виды репликаций существуют
Для чего лучше подходят синхронные и асинхронные репликации
> Похожие задачи по backend
Когда логирование хорошо использовать, а когда лучше не использовать
Почему нельзя заменить WITH (CTE) на подзапрос в SQL
Какие виды репликаций существуют
Для чего лучше подходят синхронные и асинхронные репликации
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью