Распределённые системы
Часы: построй хранилище с детекцией конфликтов
Читать, что last-write-wins роняет данные на сбитых часах — не то же самое, что увидеть это вживую и затем заставить прекратиться. Построй крошечное реплицированное хранилище ключ-значение, впрысни clock skew, воспроизведи тихую потерю, затем подставь vector clock и докажи, что конкурентные записи выживают как siblings, а не одна из них исчезает.
Преврати ментальную модель юнита в работающий код: воспроизведи тихую потерю данных от clock skew под LWW, реализуй сравнение happens-before через vector clock, детектируй конкурентность и продемонстрируй, что ни одна конкурентная запись не теряется молча.
Построй небольшое in-memory реплицированное KV-хранилище с двумя стратегиями разрешения конфликтов — настенный last-write-wins и vector clock — воспроизведи тихую потерю данных под LWW на сбитых часах, затем покажи, что vector clock детектируют конкурентные записи и держат обе как siblings.
- Воспроизводимый демо-скрипт, который под LWW с впрыснутым skew печатает отброшенную запись и успешный ack — сигнатуру тихой потери данных.
- Тот же скрипт под vector clock печатает 'concurrent' для двух записей и возвращает обе siblings, без молча отброшенных записей.
- Тесты проходят, показывая, что happens-before пары схлопываются в одну версию, а concurrent пары выносятся как siblings.
- Текст в один абзац, объясняющий, почему LWW потерял запись (наибольший timestamp принадлежит самым быстрым часам, а не самой поздней записи) и почему vector clock — нет (они сравнивают причинно, а не по физическому времени).
- Замерь стоимость метаданных: логируй размер вектора при росте с 3 до 50 узлов, затем реализуй пруниг (кэп записей или dotted version vectors) и покажи, что siblings всё ещё корректно детектируются при ограниченных метаданных.
- Добавь tombstone с будущей датой: удали K с timestamp на час вперёд под LWW и покажи, что он подавляет каждую более позднюю реальную запись; затем покажи, что хранилище с vector clock трактует удаление просто как ещё одну конкурентную версию.
- Добавь стратегию Lamport clock третьим вариантом и продемонстрируй пробел: она даёт чистый тотальный порядок, но молча выбирает победителя для конкурентной пары (без siblings), располагаясь между LWW и vector clock.
- Набросай (или реализуй мок) путь commit-wait в стиле TrueTime: назначь commit timestamp, пережди настраиваемое ε и аргументируй, что external consistency дала бы здесь по сравнению с подходом vector clock.
Это цикл за каждым реальным решением о разрешении конфликтов: воспроизведи сбой (LWW молча роняет более новую запись, потому что наибольший timestamp принадлежит самым быстрым часам), затем почини его правильным примитивом (vector clock сравнивают причинно, детектируют конкурентность и держат siblings вместо угадывания). Построив это один раз — впрыск skew, сравнение happens-before, вынос siblings и стоимость метаданных, которую платишь за детекцию — ты превращаешь ментальную модель юнита в инстинкт, к которому потянешься, когда продакшен-хранилище начнёт терять записи.