Признак найден, но устарел: как ввести срок годности данных для инференса
Модель рекомендует скидку постоянному покупателю. В хранилище есть его средний чек, число заказов и дата последней покупки. Запрос признаков завершился успешно, все поля заполнены. Только средний чек рассчитан неделю назад, когда ежедневная загрузка ещё работала. Для сервиса это обычное число, для решения о скидке — уже другая история покупок.
Проверка наличия значения пропускает такую ошибку. Нужен срок годности: сколько времени конкретный признак разрешено использовать после момента, к которому он относится. Это правило называют 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-решения.
