Python — Data Processing Pipelines
Pipeline Orchestration for ML Feature Stores
Direct answer
Orchestrating a feature store means coordinating two synchronized paths: batch jobs that compute features into an offline store for training, and materialization jobs that push the same feature values into a low-latency online store for serving. The orchestrator's real jobs are enforcing ordering between those paths, meeting per-feature-group freshness SLAs, and making backfills point-in-time correct — while the architecture guarantees both stores are fed by the same transformation code, because training/serving skew is the failure that quietly ruins models.
Feature stores fail in ways dashboards don't show: the model trains on one version of a feature and serves against another, or a backfill leaks future information into training data. This is how I structure the orchestration layer so those failures are prevented by design rather than caught by luck.
Key facts, with sources
- JetBrains' State of Python 2025 survey of more than 30,000 developers found that 51% of all Python developers are involved in data exploration and processing, with pandas and NumPy the most commonly used tools. (The JetBrains Blog)
- Apache Airflow reached more than 77,000 organizations using it as of November 2024, up from about 25,000 in 2020, with monthly downloads growing from 888,000 to over 31 million in the same period. (Astronomer State of Airflow 2025)
- The official Apache Airflow 2025 survey collected more than 5,250 responses from 116 countries, and event-driven scheduling introduced in Airflow 3 already showed almost 25% adoption. (Apache Airflow Blog)
- pandas has accumulated roughly 15.3 billion total downloads on PyPI, with around 700 million downloads in a single recent month. (pepy.tech)
- The Rust-based Polars DataFrame library passed 24 million monthly downloads and over 250 million total downloads as of September 2025, five years after its first commit. (Wikipedia)
- Gartner research estimates poor data quality costs organizations an average of at least $12.9 million per year. (Gartner)
What a feature store actually asks of your orchestrator
Beneath the terminology, a feature store is two data stores with a synchronization contract. The offline store holds historical feature values for building training sets; the online store holds current values for millisecond lookups at inference time. The orchestrator owns the contract: compute features on schedule, land them offline, materialize them online, and never let the two drift apart silently.
That translates to concrete orchestration requirements — dependency ordering (materialization must follow computation of the same window), per-feature-group scheduling since different features have different freshness needs, retries that don't double-apply writes, and backfill support that respects event time. Generic orchestrators like Airflow, Prefect, or Dagster handle all of this; the feature-store-specific discipline is in how you structure the jobs.
One definition, two materializations
Training/serving skew — where the feature a model saw in training differs subtly from the one it sees in production — is the defining failure of ML data infrastructure, and it almost always starts as two implementations of the "same" transformation: one in the batch SQL that builds training sets, another in the service code that computes values at request time. They drift, and the model quietly degrades with no error anywhere.
The architectural rule I enforce: each feature transformation is defined exactly once, and both the offline job and the online materialization execute that single definition. Whether it's a shared Python function, a dbt model both paths read from, or a feature framework's declarative definition matters less than the invariant — one definition, two destinations, zero reimplementation.
Schedule around freshness SLAs, not cron habits
Not every feature deserves the same schedule. A user's lifetime aggregate can be a day stale with no model impact; a fraud feature reflecting the last hour of activity cannot. I attach an explicit freshness SLA to each feature group, then derive the schedule from it — rather than defaulting everything to a nightly run because that's what the first job used.
The orchestrator's role is to make the SLA observable and enforced: each feature group's flow records the event-time watermark it computed through, and an independent check alerts when any group's watermark lags its SLA. That converts freshness from an assumption into a monitored contract.
from datetime import datetime
from prefect import flow, task
@task(retries=3, retry_delay_seconds=60)
def compute_user_features(window_end: datetime) -> list[dict]:
# single source of truth: same transformation code
# used for offline training data and online serving
return build_features(window_end)
@task(retries=3, retry_delay_seconds=60)
def write_offline(rows: list[dict], window_end: datetime) -> None:
upsert_offline_store(rows, watermark=window_end)
@task(retries=3, retry_delay_seconds=60)
def materialize_online(rows: list[dict]) -> None:
upsert_online_store(rows) # keyed by entity id: idempotent
@flow
def refresh_user_features(window_end: datetime) -> None:
rows = compute_user_features(window_end)
write_offline(rows, window_end)
materialize_online(rows) # only after offline write succeedsBackfills must be point-in-time correct
Backfilling a feature store is more dangerous than backfilling a dashboard, because training sets are assembled with point-in-time joins: for each training example, the feature value must be what was knowable at that example's event time. A naive backfill that computes features from today's data and stamps them onto historical rows leaks future information into training — the model looks brilliant offline and falls apart in production.
So backfill jobs replay history window by window, computing each window's features only from data with event times inside or before it, and writing values tagged with their effective time. It's the same flow as the scheduled run, parameterized by window — which is exactly why the flow above takes window_end as an argument instead of assuming now.
Be honest about whether you need one yet
A feature store is infrastructure for a real problem — multiple models sharing features, online inference needing sub-second lookups, teams reimplementing the same aggregations — and adopting one before those problems exist mostly buys operational burden. For a startup with one or two batch-scored models, a features table in the warehouse plus a cache is often the honest architecture, and it migrates cleanly later.
My adoption triggers: a second model wants features the first already computes, latency requirements force precomputed online lookups, or a skew incident actually bites. Until then, keep transformations in version-controlled, single-definition code — that discipline is the part of the feature store you can't retrofit, and it costs nothing to start with.
When to hire senior help
Bring in a senior data engineer when pipeline failures start silently corrupting business metrics, or before committing to an orchestration and warehouse architecture, since storage layout and idempotency decisions are expensive to reverse once terabytes flow through them. A few weeks of experienced design work on schemas, retries, and backfill strategy routinely saves months of firefighting later. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Python — Data Processing Pipelines projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Running pipelines as cron jobs plus scripts with no idempotency or retry semantics, so a mid-run failure leaves half-written tables that corrupt downstream reports
- ✕Loading entire datasets into pandas in memory instead of chunking or using Polars or DuckDB, causing out-of-memory crashes the first time data volume grows 10x
- ✕Skipping schema and data-quality checks at ingestion, letting silent schema drift from a source API propagate wrong numbers into dashboards for weeks
- ✕Designing pipelines that cannot deterministically backfill historical data, making every bug fix or logic change a manual one-off reprocessing project
Frequently asked questions
What causes training/serving skew in ML systems?
The usual cause is the same feature being implemented twice — once in batch code that builds training sets, once in serving code that computes values at request time — and the two drifting apart. Prevent it structurally: define each transformation exactly once and have both the offline store and online store materialized from that single definition, never from parallel reimplementations.
How often should feature store pipelines run?
Per feature group, driven by an explicit freshness SLA rather than one global schedule. Slow-moving aggregates like lifetime totals often tolerate daily refresh, while behavioral features for fraud or ranking may need hourly or streaming updates. Record an event-time watermark for each group and alert when it lags its SLA, so freshness is enforced rather than assumed.
Does a startup need a feature store?
Usually not at first. With one or two batch-scored models, a features table in your warehouse plus a cache covers the need with far less operational weight. Adopt a feature store when concrete triggers appear: multiple models sharing features, online inference requiring precomputed low-latency lookups, or an actual training/serving skew incident. Single-definition transformation code matters from day one, though.
Do we need an orchestrator like Airflow, or is cron enough?
Cron is fine for one or two independent jobs. Once tasks have dependencies, need retries, backfills, or alerting, an orchestrator pays for itself, which is why Airflow adoption tripled to 77,000+ organizations between 2020 and 2024. Managed options remove most of the operational burden for small teams.
When do we outgrow pandas?
Typically when datasets no longer fit comfortably in one machine's memory or single-threaded transforms become the bottleneck. Polars and DuckDB extend single-machine processing by 10x or more in published benchmarks before you need distributed systems like Spark, which add significant operational cost.
What does bad pipeline engineering actually cost?
Gartner puts the average cost of poor data quality at $12.9 million per year per organization, mostly through bad decisions and wasted rework. For startups the more common cost is losing trust in metrics, which stalls decision-making until someone rebuilds the pipeline with validation and lineage.
Bottom line: Dhairya Senjaliya ships Python — Data Processing Pipelines projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.