> Как реализована репликация таблиц (Go)

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

Компании: Сбер

Стек: Go

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

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

Репликация таблиц - это механизм синхронного или асинхронного копирования изменений данных с primary-реплики на standby-реплики. В Go-экосистеме она реализуется через логическую репликацию (например, PostgreSQL logical decoding) или через паттерн outbox с публикацией событий в брокер. Ключевые аспекты: гарантии консистентности, обработка конфликтов, мониторинг лага репликации и идемпотентность применения изменений на стороне реплики.

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

Репликация таблиц бывает двух основных типов:

Физическая репликация - копирование WAL (write-ahead log) на уровне байтов. Реплика получает точную копию данных, включая схему и внутреннее представление. Используется для failover и read scaling. В Go обычно не требует специального кода - управляется на уровне СУБД.

Логическая репликация - декодирование WAL в поток изменений строк (INSERT/UPDATE/DELETE). Позволяет:

  • реплицировать только выбранные таблицы
  • трансформировать данные по пути
  • реплицировать между разными версиями СУБД

В Go логическая репликация реализуется через:

  • pglogical / pgoutput для PostgreSQL
  • библиотеки типа jackc/pglogrepl для подписки на изменения
  • паттерн outbox: запись изменений в отдельную таблицу внутри той же транзакции, затем публикация в Kafka/NATS через отдельный процесс

Ключевые trade-off:

  • Синхронная vs асинхронная: синхронная гарантирует отсутствие потерь при failover, но увеличивает latency записи. Асинхронная быстрее, но возможна потеря последних транзакций.
  • Идемпотентность: реплика должна корректно обрабатывать повторную доставку одного и того же события.
  • Схема: при изменении схемы таблицы (DDL) необходимо согласованное обновление на всех репликах.
  • Конфликты: при multi-primary репликации возможны конфликты записей - нужна стратегия разрешения (last-write-wins, merge, отказ).

На практике

Для Go-сервисов типичная схема:

  1. Основной сервис пишет в PostgreSQL.
  2. Отдельный Go-процесс (replicator) подписан на логическую репликацию через pgoutput.
  3. Полученные изменения сериализуются в protobuf/JSON и публикуются в Kafka.
  4. Потребители (search index, cache, аналитика) применяют изменения с идемпотентной обработкой.

Важные аспекты на практике:

  • Лаг репликации - метрика, показывающая разницу между текущим WAL-позицией primary и позицией реплики. Нужен алертинг.
  • Backpressure - если потребитель медленный, репликатор должен уметь притормаживать чтение из WAL, не теряя данные.
  • Graceful shutdown - при остановке репликатор должен корректно сохранить свою LSN-позицию.
  • Мониторинг - через Prometheus: количество обработанных событий, ошибки декодирования, время обработки.

Пример кода

Упрощённый пример подписки на логическую репликацию PostgreSQL на Go:

GO
package main
import (
"context"
"fmt"
"log"
"github.com/jackc/pglogrepl"
"github.com/jackc/pgx/v5/pgconn"
)
func main() {
conn, err := pgconn.Connect(context.Background(), "postgres://user:pass@localhost/db")
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
plugin := "pgoutput"
opts := pglogrepl.StartReplicationOptions{
PluginArgs: []string{
"proto_version '1'",
"publication_names 'my_publication'",
},
}
err = pglogrepl.StartReplication(
context.Background(),
conn,
"my_replication_slot",
0, // LSN: 0 означает "с текущей позиции"
opts,
)
if err != nil {
log.Fatal(err)
}
for {
msg, err := conn.ReceiveMessage(context.Background())
if err != nil {
log.Fatal(err)
}
switch m := msg.(type) {
case *pgconn.CopyData:
switch pglogrepl.MessageType(m.Data[0]) {
case pglogrepl.PrimaryKeepaliveMessageID:
// keepalive - можно ответить standby status update
case pglogrepl.XLogDataID:
xld, err := pglogrepl.ParseXLogData(m.Data[1:])
if err != nil {
log.Fatal(err)
}
processChanges(xld.WALData)
}
}
}
}
func processChanges(data []byte) {
// Декодирование pgoutput-сообщений
// и применение к целевой системе
fmt.Printf("received %d bytes of changes\n", len(data))
}

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

Начните с классификации: физическая vs логическая репликация. Затем переходите к конкретике для Go: как вы строите пайплайн, какие библиотеки используете, как обеспечиваете идемпотентность. Обязательно упомяните мониторинг лага и обработку сбоев.

Если спрашивают про конкретную СУБД - уточните, про какую именно. Для PostgreSQL расскажите про pgoutput и publication/slot. Для MySQL - про binlog и go-mysql-org/go-mysql.

Покажите понимание trade-off: синхронная репликация дороже, но надёжнее. Асинхронная быстрее, но требует компенсационных механизмов.

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

  • Понимание разницы между физической и логической репликацией.
  • Знание практических проблем: лаг, идемпотентность, обработка DDL.
  • Умение проектировать отказоустойчивый пайплайн.
  • Понимание гарантий консистентности (at-least-once, exactly-once).
  • Опыт работы с реальными инструментами (pglogrepl, Kafka, Debezium).

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

  • Путаница между репликацией и шардированием.
  • Утверждение, что логическая репликация даёт exactly-once доставку - на практике это at-least-once, идемпотентность нужно реализовывать самостоятельно.
  • Игнорирование проблемы лага репликации и его влияния на чтение.
  • Отсутствие стратегии обработки конфликтов при multi-primary.
  • Предложение использовать физическую репликацию для трансформации данных - это не её задача.
  • Забывают про необходимость обработки DDL-изменений в схеме таблиц.

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

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