Зачем реплики и цикл обратной связи
Глава состоит из шести частей: вступление, дата-центричные модели консистентности, клиент-центричные, управление репликами, протоколы консистентности и любимый ведущими веб с кэшированием. Речь идёт именно о масштабировании: отказоустойчивость авторы разбирают отдельно, и клуб вернётся к ней позже.
Виталий перечисляет причины делать систему распределённой — сохранность и доступность данных при отказе узлов, подсетей и целых регионов, плановое обслуживание, а для систем планетарного масштаба — рост за пределы одного узла и близость данных к потребителю.
Ведущему понравилась логика авторов, которую он разложил по кубикам: производительность гонит в кэширование, кэширование приносит реплики, реплики поднимают вопрос консистентности — и цикл замыкается. В обсуждаемой схеме выход ищут в ослаблении требований к консистентности, но универсального рецепта нет: какая именно консистентность нужна, решают разработчики приложения, а не хранилище.
Модели авторы делят на два семейства. Дата-центричные держат в фокусе данные и процессы, которые их меняют; клиент-центричные, объясняет Виталий, выросли из мобильной эпохи, где изменения инициирует небольшое число клиентов, и вопрос сводится к тому, что конкретный клиент видел, что менял и что случится, когда он переподключится к другой реплике.
Ведущий свёл дата-центричные модели в табличку: в книгах их перечисляют подряд, и к середине списка теряешься, кто в кого вложен. Нотация общая — ось времени слева направо, процессы от P1 до P4, записи и чтения, причём чтение возвращает пустое значение, пока обновление не доехало.
Секвенциальная модель разрешает любое переупорядочение, сохраняющее порядок внутри процесса: важен не абсолютный момент записи, а единый порядок для всех читателей. В примере, где три процесса пишут каждый свою переменную и печатают две чужие, шесть операций дают 720 перестановок, и очень многие для хранилища невалидны.
От линеаризуемости к eventual consistency
Участники выделяют ограничение секвенциальной модели: гарантии для отдельных объектов не обеспечивают ту же гарантию для их совместного использования. Отсюда авторы выводят линеаризуемость и вводят перекрытие по времени: запись занимает интервал, и при наложении интервалов порядок зависит от того, где внутри окажется точка линеаризации; ведущему понравилось, что другие книги, по его памяти, идут обратным путём — от линеаризуемости к ослаблениям.
Причинная консистентность возвращает к векторным часам из прошлой главы: процесс прочитал значение и записал своё, и авторы допускают, что второе вычислено из первого. Виталий не находит в главе чёткого критерия причинности — за неё принимают сам факт связанных чтения и записи одного объекта, — и просит зрителей проверить, не пропустил ли он определение.
Связь к тому же ловится, только если общение идёт по тому же каналу: сосед у трансляции матча кричит про гол раньше картинки, а ведущий вспоминает книгу, которой в «Назад в будущее» пользовался антагонист, и ставки на спорт.
Ближе к разработчику оказывается группировка операций вокруг захвата эксклюзивного или неэксклюзивного доступа к сущности — по замечанию Виталия, это пользовательское описание причинных связей. Модель упрощена: в реальных базах и сетевых протоколах вроде NFS уровней блокировок больше и работает эскалация гранулярности — от записи к странице и диапазону значений и дальше к объекту или всей базе. На максимальном уровне изоляции база обязана выстроить транзакции в последовательность, и ведущего повеселило, что транзакция из одной операции сводится у авторов к прежней модели.
Eventual consistency означает, что при прекращении обновлений реплики в конце концов получат последние изменения и сойдутся. Участники объясняют модель на знакомых примерах: CDN раздают картинки и скрипты, для которых не всегда нужна самая свежая копия, а DNS использует многоуровневое кэширование ответов.
Континуальную модель с тремя осями отклонения — по численному отклонению значения, по времени и по порядку — ведущий предложил не копать: ось порядка он назвал самой запутанной и применений модели не знал. Виталий возразил: ограничение числа сделанных локально, но ещё не подтверждённых записей — это ровно то, как устроен локальный кэш, который сбрасывают при переполнении.
Клиентские гарантии, кворумы и CDN
Коммутативные операции переживают любой порядок: флажок, который можно взвести, но нельзя сбросить, или увеличения и уменьшения счётчика по командам двух пользователей дают один результат; стоит добавить сброс или границы вроде нуля и сотни — и итог снова зависит от порядка.
Ведущий вспоминает, как при открытии офиса в Ереване люди записывались в общую таблицу и одновременная запись в ячейку кого-то оставила без места: победила последняя запись.
Клиент-центричные модели ловят другое: клиент прочитал страницу, переключился через VPN и ставит лайк уже на другом сервере, поэтому предшествующие записи обязаны долететь раньше модификации. Отдельная боль — чтение собственных записей: лет десять назад разделение записи в мастер и чтения с реплик ломало логику приложений, которые не знали про лаг репликации.
Ведущему такое разделение потоков нравится за масштабирование, но цену он называет сам: после перевода между счетами свежую команду приходится применять к устаревшей модели чтения локально — иначе пользователь не увидит своих денег, а стриминг обновлений эту чёрную магию не убирает, а лишь сводит к минимуму.
Он же сравнивает подход с лямбда-архитектурой, где быстрый слой давал приблизительную дельту дня, вплоть до оценки уникальных посетителей через HyperLogLog. Виталий отвечает: сложность обменивают на снижение расходов, но баланс не всегда положительный.
При размещении реплик Виталий обращает внимание на трудность глобальных измерений времени доступа и загрузки каналов. Кроме технических метрик, выбор ограничивают цены и условия подрядчиков. Его шутка про решение, оставшееся от уволившегося инженера, подчёркивает разрыв между аккуратной моделью и эксплуатацией.
Из трейдингового опыта он вспоминает доставку изменений по multicast с перезапросом потерянных пакетов, из протоколов — синхронную репликацию через выделенный первичный объект и кворумы, уровень которых, добавляет ведущий, крутится прямо в запросе к Cassandra.
В описанном веб-сценарии Тинькофф персонализированную динамику не кэширует из-за таргетинга и тестов, а статику и конфиги мобильных приложений раздаёт через CDN, куда пайплайн из git кладёт файлы в хранилище и дёргает ручку обновления.
Что стоит унести с собой
- 01В обсуждаемой схеме кэширование помогает производительности, но создаёт реплики и вопросы консистентности. Выход ищут в ослаблении требований; допустимый уровень гарантий определяют потребности приложения.
- 02Гарантии секвенциальной консистентности не композируются; участники переходят от этого ограничения к линеаризуемости. Явное определение причинности в книге вызывает у Виталия вопрос, который он оставляет открытым для проверки.
- 03Коммутативные операции, например увеличение и уменьшение счётчика без границ, дают одинаковый результат при любом порядке. Сброс или ограничения счётчика возвращают зависимость от порядка; простая перезапись ячейки тоже может потерять чужое изменение.
- 04Разделение чтения и записи по репликам покупает масштабирование ценой ручной работы: свежую команду приходится применять к устаревшей модели чтения локально, иначе пользователь не увидит собственного перевода.