Как организовать чтение и запись в разные шарды в распределенной системе?

Ответ

Да, это стандартный и очень распространенный паттерн в распределенных системах, который называется Primary-Replica (или Master-Slave). Он позволяет масштабировать нагрузку и повысить отказоустойчивость.

Основной принцип:

  1. Запись (Write): Все операции записи (INSERT, UPDATE, DELETE) направляются на один узел — Primary (мастер). Это гарантирует консистентность данных.
  2. Репликация: Primary асинхронно или синхронно копирует (реплицирует) изменения на один или несколько других узлов — Реплики (slaves/secondaries).
  3. Чтение (Read): Операции чтения (SELECT) могут быть распределены между репликами. Это снимает нагрузку с Primary и позволяет обрабатывать гораздо больше запросов на чтение.

Ключевой компромисс: Производительность vs. Консистентность

Из-за того, что репликация занимает время (даже миллисекунды), возникает лаг репликации. Это означает, что данные, только что записанные в Primary, могут быть еще не доступны на реплике. Этот эффект называется Eventual Consistency (согласованность в конечном счёте).

Пример реализации на Go с MongoDB:

В MongoDB можно указать Read Preference (предпочтение чтения), чтобы направить запрос на реплику.

import (
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)

// Запись всегда идет в Primary шард по умолчанию
_, err := client.Database("test").Collection("users").InsertOne(ctx, user)
if err != nil { /* обработка ошибки */ }

// Чтение можно направить на Secondary (реплику)
// Это снизит нагрузку на основной узел
opts := options.Find().SetReadPreference(readpref.SecondaryPreferred())
cursor, err := client.Database("test").Collection("users").Find(ctx, bson.M{}, opts)

Что необходимо учитывать:

  • Лаг репликации: Критически важные данные сразу после записи лучше читать с Primary, чтобы избежать получения устаревшей информации (проблема read-your-writes).
  • Балансировка нагрузки: Нужен механизм (часто встроенный в драйвер или прокси) для распределения запросов на чтение между доступными репликами.
  • Отказоустойчивость: Система должна уметь обрабатывать отказ Primary (выбрать новую Primary из реплик) или отказ реплики (исключить ее из ротации).