Прямой ответ
Feature store — слой, который хранит и отдаёт признаки: одинаково для обучения (офлайн, большие срезы истории) и для инференса (онлайн, одно значение по ключу с низкой задержкой). Главная решаемая проблема — рассогласование признаков между обучением и продакшеном (training-serving skew).
Как возникает skew без единого слоя:
- обучение считает признак SQL-запросом по складу, а сервис — кодом на Python по данным запроса;
- определения расходятся: разное окно агрегации, разная обработка пропусков, разный часовой пояс;
- модель в продакшене получает не те признаки, на которых обучалась, и качество падает без видимых причин.
Состав feature store:
- Офлайн-хранилище. Исторические срезы признаков с меткой времени; используется для обучения и бэктестов. Обычно таблицы в складе или файлы в lakehouse.
- Онлайн-хранилище. Актуальные значения по ключу с задержкой единицы миллисекунд: Redis, DynamoDB, специализированные хранилища.
- Реестр признаков. Описание признака: тип, владелец, частота обновления, источник, версия.
- Точечно-корректная выборка (point-in-time correctness). При сборе обучающей выборки для события берутся только те значения признаков, которые были известны на момент события. Без этого возникает утечка будущего — модель показывает отличное качество офлайн и проваливается в проде.
Когда feature store оправдан:
- несколько моделей используют общие признаки;
- признаки требуют дорогих вычислений, и их нужно переиспользовать;
- есть расхождения между офлайн и онлайн-логикой;
- команда растёт и нужна документация признаков.
Когда избыточен: одна модель, десяток признаков, вычисляемых на лету из данных запроса. В этом случае достаточно аккуратного кода и тестов.
Практика: организация признаков
Описание признака как контракт:
features:
- name: orders_last_30d
entity: customer_id
dtype: int
description: "Число заказов за 30 дней до момента события"
owner: data-team
freshness: "1h"
source: warehouse.orders
window: 30d
Point-in-time join при сборе выборки:
SELECT e.event_id, e.event_ts, f.orders_last_30d, f.avg_order_value
FROM events e
LEFT JOIN features_daily f
ON f.customer_id = e.customer_id
AND f.feature_date = (e.event_ts::date - 1); -- только данные, известные до события
Практические рекомендации:
- Разделяйте сырые данные и признаки. Признак — вычисленная величина с определённым окном и версией. Хранить «просто таблицу клиентов» и называть это feature store бесполезно.
- Версионируйте определения. Изменение окна с 30 на 60 дней — это новый признак или новая версия; старые модели не должны молча получить другое значение.
- Материализуйте тяжёлые признаки. Агрегации по большим таблицам удобно держать в материализованных представлениях с расписанием обновления.
- Проверяйте согласованность офлайн и онлайн. Регулярно сравнивайте значение признака из онлайн-хранилища со значением, вычисленным офлайн на ту же дату. Расхождение — индикатор skew.
- Следите за свежестью. Признак с задержкой обновления ведёт себя как другой признак; метрики свежести должны быть на дашборде — см. мониторинг моделей.
Онлайн-отдача признаков:
def get_online_features(customer_id: str) -> dict:
key = f"feat:customer:{customer_id}"
data = redis.hgetall(key)
if not data:
return DEFAULT_FEATURES # явная обработка отсутствия
return {k: decode(v) for k, v in data.items()}
Обработка отсутствия признаков должна быть явной и одинаковой при обучении и в сервисе: модель, обученная на заполненных значениях, при пустом входе ведёт себя непредсказуемо.
Практический сценарий: модель отлично работает офлайн и плохо в проде
Контекст: модель оттока с ROC-AUC 0.93 офлайн показывает 0.71 на реальном трафике.
- Проверка утечки. В обучающей выборке признак
support_tickets_last_7dбрался по состоянию на конец месяца, а не на момент события. Модель фактически «знала будущее»: обращения в поддержку часто следовали за решением об оттоке. - Вторая причина. Онлайн-значение
avg_order_valueсчиталось сервисом по последним 10 заказам, а офлайн — по всем заказам клиента. - Исправление. Пересобрали выборку с point-in-time join; унифицировали определения признаков и вынесли их в единый слой с описанием окна; добавили сверку офлайн/онлайн значений.
- Результат. Офлайн-метрика снизилась до 0.86, зато онлайн-качество совпало с офлайн-оценкой. Предсказуемость важнее красивой цифры на тесте.
- Профилактика. Тест на отсутствие признаков из будущего включён в пайплайн сборки выборки; автоматизация — в статье про MLOps-пайплайн.
Вывод: основная ценность feature store — не «хранилище признаков», а единое определение и point-in-time корректность.
Документация признаков как часть процесса. У каждого признака должен быть владелец и короткое описание: что считает, по какому окну, как обрабатывает пропуски, где используется. Без этого признаки дублируются под разными именами, а их изменения ломают чужие модели. Описание хранят рядом с определением признака, а не в отдельном документе: то, что не обновляется вместе с кодом, устаревает.
Миграция признака. Изменение определения (например, окно с 30 на 60 дней) безопаснее делать как новый признак с последующим переводом моделей, а не правкой существующего. Правка на месте молча меняет вход уже обученных моделей, и деградацию обнаруживают только по онлайн-метрикам.
FAQ
Можно ли заменить feature store материализованными представлениями? Частично: представления решают задачу хранения и вычисления, но не дают онлайн-отдачу с низкой задержкой, реестр признаков и версионирование. Для одной-двух моделей такого решения часто достаточно.
Как быть с признаками, которые нужны в реальном времени? Их считают в потоке и пишут в онлайн-хранилище; офлайн-версия того же признака должна вычисляться по той же логике. Расхождение в окнах или округлениях — типичный источник skew.
Что хранить дольше: признаки или сырые события? Сырые события хранят дольше, потому что определения признаков меняются, и историю нужно уметь пересчитать. Признаки — производный слой, который можно восстановить из событий.
Как оценить, что внедрение feature store оправдано? Посчитайте, сколько раз один признак переиспользуется и сколько инцидентов связано с расхождением определений. Если признаков меньше двух десятков и модель одна, затраты на внедрение не окупятся.
Источники: Feast docs, Wikipedia (feature engineering), PostgreSQL docs (materialized views) — ссылки в шапке.