Согласованность — обещание, что клиент увидит после записи. Strong, eventual, read-your-writes и monotonic reads различаются окном видимости и ценой координации. Это не то же самое, что транзакция в одной БД.
Распределённые системы architecture собеседование
Пока одна база и один процесс, «сохранил — сразу прочитал» кажется законом. Как только появляются реплики, кэш и несколько датацентров, закон пропадает.
Модель согласованности — контракт. Какие истории наблюдений допустимы. Это не синоним ACID. Это не свойство одной таблицы Postgres.
Клиент прав, когда говорит «я сохранил и открыл старое». Система выполнила более слабое обещание, чем он ждал.
Пример. Пользователь сменил аватар. Запись ушла на primary. Следующий запрос попал на отстающую реплику и вернул старую фотографию. Для него контракт сломан. Для eventual consistency это нормально, пока вы так и договорились.
Шкала обещаний
Линеаризуемость (strong): операции выглядят так, будто есть один регистр и глобальные часы. Это дорого. Нужен кворум или лидер. При партиции часто отказ.
Sequential consistency слабее: есть общий порядок, но он не обязан совпадать с реальным временем. Causal: «причина раньше следствия» видно. Независимые записи могут расходиться.
Eventual consistency обещает только сходимость. Если записи прекратятся или конфликты разрешатся, и реплики обменяются, они придут к одному состоянию. Пока поток записей идёт, клиент может увидеть смесь старого и нового.
Сессионные гарантии
- Read-your-writes. Свой write виден своим следующим read. Липкая сессия на primary, версия в токене, чтение с той реплики, которая уже применила запись.
- Monotonic reads. На одной сессии нельзя откатиться на более старую версию после более новой. Иначе профиль «прыгает» назад.
- Monotonic writes. Записи одного клиента не переставляются местами. Иначе «сменил email, потом имя» на реплике применится наоборот.
Пример. Read-your-writes не даёт «все в мире видят мою запись сразу». Только автор видит свой эффект. Два телефона одного человека без общего токена сессии не обязаны видеть одно и то же.
package profile
type Session struct {
UserID int64
SeenVer int64 // растёт на записи этого пользователя
}
func (s *Store) Get(sess Session) (Profile, error) {
p, err := s.replica.Get(sess.UserID)
if err != nil {
return Profile{}, err
}
if p.Version < sess.SeenVer {
return s.primary.Get(sess.UserID)
}
return p, nil
}
Версия в сессии ловит отсталую реплику. Иначе monotonic reads сломаны.
Где ломается в проде
Кэш перед БД ломает read-your-writes, если invalidate опаздывает: клиент читает ключ раньше, чем истечёт TTL.
Read replica без sticky-сессии — классика «сохранил профиль, открыл — старое».
Имеет смысл спрашивать: согласованность для какого ключа и какого клиента. Линеаризуемость корзины одного user_id дешевле, чем «все корзины мира в одном порядке».
Типичная ошибка
Сказать «у нас eventual consistency» и остановиться. Без окна сходимости, правила конфликтов и ответа на «что увидит автор через 200 мс» это не модель, а отказ обсуждать контракт.
На собеседовании
Назовите, чью сессию защищаете. Read-your-writes и monotonic reads — отдельные обещания. Их не даёт «eventual само». Кэш и replica — первые места, где контракт рвётся.