ML Pipelines and Feature Stores
Feature pipelines, training pipelines, inference pipelines. Feast for feature stores. Airflow and Prefect for orchestration. How production ML actually runs.
A Jupyter notebook trains a model once. A pipeline trains it every day, on fresh data, reproducibly, without anyone running it manually. That is the difference between a prototype and a production ML system.
Every ML model at DoorDash, Stripe, and Amazon runs on a pipeline. The delivery time prediction model retrains every night on the day's orders. The fraud detection model retrains weekly as new fraud patterns emerge. The product recommendation model retrains daily as inventory changes. None of these happen by someone running a notebook — they are automated, scheduled, monitored, and alerting pipelines.
Three types of pipelines work together in every production ML system. The feature pipeline extracts raw events from databases and streams, transforms them into model-ready features, and writes them to a feature store. The training pipeline reads features from the store, trains the model, evaluates it, and registers it if it passes quality gates. The inference pipeline reads features for a specific prediction request, loads the registered model, and serves a prediction in milliseconds. These three must stay in sync — if the feature pipeline changes how it computes a feature, the training and inference pipelines must change together or the model silently degrades.
A restaurant kitchen has three pipelines: the supply pipeline (ingredients arrive, are prepped, and stored in the walk-in fridge), the recipe pipeline (chefs create and test new dishes using stored ingredients), and the serving pipeline (orders come in, ingredients are pulled from storage, dishes are prepared and served). The walk-in fridge is the feature store. If the supply pipeline changes how the vegetables are cut (feature engineering), the recipes (training) and serving (inference) must use the same cut — or the dish comes out wrong.
Training-serving skew — when features are computed differently at training time vs inference time — is the number one silent failure mode in production ML. Feature stores exist specifically to prevent it by computing features once and serving the same values to both pipelines.
Feature pipeline, training pipeline, inference pipeline — how they connect
Feature stores — one definition, consistent values, training and serving
A feature store solves the most common production ML problem: the feature computed in the training notebook is not the same feature computed in the serving API. The feature store is a central registry where features are defined once, computed once, and read by both the training pipeline and the inference service. Training and serving are always consistent.
Feature stores have two components. The offline store (typically S3, BigQuery, or Parquet files) holds the full historical feature values — used for training. It supports time-travel queries: give me the value of this feature for this entity as of a specific past timestamp. This is critical for preventing data leakage. The online store (typically Redis or DynamoDB) holds only the latest feature values — used for real-time inference. Writes to both are handled by the feature pipeline's materialisation job.
Prefect — define pipelines as Python, schedule and monitor from the UI
Orchestrators schedule pipeline runs, handle failures, retry failed steps, send alerts, and provide a dashboard of what ran, when, and what failed. Airflow is the industry standard but requires significant infrastructure. Prefect offers the same capabilities with far simpler setup — decorate Python functions with @task and @flow, run them locally or in the cloud, and get a full observability dashboard.
Airflow DAGs — the pattern used at DoorDash, Amazon, and Stripe
Apache Airflow is the most widely deployed ML orchestrator in the industry. Every major tech company runs Airflow for data and ML pipelines. An Airflow DAG (Directed Acyclic Graph) defines the tasks and their dependencies as Python code. Airflow schedules DAG runs, retries failures, sends email alerts, and provides a rich UI showing every run's status.
Feast — define features once, serve consistently to training and inference
Every common ML pipeline mistake — explained and fixed
Why feature stores exist — the skew problem that made platform teams build them
Feature stores were not invented because they sounded architecturally elegant. Uber built Michelangelo Palette, Airbnb built Zipline, and Twitter and Shopify adopted Feast for the same blunt reason: teams kept shipping models that scored well in evaluation and then quietly underperformed in production, and the root cause traced back to the same place almost every time — the feature computed in the training notebook was not exactly the feature computed in the serving path. A feature store is the fix a platform team builds once, so every model team stops re-discovering the same bug independently.
In practice this usually shows up as an internal platform, not a library any one model team owns. A central ML platform team runs the feature store as a product: they own the offline/online sync job, the schema registry, and the on-call rotation for materialisation failures. Model teams are customers — they define feature views, consume features by name, and never touch the underlying infrastructure. That separation is what actually prevents skew at scale: nobody can quietly reimplement a feature differently in their own notebook, because the feature store is the only sanctioned way to get it.
The offline/online split is also a real infrastructure decision with real tradeoffs, not a diagram convention. The offline store — S3, BigQuery, Snowflake — is optimised for scanning huge historical ranges cheaply, exactly what training needs, but a single lookup is far too slow for a live request. The online store — Redis, DynamoDB, Cassandra — is optimised for the opposite: single-key lookups in low single-digit milliseconds, but it is expensive to store the full history there and nobody tries to. Every production feature store is really two databases plus a materialisation job that keeps promoting fresh values from one to the other on a schedule.
Five things people get wrong about ML pipelines and feature stores
A plain database gives you storage and lookups; it does not give you the specific capabilities that make a feature store worth building. Point-in-time correct joins for training data, a synchronised pair of an offline store and an online store that agree on values, a schema registry so feature definitions are shared rather than reinvented per team, and a materialisation pipeline that keeps the online copy fresh — none of that comes for free from Postgres or S3 alone. The feature store is the system built around a database (often two databases) specifically to solve training-serving consistency, which a database on its own has no concept of.
Having a timestamp column does not by itself prevent leakage — the join logic has to actually use it correctly, and it is easy to get subtly wrong. A common mistake is joining on the feature's created_timestamp instead of the event's event_timestamp, or forgetting that a feature's time-to-live means a value from ninety days ago should not be joined against a training event today. Real point-in-time correctness requires an as-of join that explicitly asks 'what was true at time T for this entity,' implemented deliberately — Feast's get_historical_features() does this work, but a manual pandas merge on entity ID alone, even with timestamps sitting right there in the columns, will silently leak future values if the join does not filter on them.
Reuse saves engineering time but creates a real coupling cost: every model that consumes a shared feature is now depending on its exact definition staying stable. Change how restaurant_avg_delivery_time is computed to fix a bug for the delivery-time model, and the fraud model and the recommendation model that also consume it can silently degrade without anyone on those teams touching a line of their own code. This is an ownership and versioning problem as much as a technical one — production feature stores need change review and consumer visibility (who depends on this feature) precisely because reuse is not the free lunch it looks like at the point of definition.
An online store being low-latency in principle does not mean every lookup against it is fast in practice. A cold-start entity that has not been materialised yet returns nothing or a stale default. Fetching many features across many entities in one request without batching the lookups turns a single 1ms Redis call into dozens of round trips. Connection pool exhaustion under load can turn a normally-fast store into the slowest part of the request. Treating 'it is in the online store' as equivalent to 'it will be fast' skips the actual engineering work of batching lookups, warming caches, and monitoring materialisation freshness that production latency budgets depend on.
What actually needs to match is the transformation logic that produces the values, not just the values that happen to be sitting in each store today. If the offline feature is computed by a nightly batch Spark job and the online feature is computed independently by a separate streaming job written by a different engineer, small differences in windowing, null handling, or aggregation order will produce different numbers for what is supposedly 'the same feature' — and this happens even inside systems that are correctly labelled a feature store. The materialisation pattern (compute once offline, copy the same values to the online store) exists specifically to close this gap; maintaining two independent computation paths for one feature reopens exactly the skew problem the feature store was built to prevent.
ML pipelines and feature stores — 5 questions interviewers actually ask
It happens whenever the code path that computes a feature for training diverges from the code path that computes it for serving — different aggregation windows, different null handling, different library versions, or simply two engineers implementing the 'same' feature independently in a notebook and in a serving API. Prevention means removing the duplication entirely: define each feature once, in one place, and have both training and serving read from that single definition — which is exactly what a feature store provides structurally. Where a full feature store is not yet in place, the minimum fix is a shared feature-computation library imported by both pipelines, never two independent implementations of the same logic.
A point-in-time join retrieves, for each training example at its own event timestamp, only the feature values that were actually available at that moment — not the latest values as of today. Without this, a training row from January can get joined against a feature value computed from March data, which means the model is trained on information that would not have existed yet at prediction time. The model looks excellent in offline evaluation because it is effectively looking at the future, then performs far worse in production once only present-moment features are available. Feast and similar tools implement this as an as-of join keyed on event_timestamp specifically to prevent that class of data leakage.
They are optimised for opposite access patterns. Training needs to scan large historical ranges across millions of rows, which is what columnar formats like Parquet or warehouses like BigQuery are built for, but a single key lookup against them can take seconds. Serving needs the opposite: a single entity's latest feature values in low single-digit milliseconds, which is what an in-memory or key-value store like Redis is built for, but storing years of history there is prohibitively expensive per gigabyte. Using one store for both jobs means being bad at one of them; the split exists purely to match each access pattern to the storage technology that is actually good at it, with a materialisation job syncing values from offline to online on a schedule.
I would start with the entities the organisation actually predicts about (restaurant, driver, user) and define feature views per entity with an explicit schema and a time-to-live. I would pick an offline store that matches where the raw data already lives (often the existing warehouse) and an online store optimised for low-latency key lookups, then build a materialisation job — scheduled through the same orchestrator already running the training pipelines — that copies fresh values from offline to online on a fixed cadence. Critically, I would build the point-in-time query path for training before worrying about anything else, since that is the capability a shared feature library cannot easily replicate and the reason most teams adopt a real feature store instead of one.
First, know who depends on it — a feature registry should make it possible to list every model consuming a given feature view before touching its definition. Then version it rather than mutating it in place: register the changed logic as a new feature view or a new version of the existing one, backfill it into the offline store, and let consuming teams migrate on their own schedule instead of finding out from a production incident. Only retire the old definition once every consumer has confirmed the migration. Silently changing a shared feature's computation in place is exactly the kind of hidden coupling that makes 'free' feature reuse expensive later.
You can build ML pipelines. Next: track every experiment so you never lose a good model again.
Pipelines produce models automatically. But which of the 50 models trained over the past month is the best? What hyperparameters, what data version, what feature set produced it? Without experiment tracking you cannot answer these questions. Module 70 covers MLflow and Weights & Biases — log every run, compare experiments on a dashboard, version models, and register the best ones for deployment.
Log every run, compare experiments, version models, register artifacts. Never lose a good experiment again.
🎯 Key Takeaways
- ✓Three pipelines power every production ML system: the feature pipeline (raw events → features → feature store, runs hourly/daily), the training pipeline (feature store → model → model registry, runs daily/weekly), and the inference pipeline (request → feature store → model → prediction, runs in real time). All three must use the same feature definitions or training-serving skew silently degrades model quality.
- ✓A feature store has two layers: the offline store (full history in S3/BigQuery/Parquet, used for training with point-in-time queries) and the online store (latest values in Redis, used for inference at ~1ms latency). Materialisation jobs sync the offline store to the online store, typically daily via an Airflow DAG.
- ✓Point-in-time correct feature retrieval is mandatory for training. For each training event at time T, only use feature values computed from data with timestamp ≤ T. Fetching the latest features regardless of event time causes data leakage — the model appears excellent in evaluation but fails in production because future data is not available at inference time.
- ✓Prefect turns Python functions into pipeline tasks with @task and @flow decorators. Tasks get retries, caching, and logging automatically. Flows define the DAG structure. Run locally for development, deploy to Prefect Cloud or self-hosted server for production scheduling.
- ✓Airflow DAGs define ML pipelines as Python — tasks are PythonOperator/BranchPythonOperator/EmailOperator nodes connected by >> dependencies. BranchPythonOperator enables conditional logic (pass data validation → train, fail → alert). XCom passes data between tasks. schedule_interval sets the cron schedule. Used by DoorDash, Amazon, Stripe, and most unicorns.
- ✓The most dangerous ML pipeline failure is silent: feature extraction succeeds but returns 0 or wrong rows, training proceeds on bad data, a degraded model is promoted. Always add explicit data quality gate tasks that check row counts, null rates, and value distributions before training. Make quality checks raise exceptions on failure — Airflow marks tasks as failed only on unhandled exceptions.
Discussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.