Суть Читай обработчики Raft RPC, шаг state machine и реальный лог AppendEntries, затем выбирай поведение, баг или фикс с наибольшим рычагом, который senior-инженер делает первым.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги Raft прячутся в четырёх местах: consistency check в AppendEntries, продвижение commit index, правило выдачи голоса и state machine, применяющая лог. Читай каждый сниппет так, как читал бы на ревью или в инциденте, и выбирай то, что senior-инженер пометит первым.
Цель
Потренируйся читать протокол там, где он реально живёт — в обработчиках RPC и строках лога — и замечать off-by-one, отсутствующую проверку и сигнатуру расхождения, отделяющие корректный Raft от портящего данные.
Сниппет 1 — consistency check в AppendEntries
// follower handling AppendEntriesfunc (r *Raft) handleAppend(a AppendEntries) AppendReply { if a.Term < r.currentTerm { return AppendReply{Term: r.currentTerm, Success: false} } // BUG IS HERE: appends without the prevLog check r.log = append(r.log[:a.PrevLogIndex+1], a.Entries...) r.fsync() return AppendReply{Term: r.currentTerm, Success: true}}
Викторина
Completed
В этом обработчике follower'а нет одной проверки, на которой держится безопасность Raft. Какой именно и что ломается без неё?
Heads-up Гигиена конкурентности важна, но дефект уровня протокола — отсутствие совпадения prevLog. Даже однопоточно этот обработчик вставит поверх расходящегося префикса и сломает Log Matching.
Heads-up AppendEntries с равным term — это как раз нормальный случай от текущего лидера. Их отвержение остановило бы всю репликацию. Дефект — отсутствующая проверка prevLogIndex/prevLogTerm.
Heads-up Этот слайс слепо доверяет индексу лидера, не подтверждая совпадение префикса. Если запись follower'а на PrevLogIndex имеет другой term, вставка портит историю. Проверка prevLog должна гейтить append.
Сниппет 2 — продвижение commit index
// leader, after collecting matchIndex[] from followersfunc (r *Raft) advanceCommit() { for n := r.commitIndex + 1; n <= r.lastIndex(); n++ { count := 1 // count self for _, m := range r.matchIndex { if m >= n { count++ } } if count >= r.majority() { r.commitIndex = n // committed by majority } }}
Викторина
Completed
Majority реплицировал запись n, поэтому код её коммитит. Но статья Raft добавляет ещё одно условие перед тем, как лидер может продвинуть commitIndex до n. Какое и почему?
Heads-up Ожидание всех узлов никогда не требуется и убило бы доступность. Недостающее условие — правило текущего term, а не более сильный quorum.
Heads-up Персист commitIndex — деталь реализации; commitIndex пересчитывается из лога при рестарте. Пробел уровня протокола — правило коммита текущего term.
Heads-up Для записей прошлого term — нет. Прямой коммит реплицированной, но прошлого-term записи и есть классический баг figure-8; защита текущего term его предотвращает.
Этот обработчик голоса соблюдает один-голос-на-term, но пропускает проверку. Что теперь может сделать кандидат и какое свойство безопасности отказывает?
Heads-up Проверка votedFor уже не даёт голосовать дважды в одном term. Недостающее — сравнение актуальности лога, защищающее закоммиченную историю, а не подсчёт голосов.
Heads-up Election Safety держится: один голос на узел на term плюс majority дают максимум одного победителя за term. Здешний пробел даёт победить кандидату с устаревшим логом, ломая вместо этого Leader Completeness.
Heads-up Сравнение term лишь упорядочивает сообщения; оно ничего не говорит о полноте лога кандидата. Именно явная проверка lastLogTerm/lastLogIndex защищает полноту.
Сниппет 4 — реальный лог AppendEntries
15:42:08 INFO raft: AppendEntries -> D failure (mismatch at idx=400100 term=12 vs follower idx=400100 term=11)15:42:09 INFO raft: AppendEntries -> D retry prevIdx=400050 (decremented)15:42:09 INFO raft: AppendEntries -> D failure (mismatch at idx=400050 term=12 vs follower idx=400050 term=10)15:42:10 INFO raft: AppendEntries -> D retry prevIdx=399000 (decremented)15:42:10 INFO raft: AppendEntries -> D failure (mismatch at idx=399000 term=12 vs follower idx=399000 term=8)15:42:11 WARN raft: D has diverged extensively; consider InstallSnapshot
Викторина
Completed
Лидер раз за разом декрементирует nextIndex для follower'а D по одному шагу. Читая этот лог, какой диагноз и правильная операционная реакция?
Heads-up Декремент — это корректно работающий consistency check: он откатывается к последнему совпадающему индексу. Проблема — масштаб расхождения, требующий InstallSnapshot, а не фикс кода.
Heads-up Несовпадающие старые term'ы — нормальная сигнатура долго оффлайн-follower'а, а не атака. Фикс — вернуть его в синхрон через snapshot, а не предполагать Byzantine-поведение.
Heads-up D — отстающий follower на починке, а не причина выборов здесь. Правильное действие — InstallSnapshot, чтобы прекратить линейный откат.
Итог
Четыре обработчика несут безопасность Raft. AppendEntries обязан гейтить каждую вставку совпадением prevLogIndex/prevLogTerm, иначе логи тихо расходятся. Лидер вправе напрямую коммитить лишь запись своего текущего term — подсчёт реплик на записи прошлого term снова открывает перезапись figure-8. RequestVote обязан сравнивать lastLogTerm/lastLogIndex, иначе устаревший кандидат побеждает и ломает Leader Completeness. А длинная серия пошаговых декрементов nextIndex с падающими term’ами — сигнатура расхождения, означающая «переключайся на InstallSnapshot». Читай протокол там, где он живёт — в обработчиках и логах — и это ровно те строки, которые senior-инженер проверяет первыми.