Пакетный прогноз после сбоя: как продолжить расчёт без смешения версий

Большой пакет прогнозов может упасть после записи половины результата. Покажем возобновление с фиксированными данными и версией модели, чтобы повтор не создавал дубликаты и не смешивал расчёты.

Повтор должен продолжать то же задание

Ночной процесс рассчитывает прогнозы для миллионов клиентов. После сбоя его запускают снова. Если данные за это время обновились, а модель автоматически взяла новый артефакт, итоговый файл окажется смесью двух вычислений. Даже при сохранённых id такой результат трудно объяснить.

У задания должны быть зафиксированы снимок входных данных, версия модели вместе с преобразованиями, схема выхода и список частей. Эти сведения образуют manifest задания. Каждый результат связан с job id и id объекта. Повтор с другой моделью или снимком становится новым заданием, даже если список клиентов похож.

Разбейте вход на устойчивые части. Номер части должен означать один и тот же набор id при повторе. Пагинация по изменяющейся таблице через offset этого не гарантирует: новые строки сдвигают границы. Лучше работать с неизменяемым снимком и явными диапазонами либо заранее сохранённым списком частей.

Свяжем запись результатов и отметку завершения

В примере SQLite хранит прогнозы и завершённые части. Для каждой части запись результатов и отметка done выполняются в одной транзакции. Повтор уже завершённой части ничего не меняет. Внутри записи также есть уникальность пары job/id, чтобы технический повтор вставки не создавал второй прогноз для того же объекта.

База работает в памяти и содержит один job с фиксированным снимком и версией. Функция счёта условная: умножение признака на два позволяет проверить именно логику возобновления. В рабочем процессе на её месте вызывается закреплённый inference pipeline, а метаданные задания проверяются перед чтением частей.

import sqlite3
db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE predictions(job TEXT, id TEXT, value REAL, PRIMARY KEY(job,id));
CREATE TABLE done(job TEXT, part INTEGER, PRIMARY KEY(job,part));
""")
job = "snapshot-2026-10-01_model-v3"
parts = {0: [("a", 2), ("b", 3)], 1: [("c", 4)]}
def process(part):
    if db.execute("SELECT 1 FROM done WHERE job=? AND part=?", (job, part)).fetchone():
        return False
    with db:
        db.executemany("INSERT INTO predictions VALUES(?,?,?) ON CONFLICT(job,id) DO NOTHING",
                       [(job, key, x*2) for key, x in parts[part]])
        db.execute("INSERT INTO done VALUES(?,?)", (job, part))
    return True
assert process(0)
assert not process(0)
assert process(1)
result = db.execute("SELECT id,value FROM predictions WHERE job=? ORDER BY id", (job,)).fetchall()
print(result)
assert result == [("a", 4.0), ("b", 6.0), ("c", 8.0)]
assert db.execute("SELECT COUNT(*) FROM done").fetchone()[0] == 2

После повтора части 0 в таблице остаются три результата, а не пять. Отметка done появляется вместе с прогнозами. Если транзакция прервётся до фиксации, ни частичный результат, ни завершение части не станут видны как успешные. Именно эта связь важнее отдельного файла «последний обработанный номер».

В примере один исполнитель. Для параллельных работников нужна атомарная выдача части и защита от одновременной обработки; одной предварительной проверки done недостаточно. При записи в объектное хранилище обычно используют отдельные неизменяемые файлы частей и manifest завершения. Свойства транзакции SQLite нельзя автоматически перенести на два независимых сервиса.

ON CONFLICT DO NOTHING тоже имеет условие: job/id должен однозначно определять прогноз. Если один вход случайно попал в две части с разными признаками, молчаливый пропуск конфликта скроет проблему. До запуска проверьте уникальность id в снимке, а при необходимости сохраняйте хеш входа и сравнивайте его при повторной записи.

Перед выдачей результата потребителю проверьте завершённость всех ожидаемых частей, число уникальных объектов, ошибки и пропуски. Затем публикуйте целиком согласованную версию результата, например сменой указателя на готовый manifest. Продолжение после сбоя должно восстанавливать одно понятное вычисление, а потребитель — видеть его завершённый снимок.

Для следующего шага пригодится связь пакетного прогноза с id строки.

В платном курсе «ML Engineer / Data Scientist: табличные модели в продакшн» вы соберёте воспроизводимый пайплайн пакетного инференса и сохраните его артефакты. Эти результаты можно использовать как основу повторяемого ночного вычисления. Откройте программу курса.

Продолжить на практике

  • ML Engineer / Data Scientist: табличные модели в продакшн ↗

    Практический курс для тех, кто хочет получить навыки ML Engineer / Data Scientist на реальных табличных задачах. В курсе Вы не просто обучите CatBoost/LightGBM, а соберёте полноценный production-style пайплайн: подготовка данных, feature engineering (генерация признаков), корректная валидация, защита от leakage (утечек), тюнинг через Optuna, интерпретация через SHAP, batch-инференс и REST API для предсказаний.