Цели главы. Финальная глава устроена иначе: это семинар. Теория закончилась в главе 11; здесь мы трижды проектируем систему с нуля — счётчик, платежи, чат — проговаривая каждый выбор языком курса и явно называя его цену. Затем — каталог антипаттернов, методические указания преподавателю (глава рассчитана на семинарское занятие с обсуждением) и заключение курса. Цель — переход от «знаю алгоритмы» к «умею выбирать»: распределённые системы проектируются не подбором модных компонентов, а честными ответами на десяток вопросов.
Перед любой схемой — анкета; каждая строка отсылает к главе, где живёт ответ:
Задача: лайки к постам, пики до 100 тыс. записей/с на горячие посты, чтения на каждом просмотре, «не более одного лайка от пользователя».
Анкета: инвариантов глобальных нет — кроме «не более одного», но он локализуем: все операции над одной парой (пользователь, пост) должны попадать в одну логическую партицию. Значение счётчика терпит расхождение на секунды — итоговая согласованность. Горячие посты — определяющий фактор.
Решение: факт лайка хранится с ключом (пост, пользователь), а повтор с тем же ключом идемпотентен. Уникальное ограничение обеспечивает это только при единственном авторитетном шарде или согласованной группе реплик; принимать независимые записи в обеих половинах разделения и одновременно полагаться лишь на локальный UNIQUE нельзя. Для добавлений без удаления альтернативой служит сливаемое множество идентификаторов пользователей. Счётчик — производная величина: подсоленные ячейки (глава 8) или счёт по множеству, агрегируемый асинхронно и кэшируемый с меткой свежести; события лайков идут в журнал (глава 10) для лент и аналитики. При разделении точный счёт может временно отставать, а запись в недоступный авторитетный шард — получить отказ; выбранное поведение должно быть явным. Чего мы не делаем: линеаризуемого глобального счётчика — кворум на каждый лайк не требуется для точного устранения повторов по паре.
Задача: переводы между счетами, «не уйти в минус», внешний платёжный шлюз, полный аудит; поток умеренный, цена ошибки максимальна.
Анкета — зеркальная первому разбору: инварианты жёсткие и глобальные в пределах счёта, потери недопустимы, при разделении — отказывать (CP: лучше «попробуйте позже», чем двойное списание), повторы неизбежны (клиенты, шлюз) — идемпотентность всюду.
Решение: остаток счёта — линеаризуемая сущность; счёт — единица шардирования (глава 8), внутри шарда — ACID-транзакции поверх синхронной репликации (глава 5) либо распределённый SQL с консенсусом на группу (глава 9). Перевод между собственными шардами по возможности выполняется одной распределённой ACID-транзакцией: для денежного инварианта она предпочтительнее саги. Если границы систем не позволяют общую транзакцию, нужен устойчивый процесс с резервированием, двойной записью в неизменяемый бухгалтерский журнал, идемпотентными шагами, сверкой и ручным разбором неустранимых расхождений; компенсация не должна притворяться настоящей атомарностью. Вызов внешнего шлюза — отдельный шаг с ключом идемпотентности, outbox и обязательной сверкой. Журнал событий помогает аудиту, но не делает его бесплатным: нужны контроль доступа, срок хранения, проверка полноты и отдельные бухгалтерские проводки. Чего мы не делаем: 2PC с внешним шлюзом и микросервисную нарезку денежного ядра без необходимости — перевод внутри одной управляемой СУБД проще доказуемо корректен.
Задача: личные и групповые диалоги, миллионы пользователей, мобильные клиенты с плохой связью, «сообщения не теряются и не путаются».
Анкета: «не путаются» — порядок нужен в пределах диалога, глобальный не нужен ни для чего; «не теряются» — долговечность подтверждённого; клиент офлайн — норма (часть II статьи о надёжности); чтения — «хвост диалога», классика журнала.
Решение: диалог = ключ партиционирования = единица порядка — журнал сообщений, партиционированный по диалогу (главы 8, 10; Kafka-стиль или своя таблица с монотонным номером в диалоге). Для Kafka одного acks=all недостаточно: нужны подходящие min.insync.replicas, фактор репликации, размещение по зонам и запрет выбора лидера из отставшей реплики. Клиент ведёт локальную очередь исходящих с ключами идемпотентности и синхронизируется по номерам «всё после N». Статусы следует различать точно: «принято сервером», «доставлено на устройство» и «прочитано получателем» — разные подтверждения, а не синонимы долговечности записи. Причинность между диалогами (переслал из одного в другой) либо поддерживается метаданными главы 2, либо явно не гарантируется. Fan-out групповых чатов выполняют потребители журнала. Чего мы не делаем: глобального порядка всех сообщений и синхронной доставки офлайн-получателям.
Формат, обкатанный жанром: группа 8–15 человек, 2 занятия по паре. Кейс выдаётся заранее (варианты — ниже); на занятии один студент ведёт разбор у доски по анкете 12.1, группа атакует вопросами «что при разделении?», «что при повторе?», «покажи потерю» — роль немезиды из главы 11, перенесённая в дискуссию. Преподаватель вмешивается тремя вопросами: какой инвариант защищаем? какой главой это куплено? что мы сознательно НЕ гарантируем? Оценивается не «правильная архитектура» (их несколько), а явность компромиссов: решение без названной цены — незачёт независимо от модности. Хорошая заключительная точка курса — вернуть группу к сводной таблице границ главы 3 и попросить разметить на ней принятые в кейсе решения.
Курс прошёл дугу от «почему это трудно» к «как это строить»: модель и её честные пределы (часть I), согласие и его цена (часть II), масштаб и его механика (часть III), проверка и синтез (часть IV). Если сжать двенадцать глав в три принципа, получится так. Называйте предположения — каждая гарантия куплена моделью отказов, кворумом или тайм-аутом, и авария — это всегда встреча с неназванным предположением. Выбирайте согласованность по данным, а не по моде — спектр от линеаризуемости до CRDT существует затем, чтобы платить только за нужное. Не верьте — проверяйте — гарантии систем проверяемы вашими руками за вечер, и глава 11 оставила вам для этого полный инструментарий. А самое обнадёживающее в предмете мы отмечали ещё в обзоре: его фундамент — Лэмпорт, FLP, кворумы — не стареет; выучив его один раз, вы будете читать документацию систем, которых ещё не существует, как знакомый текст. Дальнейшее чтение: Клеппманн — настольной книгой; первоисточники из списков литературы — по мере столкновения с темами; и лучшая практика — та самая «свободная охота» из лабораторной 5.
Каждый кейс — по анкете 12.1; в скобках — узлы напряжения, вокруг которых должна закрутиться дискуссия.