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

Ответ

Да, это стандартный и очень распространенный паттерн в распределенных системах, который называется 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 из реплик) или отказ реплики (исключить ее из ротации).

Ответ 18+ 🔞

Ага, вот эта штука, когда один главный, а остальные как подпевалы! Primary-Replica, или по-нашему — мастер и его подмастерья. Классика жанра, хуле. Весь интернет на этом стоит, а ты не знал?

Смотри, как это работает, чтобы мозг не ебал:

  1. Писать — только хозяину. Все эти INSERT, UPDATE, DELETE — летят прямиком в Primary (мастер). Он тут царь и бог, чтобы бардака не было. Один источник правды, ёпта!
  2. Размножаем данные. Мастер не жадный — он тут же начинает раскидывать копии своих изменений по Репликам (слейвам). Может быстро, может с задумчивостью, но раскинет.
  3. Читать — можно у холопов. А вот чтобы выбрать что-то (SELECT), можно идти к любой реплике. Снимаем нагрузку с мастера, он и так устал, пиздец. Читателей-то обычно дохуя больше, чем писателей.

Но есть подвох, сука!

Между пунктом 1 и 3 — пропасть. Называется лаг репликации. Ты только что записал в мастера, а реплика ещё чай пьёт и твою запись не видит. Это и есть Eventual Consistency — «со временем, блядь, всё согласуется». Не сразу, а когда дойдёт.

Вот тебе пример на Go, смотри не запоролись:

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

// Писать — всегда только в главного. Без вариантов.
_, err := client.Database("test").Collection("users").InsertOne(ctx, user)
if err != nil { /* тут всё хуёво */ }

// А читать можно и с реплики, чтобы мастера не ебать.
// SecondaryPreferred — значит «дай реплику, если жива, а нет — ладно, иди к мастеру».
opts := options.Find().SetReadPreference(readpref.SecondaryPreferred())
cursor, err := client.Database("test").Collection("users").Find(ctx, bson.M{}, opts)

А теперь, блядь, главное что запомнить:

  • Лаг — он везде. Только что записал данные и тут же хочешь прочитать? Читай с мастера, а то получишь пиздюлину от пользователя, который кричит «ГДЕ МОИ ИЗМЕНЕНИЯ, СУКА?».
  • Балансировщик — твой друг. Кто-то же должен умно раскидывать запросы на чтение по всем репликам, а не ты вручную. Обычно драйвер или какой-нибудь прокси это умеют.
  • Всё падает. Мастер сдох? Надо выбрать нового из реплик. Реплика откинулась? Выкидываем её из игры, пока не починят. Отказоустойчивость, ёпта, не просто слово.

Вот и вся магия, ничего сложного. Просто не наебнись с лагами, а то будет тебе волнение ебать.