Строка outbox помечена как отправленная, а брокер о сообщении ничего не знает. Процесс умер ровно между двумя действиями — записью статуса и публикацией. Окно открыто в каждом тике, просто в обычном прогоне его никто не задевает.
Суть бага в порядке операций. Воркер сначала меняет статус строки на sent и только потом вызывает publish. Пока вызов возвращается нормально, всё сходится. Как только между двумя строками вклинивается сбой — падение, OOM, рестарт пода, — строка остаётся sent навсегда, а сообщение не уходит. Следующий запуск её пропустит: фильтр смотрит только на pending. Пользователь не получит ничего, в логах будет чисто.
Поэтому happy-path не доказывает ничего. Тест, который просто прогоняет воркер и печатает результат, показывает зелёный сценарий и молчит о потерянных строках. Дыру видно только тогда, когда падение воспроизводится намеренно.
Что нельзя смешивать
Задача держится на разнице между тремя вещами, и слабые решения рушатся именно здесь.
- Порядок записи внутри одного процесса. Тут всё детерминировано: сначала статус, потом публикация. Кандидат должен показать, что понимает этот порядок, а не угадывает его.
- Что увидит следующий запуск. Строка лежит в sent и никем не подхватится.
- Что вообще способен доказать двойник в памяти. Он не говорит о реальной долговечности ровно ничего: ни про диск, ни про то, как поведут себя два хоста, если один из них потеряет связь с другим. Двойник показывает только порядок событий внутри процесса — и выдавать его за гарантию сохранности нельзя.
Смешение этих слоёв и рождает фразу «у меня же всё работало». Работало — до момента, когда между записью статуса и вызовом publish появилась задержка.
Что требуется в задании
Кандидат получает buggy_worker.py и брокера-двойника в памяти. Задача — сделать баг наблюдаемым и предложить состояние, из которого система продолжит работу после краха.
Добавь kill-хук, который останавливает процесс после записи статуса. Покажи, что строка sent, а список сообщений брокера пуст. Предложи стейт-машину, которая переживёт такой крах. Публикация должна быть идемпотентной по id строки. Никакого реального сетевого брокера. Чистый прогон без kill-хука проходным не считается. Таймбокс — 90 минут.
Формулировка узкая намеренно. Оценивают одну стейт-машину, а не платформенный рефакторинг: ранний коммит, путь возобновления и ключ идемпотентности. 90 минут — это ограничение найма, а не оценка того, сколько времени займёт аккуратная правка в продакшене.
Как выглядит рабочая связка
Хук делают не через настоящий сигнал, а через исключение внутри процесса — так тест остаётся юнит-тестом и запускается одной командой на любой машине с Python. Порядок в тике получается такой: строка переводится в sent, счётчик попыток растёт, срабатывает хук и выбрасывает SystemExit. До публикации дело не доходит.
Ожидаемая диагностика незамысловатая: текст выхода, слово sent и пустой список сообщений. Видите ровно это — окно найдено. Запуск в два шага: сначала pytest по файлу с тестом, потом короткий прогон, который печатает состояние. Тестам не нужны ни приватные токены, ни хостовая консоль, ни конкретный вендор модели.
Тест на дыру фиксирует две вещи: статус строки равен sent и список сообщений пуст. На дырявом воркере такое утверждение проходит — оно описывает баг, а не исправление. А вот тест из образцового решения, поставленный против того же воркера, обязан упасть. Зелёный набор после удалённого хука — провал, даже если оставшиеся ассерты честно описывают счастливый путь.
Таблица проверок
| Проверка | Проход | Провал |
|---|---|---|
| Kill-хук после записи статуса | Строка sent, список брокера пуст | Прогнан только happy path |
| Состояние для возобновления | Статус in-flight отличим от sent | Обработчик исключения оставляет sent |
| Второй тик | Одно сообщение, затем sent | Второй тик пропускает строку навсегда |
| Идемпотентный ключ | Повтор не добавляет второе тело | Дубликат публикации переименован и оставлен рядом |
| Границы правки | Патч живёт внутри воркера и теста | Появляется новый продукт очереди или облачный аккаунт |
| Записка с доказательством | Сказано, чего двойник доказать не может | Заявлена долговечность на диске или fencing между хостами |
Оценка бинарная намеренно: дробные баллы в коротком упражнении тянут за собой эссе. Проход по одной строке не одалживает кредит соседней, иначе гладкая записка прикрывает пропущенный хук. И да — таблицу можно пройти и остаться слабым кандидатом по причинам, которые она не измеряет: широта системного дизайна, поведение в дежурстве, умение писать для неинженерной аудитории.
Типичные провалы
- Хук удалили, набор зелёный, процесс завершился с кодом 0 — и это подали как результат. Ноль на выходе не является заявлением о сохранности данных.
- Ассерт правили до победного. Проверяющий сравнивает присланный тест с условием задания, а не только с кодом возврата.
- Вторую публикацию просто переименовали и оставили в коде — дубликат никуда не делся.
- В решение притащили настоящую очередь или завели облачный аккаунт. Упражнение на такое не рассчитано.
- В записке из двойника в памяти вывели гарантию про диск или про разделение отказов между машинами. Двойник этого не проверяет.
Где подход не работает
Фикстур с хуком намеренно живёт в одном процессе. Шаг с отдельным процессом и настоящим сигналом существует как необязательное усложнение — увидеть баг он не нужен, потому что баг в порядке операций, а не в механике доставки сигнала. Тестовый код не импортируют в работающий сервис: хук выбрасывает SystemExit специально во время тика, и это сломает всё, что рядом.
Если нужен разбор с реальным кластером и брокером, берут другой пакет. Этот изолирует ранний коммит, путь возобновления и идемпотентный ключ — и останавливается там, где начинается инфраструктура.
На выходе у упражнения ровно три артефакта: воспроизводимый падающий тест, отдельное состояние in-flight между pending и sent и ключ идемпотентности по id строки. Всё, что выходит за эти рамки, — расползание объёма, за которое в таймбоксе 90 минут никто спасибо не скажет.
Комментарии (0)
Войдите, чтобы комментировать.
Пока нет комментариев. Будьте первым.