О лекции

При разрыве сети нельзя быть сразу строго согласованным и всегда доступным. CP жертвует ответом, AP — единым снимком. PACELC добавляет тот же выбор в обычной работе: скорость или свежесть данных.

Распределённые системы architecture собеседование

CAP — про конкретный отказ. Узлы живы. Сеть между ними порвалась. Сообщения между двумя группами узлов теряются. Это и есть партиция.

В этот момент нельзя обещать сразу два свойства. Строгая согласованность: все видят одни и те же данные в одном порядке. Доступность: каждый живой узел отвечает на запрос, не дожидаясь остальных.

Пока сеть целая, система может быть и согласованной, и доступной. Спор начинается, когда связь пропала. Один Postgres с одним primary — не кластер CAP: там нет независимых реплик, которые должны отвечать клиенту при разрыве.

Пример. Два датацентра, кабель между ними перерезали. Если ждать вторую сторону — баланс счёта не разъедется, но часть клиентов получит ошибку. Если писать у себя — сервис отвечает, но в двух центрах уже разные суммы.

Буквы C, A и P

C ближе к линеаризуемости, чем к «когда-нибудь сойдёмся». Есть как будто один регистр. Операции идут в одном порядке, который совпадает с реальным временем.

A — не «всегда HTTP 200». Это способность узла, до которого вы ещё достучались, выдать ответ. Он не ждёт кворум и не ждёт вторую половину сети.

P — не «сеть иногда тормозит». Это split: две группы узлов не могут договориться неизвестно как долго. В проде так бывает: потеря линка между DC, зависший switch, split-brain в кластере.

CP и AP

  • CP: лучше отказать части клиентов, чем отдать разные ответы. ZooKeeper, etcd, типичная запись в кворум. Лидер недоступен — запись стоится.
  • AP: отвечаем, даже если реплики разошлись. Dynamo-стиль, многие кэши, DNS. Сходимость — отдельная работа: anti-entropy, last-write-wins, CRDT.
  • Tunable consistency (Cassandra, DynamoDB): на запрос выбираете, насколько близко к CP. Это ползунок на том же отрезке, а не «мы вне CAP».

Пример. Деньги и уникальный username почти всегда тянут к CP: лучше 503, чем два лидера. Лента и счётчик просмотров чаще живут как AP: покажут чуть старое и догонят позже.

package store

type Mode int

const (
	ModeCP Mode = iota // инвариант важнее ответа
	ModeAP             // ответ важнее единого снимка
)

func (c *Cluster) Put(key, val string) error {
	if c.partitioned && c.mode == ModeCP && !c.hasQuorum() {
		return ErrUnavailable // лучше 503, чем два лидера
	}
	return c.localWrite(key, val)
}

При партиции CP отказывает. AP пишет локально и сходится потом.

PACELC: жизнь без аварии

If Partition: A vs C. Else: Latency vs Consistency. Даже когда сеть целая, приходится выбирать.

Синхронная репликация на три датацентра бьёт хвост задержки: запись ждёт подтверждения с другого континента. Асинхронная реплика отвечает быстро. Но чтение с отстающей копии отдаст устаревшее.

Пример. Checkout ждёт синхронный кворум — плюс 80 мс на каждый заказ. Сняли кворум — заказ сохраняется сразу, но соседняя реплика ещё секунду показывает старую корзину. Это PACELC, не CAP: партиции нет, платите латентностью или окном рассинхрона.

Типичная ошибка

Сказать «Postgres это CA». Один primary — не распределённая система в смысле CAP. Async-реплика для читателей уже ближе к AP. Patroni и etcd вокруг Postgres тянут к CP для выбора лидера, а не делают саму БД магически CA.

На собеседовании

Опишите отказ и инвариант. Разрыв DC, потеря лидера. Деньги, уникальный username, лента. Добавьте PACELC: чем платите, когда аварии нет — хвостом задержки или окном рассинхрона.