Признак найден, но устарел: как ввести срок годности данных для инференса

Модель рекомендует скидку постоянному покупателю. В хранилище есть его средний чек, число заказов и дата последней покупки. Запрос признаков завершился успешно, все поля заполнены. Только средний чек рассчитан неделю назад, когда ежедневная загрузка ещё работала. Для сервиса это обычное число, для решения о скидке — уже другая история покупок.

Проверка наличия значения пропускает такую ошибку. Нужен срок годности: сколько времени конкретный признак разрешено использовать после момента, к которому он относится. Это правило называют TTL. Его удобно проверять до вызова модели, чтобы устаревшее значение не успело превратиться в правдоподобный ответ.

У значения должны быть часы

Храните рядом с признаком время расчёта и время данных, вошедших в расчёт. Они отвечают на разные вопросы. Сегодняшняя задача могла успешно пересчитать вчерашний неполный снимок. Свежая запись в базе тогда маскирует старый источник. Для ежедневного среднего чека нас интересует момент актуальности исходных заказов; для задержки самого конвейера дополнительно пригодится время завершения задания.

Начните с бизнес-действия. Остаток для обещания доставки может устаревать за минуты, профиль интересов — за дни. Один общий лимит на весь вектор вынуждает либо принимать опасно старые остатки, либо без пользы отвергать медленно меняющиеся характеристики. Версию правил сохраните вместе с запросом: изменение TTL влияет на состав обслуженных клиентов.

Проверим границу, пропуск и дату из будущего

В примере момент решения фиксирован, все даты заданы в UTC. Значение считается пригодным при возрасте от нуля до лимита включительно. Будущее время выделяем отдельно: оно может означать сдвиг часов или ошибочную часовую зону. Python позволяет вычитать даты с явно заданным смещением, получая возраст как timedelta.

from datetime import datetime, timezone, timedelta
now = datetime(2026, 10, 29, 12, tzinfo=timezone.utc)
items = [
    ("stock", 7, now-timedelta(minutes=5), 300),
    ("spend", 4500, now-timedelta(days=2), 86400),
    ("interest", None, now, 86400),
    ("visits", 8, now+timedelta(seconds=1), 300),
]
def status(value, as_of, ttl):
    if value is None or as_of is None:
        return "missing"
    age = (now-as_of).total_seconds()
    if age < 0:
        return "future"
    return "fresh" if age <= ttl else "stale"
result = {name: status(v, ts, ttl) for name, v, ts, ttl in items}
print(result)
assert result == {"stock": "fresh", "spend": "stale",
                  "interest": "missing", "visits": "future"}

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

Что вернуть вместо старого признака

У сервиса остаются три осмысленных действия: дождаться обновления в пределах бюджета ответа, применить заранее проверенный резервный сценарий или отказаться от рекомендации. Выбор зависит от цены ошибки. Для скидки резервом может быть обычное публичное предложение; для обещания наличия лучше запросить актуальный остаток. Случайная подстановка среднего обучающей выборки меняет вход модели и требует отдельной оценки качества.

Если модель обучалась с пропусками, ещё проверьте, что она видела именно такой способ заполнения и соответствующий индикатор отсутствия. Старое значение и отсутствие значения имеют разный смысл. Нельзя просто сохранить вчерашнее число, выставить сегодняшнее время и считать проблему решённой: так исчезает возможность объяснить, на каких данных принято решение.

Для пакетного расчёта фиксируйте один момент принятия решения на весь запуск. Иначе записи у границы будут проходить проверку в начале партии и отвергаться в конце только из-за длительности обработки. После выпуска смотрите долю stale по каждому признаку и источнику. Одновременный скачок в нескольких полях одного источника даёт конкретный адрес для расследования.

До изменения лимита воспроизведите несколько прошлых дней и посчитайте, сколько запросов уйдёт в резерв. Отдельно сравните качество на свежих и устаревших данных там, где уже появился факт. Это связывает допустимый возраст с последствиями для пользователя, а не с удобным круглым числом в конфигурации.

Для признака с разными источниками сохраняйте источник явно. Агрегат по заказам магазина и агрегат по заказам всей сети могут иметь одинаковое название, но разные расписания обновления. Проверка возраста должна использовать правило именно того источника, из которого пришло значение.

Следующий шаг — включить проверку актуальности в общий путь подготовки признаков и приёмку сервиса. В курсе разбираются feature engineering, тестирование data pipeline, API-контракт и мониторинг моделей: вы сможете связать входные проверки с batch-инференсом и работой API. Откройте программу платного курса «Data Scientist: от данных до деплоя моделей».

Как учитывать версию данных в кэше прогнозов.

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

  • Data Scientist: от данных до деплоя моделей ↗

    Практический курс по Data Science: от постановки ML-задачи и работы с данными до inference API, CI/CD и мониторинга моделей в продакшне. Вы пройдёте EDA, feature engineering, обучение и оценку моделей, experiment tracking, error analysis и deployment — чтобы собирать полноценный рабочий контур вокруг ML-решения.