LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 177 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnProduction ML SystemsBatch and Streaming Feature Pipelines
⚙️MediumMLOps & Deployment

Batch and Streaming Feature Pipelines

Build point-in-time training-run features from events and preserve the same meaning in online serving.

12 min read
Learning path
Step 43 of 177 in the full curriculum
Feature Engineering for Production MLGradient Boosted Trees in Production

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

A valid training-job SLA row is useful only if the pipeline can build it honestly at scale. The system must produce millions of historical rows for training and keep fresh values ready for live scoring jobs.

Those jobs run at different speeds, but they share one question: what did the scoring system know at the prediction timestamp? If an offline replay sees facts that live serving couldn't have known yet, training gets an unfairly clean view of history.

Two-clock point-in-time quadrant for run R-204: E1 occurred and arrived before 10:00 and is visible, E2 occurred before 10:00 but arrived at 11:00 and is excluded as a late arrival, and E3 occurred after 10:00 and is future; adjacent as-of states advance from queued to warmup_spike to gpu_pressure.
At 10:00, only E1 lies inside both time boundaries. E2 is the subtle failure: it occurred at 09:30 but arrived at 11:00, so an event-time-only replay leaks information unavailable to live serving.

One event has two relevant times

Consider a training-job telemetry event:

FieldExampleMeaning
event_timeMay 1, 09:30when the scheduler recorded the heartbeat
ingested_atMay 1, 11:00when this pipeline learned about the heartbeat
prediction_timeMay 1, 10:00when the scoring system asked for a training SLA feature row

The heartbeat happened before the scoring request, but it arrived one hour afterward. A live prediction at 10:00 couldn't use it. A faithful replay excludes facts that occurred too late or arrived too late:

text
1event_time <= prediction_time 2and ingested_at <= prediction_time

The first boundary prevents future-event leakage. The second reproduces what the live system knew. Keep both timestamps even if your first source rarely arrives late.

Table time travel isn't a point-in-time join

A common ML systems mistake is conflating table-level time travel with per-entity point-in-time (PIT) joins.

  • Table Time Travel: Analytical storage formats like Delta Lake or Apache Iceberg let you query a whole table "as of" one specific snapshot or database transaction timestamp (e.g. 2026-05-01 10:30:00). This restores the entire dataset to a single global timestamp.
  • Point-in-Time Join: In ML training, each row in your label dataset represents a separate prediction event with its own distinct prediction_time (e.g., R-201 predicted at 10:00:00, R-202 predicted at 10:30:00). A point-in-time join must search backward from each row's unique timestamp to fetch the latest feature state visible at that exact millisecond.

This simplified comparison treats each feature update's time as the moment it became available. When event time and ingestion time differ, the production join must enforce both boundaries introduced above.

If you query a single global table snapshot (e.g., at 10:30:00) to construct features for all rows:

  • For R-202 (predicted at 10:30:00), you get the correct state.
  • For R-201 (predicted at 10:00:00), you accidentally pull in feature updates that occurred between 10:00:00 and 10:30:00 (e.g. a new queue backlog value at 10:15:00). This is data leakage.

Compare a pandas merge_asof point-in-time join against a table-snapshot query:

pit-vs-snapshot-join.py
1import pandas as pd 2 3# Prediction labels: each entity has a different prediction cutoff 4labels = pd.DataFrame([ 5 {"run": "R-201", "prediction_time": pd.Timestamp("2026-05-01 10:00:00")}, 6 {"run": "R-202", "prediction_time": pd.Timestamp("2026-05-01 10:30:00")}, 7]) 8 9# Feature updates log over time 10feature_updates = pd.DataFrame([ 11 {"run": "R-201", "time": pd.Timestamp("2026-05-01 09:50:00"), "queue_backlog": 5}, 12 {"run": "R-201", "time": pd.Timestamp("2026-05-01 10:15:00"), "queue_backlog": 12}, # Future for R-201 at 10:00! 13 {"run": "R-202", "time": pd.Timestamp("2026-05-01 10:20:00"), "queue_backlog": 8}, 14]) 15 16# 1. Correct Point-in-Time Join (ASOF join) 17labels_sorted = labels.sort_values("prediction_time") 18features_sorted = feature_updates.sort_values("time") 19 20pit_join = pd.merge_asof( 21 labels_sorted, 22 features_sorted, 23 left_on="prediction_time", 24 right_on="time", 25 by="run", 26 direction="backward" 27) 28 29# 2. Leaky Table Snapshot Join (taking a snapshot at 10:30 for the whole table) 30snapshot_time = pd.Timestamp("2026-05-01 10:30:00") 31visible_features = feature_updates[feature_updates["time"] <= snapshot_time] 32latest_snapshot = visible_features.sort_values("time").groupby("run").last().reset_index() 33 34leakage_join = pd.merge(labels, latest_snapshot, on="run") 35 36print("ASOF (PIT) JOIN:") 37print(pit_join[["run", "prediction_time", "queue_backlog"]]) 38print("\nSNAPSHOT JOIN (LEAKAGE):") 39print(leakage_join[["run", "prediction_time", "queue_backlog"]]) 40 41assert pit_join.loc[pit_join["run"] == "R-201", "queue_backlog"].values[0] == 5 42assert leakage_join.loc[leakage_join["run"] == "R-201", "queue_backlog"].values[0] == 12
Output
1ASOF (PIT) JOIN: 2 run prediction_time queue_backlog 30 R-201 2026-05-01 10:00:00 5 41 R-202 2026-05-01 10:30:00 8 5 6SNAPSHOT JOIN (LEAKAGE): 7 run prediction_time queue_backlog 80 R-201 2026-05-01 10:00:00 12 91 R-202 2026-05-01 10:30:00 8

Feature stores commonly provide point-in-time historical retrieval. Feast documents historical joins that scan backward from each entity timestamp within a configured time-to-live (TTL) window.[1]Reference 1Feast: Production Feature Store for Machine Learninghttps://feast.dev/ Your pipeline still owns the exact replay contract, including whether ingestion time belongs in it.

Diagram showing Event log event_time + ingested_at, Feature definition v1 time boundary + missing policy, Batch replay historical training rows, and Stream updater newly arrived events.
Event log event_time + ingested_at, Feature definition v1 time boundary + missing policy, Batch replay historical training rows, and Stream updater newly arrived events.

A heartbeat occurred at 09:30 but wasn't ingested until 11:00. Can a prediction made at 10:00 use it?

Answer

No. A faithful replay requires both event_time <= prediction_time and ingested_at <= prediction_time. The heartbeat had happened, but the live system didn't know about it yet.

Rebuild what serving knew

Build the replay in small steps. The first cell defines one normal event, one late arrival, and one future event for run R-204. Run the cells in order.

01-inspect-event-clocks.py
1from datetime import datetime 2from datetime import timedelta 3 4def dt(value): 5 return datetime.fromisoformat(value) 6 7events = [ 8 { 9 "id": "E1", 10 "run": "R-204", 11 "event_time": dt("2026-05-01T08:00:00"), 12 "ingested_at": dt("2026-05-01T08:02:00"), 13 "source_version": 1, 14 "status": "queued", 15 }, 16 { 17 "id": "E2", 18 "run": "R-204", 19 "event_time": dt("2026-05-01T09:30:00"), 20 "ingested_at": dt("2026-05-01T11:00:00"), 21 "source_version": 1, 22 "status": "warmup_spike", 23 }, 24 { 25 "id": "E3", 26 "run": "R-204", 27 "event_time": dt("2026-05-01T13:00:00"), 28 "ingested_at": dt("2026-05-01T13:01:00"), 29 "source_version": 1, 30 "status": "gpu_pressure", 31 }, 32] 33prediction_time = dt("2026-05-01T10:00:00") 34 35for event in events: 36 lag_minutes = int((event["ingested_at"] - event["event_time"]).total_seconds() / 60) 37 print( 38 f'{event["id"]} status={event["status"]} ' 39 f'occurred={event["event_time"]:%H:%M} arrived={event["ingested_at"]:%H:%M} ' 40 f'lag={lag_minutes}m' 41 )
Output
1E1 status=queued occurred=08:00 arrived=08:02 lag=2m 2E2 status=warmup_spike occurred=09:30 arrived=11:00 lag=90m 3E3 status=gpu_pressure occurred=13:00 arrived=13:01 lag=1m

A filter on event_time alone looks reasonable, but it leaks E2 into a replay of the 10:00 decision.

02-expose-event-time-only-bug.py
1def event_time_only(events, run_id, at): 2 return [ 3 event 4 for event in events 5 if event["run"] == run_id and event["event_time"] <= at 6 ] 7 8naive_replay = event_time_only(events, "R-204", prediction_time) 9print("event-time-only replay:", [event["status"] for event in naive_replay]) 10print("leaked late arrival:", "warmup_spike" in {event["status"] for event in naive_replay})
Output
1event-time-only replay: ['queued', 'warmup_spike'] 2leaked late arrival: True

Add the availability boundary. Now replay sees the same facts live serving saw at 10:00.

03-filter-by-decision-time.py
1def known_by(events, run_id, at): 2 return [ 3 event 4 for event in events 5 if ( 6 event["run"] == run_id 7 and event["event_time"] <= at 8 and event["ingested_at"] <= at 9 ) 10 ] 11 12faithful_replay = known_by(events, "R-204", prediction_time) 13print("decision-time replay:", [event["status"] for event in faithful_replay]) 14print("late heartbeat excluded:", "warmup_spike" not in {event["status"] for event in faithful_replay})
Output
1decision-time replay: ['queued'] 2late heartbeat excluded: True

A historical training snapshot needs the latest visible fact for each prediction request. The warmup_spike becomes usable after it arrives at 11:00; gpu_pressure becomes usable after 13:01.

04-build-as-of-rows.py
1def state_order(event): 2 return (event["event_time"], event.get("source_version", 0)) 3 4def revision_value(event): 5 return event["status"] 6 7def as_of_status(events, run_id, at): 8 visible = known_by(events, run_id, at) 9 if not visible: 10 return "missing" 11 latest_order = max(state_order(event) for event in visible) 12 latest = [event for event in visible if state_order(event) == latest_order] 13 if len({revision_value(event) for event in latest}) > 1: 14 return "quarantined" 15 return revision_value(latest[0]) 16 17for requested_at in [ 18 dt("2026-05-01T10:00:00"), 19 dt("2026-05-01T12:00:00"), 20 dt("2026-05-01T16:00:00"), 21]: 22 print(f"{requested_at:%H:%M} -> {as_of_status(events, 'R-204', requested_at)}")
Output
110:00 -> queued 212:00 -> warmup_spike 316:00 -> gpu_pressure

Analysts may also need corrected history of what happened by 10:00, regardless of arrival time. Keep that artifact separate: corrected history may include warmup_spike, while the decision-time replay contains only queued.

Keep online state current

Batch replay, stream updates, and online reads are related but distinct jobs:

JobReadsProducesTypical latency
batch replaybounded event historyversioned training snapshotminutes or hours
stream updaternewly arrived eventscurrent feature stateseconds
online readcurrent feature stateone scoring rowmilliseconds

Different stores are acceptable. Different meanings aren't. If batch computes a seven-day backlog mean while online returns a one-hour queue count under the same name, offline metrics can't predict live behavior.

Feast documents an online store built for low-latency feature serving. For each entity key, it stores only the latest feature values rather than full history.[1]Reference 1Feast: Production Feature Store for Machine Learninghttps://feast.dev/ Google Cloud's MLOps guidance describes a feature store as an optional shared repository for definitions, storage, and access across high-throughput batch and low-latency serving workloads.[2]Reference 2MLOps: Continuous Delivery and Automation Pipelines in Machine Learning.https://docs.cloud.google.com/architecture/mlops-continuous-delivery-and-automation-pipelines-in-machine-learning

A late, older event mustn't overwrite newer online state. Same-time corrections need a deterministic rule too. Use the same source-owned (event_time, source_version) order in batch replay and online updates: version 2 replaces version 1, including when a stale version-1 retry arrives later. In a real source contract, require a stable sequence or revision field rather than trusting arrival order.

06-protect-latest-online-state.py
1online_state = {} 2 3def apply_latest_state(state, event): 4 previous = state.get(event["run"]) 5 if previous is not None: 6 if event["id"] == previous.get("id"): 7 return "ignored duplicate event" 8 if state_order(event) < state_order(previous): 9 return "ignored stale event" 10 if state_order(event) == state_order(previous): 11 if previous.get("quarantined"): 12 return "quarantine persists" 13 if revision_value(event) == revision_value(previous): 14 return "ignored duplicate revision" 15 state[event["run"]] = { 16 "run": event["run"], 17 "event_time": event["event_time"], 18 "source_version": event.get("source_version", 0), 19 "status": "quarantined", 20 "quarantined": True, 21 } 22 return "quarantined conflicting revision" 23 state[event["run"]] = event 24 return "stored" 25 26def online_status(state, run_id): 27 current = state.get(run_id) 28 if current is None: 29 return "missing" 30 return "quarantined" if current.get("quarantined") else revision_value(current) 31 32arrivals = [ 33 { 34 "id": "E4", 35 "run": "R-204", 36 "event_time": dt("2026-05-01T10:30:00"), 37 "ingested_at": dt("2026-05-01T10:31:00"), 38 "source_version": 1, 39 "status": "worker_scaled", 40 }, 41 { 42 "id": "E4-correction", 43 "run": "R-204", 44 "event_time": dt("2026-05-01T10:30:00"), 45 "ingested_at": dt("2026-05-01T10:34:00"), 46 "source_version": 2, 47 "status": "worker_scaled_corrected", 48 }, 49 { 50 "id": "E4-v1-retry", 51 "run": "R-204", 52 "event_time": dt("2026-05-01T10:30:00"), 53 "ingested_at": dt("2026-05-01T10:36:00"), 54 "source_version": 1, 55 "status": "worker_scaled", 56 }, 57 { 58 "id": "E1-retry", 59 "run": "R-204", 60 "event_time": dt("2026-05-01T08:00:00"), 61 "ingested_at": dt("2026-05-01T11:05:00"), 62 "status": "queued", 63 }, 64] 65 66for event in arrivals: 67 print(f'{event["id"]}: {apply_latest_state(online_state, event)}') 68print("current status:", online_status(online_state, "R-204"))
Output
1E4: stored 2E4-correction: stored 3E4-v1-retry: ignored stale event 4E1-retry: ignored stale event 5current status: worker_scaled_corrected

Latest-value state isn't enough for aggregates. A duplicated queue update can silently inflate queue backlog unless the updater is idempotent. Idempotent means retrying the same event doesn't change the result after its first successful application. The updater should also reject impossible aggregate states instead of publishing them.

07-deduplicate-aggregate-updates.py
1seen_event_ids = set() 2origin_backlog = 0 3 4def apply_queue_update(event): 5 global origin_backlog 6 if event["id"] in seen_event_ids: 7 return "duplicate skipped" 8 next_backlog = origin_backlog + event["delta"] 9 if next_backlog < 0: 10 return "blocked negative backlog" 11 seen_event_ids.add(event["id"]) 12 origin_backlog = next_backlog 13 return "applied" 14 15queue_updates = [ 16 {"id": "Q1", "delta": 3}, 17 {"id": "Q1", "delta": 3}, 18 {"id": "Q2", "delta": -1}, 19 {"id": "Q3", "delta": -5}, 20] 21 22for update in queue_updates: 23 result = apply_queue_update(update) 24 print(f'{update["id"]}: {result}; backlog={origin_backlog}')
Output
1Q1: applied; backlog=3 2Q1: duplicate skipped; backlog=3 3Q2: applied; backlog=2 4Q3: blocked negative backlog; backlog=2

The set makes sequential retries visible, but production needs a durable atomic write: record the event ID and apply its aggregate change together. Otherwise a worker can crash between those steps and a retry can lose or repeat an update. Use a transaction, conditional write, or stream processor state primitive with the same contract.

A version-2 correction and a later retry of version 1 share the same event time. Which value should remain online?

Answer

Version 2. Ordering by (event_time, source_version) prevents a late retry from moving current state backward. If both ordering fields match but values conflict, quarantine the state instead of choosing by arrival order.

Bound lateness and freshness

A stream processor can't wait forever for missing events. It commonly tracks a watermark, an estimate of how far event-time processing has progressed. A watermark isn't a guarantee: an event with an older timestamp can still arrive later. A product policy decides whether that event can update a recent window or must wait for a controlled replay.

This miniature policy accepts updates no more than 45 minutes behind the watermark. A real stream processor also defines windows, triggers, state cleanup, and monitoring.

08-classify-late-events.py
1def late_event_action(event_time, watermark, allowed_lateness): 2 lateness = watermark - event_time 3 if lateness <= timedelta(0): 4 return "on-time path" 5 if lateness <= allowed_lateness: 6 return "accept late update" 7 return "quarantine for replay" 8 9watermark = dt("2026-05-01T12:00:00") 10allowed_lateness = timedelta(minutes=45) 11 12for event_time in [ 13 dt("2026-05-01T12:05:00"), 14 dt("2026-05-01T11:30:00"), 15 dt("2026-05-01T10:00:00"), 16]: 17 print(f"{event_time:%H:%M} -> {late_event_action(event_time, watermark, allowed_lateness)}")
Output
112:05 -> on-time path 211:30 -> accept late update 310:00 -> quarantine for replay

Freshness requires a serving policy:

09-apply-serving-freshness-policy.py
1def serving_mode(updated_at, requested_at): 2 lag_minutes = int((requested_at - updated_at).total_seconds() / 60) 3 if lag_minutes < 0: 4 raise ValueError("feature update is from the future") 5 if lag_minutes <= 15: 6 return "normal scoring" 7 if lag_minutes <= 45: 8 return "fallback and log degraded mode" 9 return "abstain from narrow SLA estimate" 10 11requested_at = dt("2026-05-01T12:00:00") 12for updated_at in [ 13 dt("2026-05-01T11:52:00"), 14 dt("2026-05-01T11:30:00"), 15 dt("2026-05-01T10:30:00"), 16]: 17 print(f"{updated_at:%H:%M} -> {serving_mode(updated_at, requested_at)}")
Output
111:52 -> normal scoring 211:30 -> fallback and log degraded mode 310:30 -> abstain from narrow SLA estimate

No model fixes a missing or temporally invalid input. Record freshness lag with each prediction trace so operators can distinguish model error from stale state.

Compare offline and online rows

Before promotion, replay a sample of requests offline and compare those rows with rows captured from online serving. Start with exact equality for categorical and integer fields; define tolerances explicitly if floating-point aggregations need them.

10-check-offline-online-parity.py
1def mismatches(left, right): 2 keys = left.keys() | right.keys() 3 return {key: (left.get(key), right.get(key)) for key in keys if left.get(key) != right.get(key)} 4 5correction_offline_row = { 6 "status": as_of_status(arrivals, "R-204", dt("2026-05-01T10:40:00")), 7} 8correction_online_row = {"status": online_status(online_state, "R-204")} 9correction_differences = mismatches(correction_offline_row, correction_online_row) 10print("same-time retry mismatches:", correction_differences) 11 12conflicting_revisions = [ 13 { 14 "id": "E5-a", 15 "run": "R-205", 16 "event_time": dt("2026-05-01T10:30:00"), 17 "ingested_at": dt("2026-05-01T10:31:00"), 18 "source_version": 7, 19 "status": "worker_scaled", 20 }, 21 { 22 "id": "E5-b", 23 "run": "R-205", 24 "event_time": dt("2026-05-01T10:30:00"), 25 "ingested_at": dt("2026-05-01T10:32:00"), 26 "source_version": 7, 27 "status": "worker_scale_failed", 28 }, 29] 30conflict_online_state = {} 31for event in conflicting_revisions: 32 apply_latest_state(conflict_online_state, event) 33conflict_offline = as_of_status(conflicting_revisions, "R-205", dt("2026-05-01T10:40:00")) 34conflict_online = online_status(conflict_online_state, "R-205") 35assert conflict_offline == conflict_online == "quarantined" 36print("equal-order conflict parity:", conflict_offline, "==", conflict_online) 37 38for event in events: 39 if event["ingested_at"] <= dt("2026-05-01T16:00:00"): 40 apply_latest_state(online_state, event) 41 42offline_row = { 43 "status": as_of_status(events, "R-204", dt("2026-05-01T16:00:00")), 44 "origin_backlog": origin_backlog, 45} 46online_row = { 47 "status": online_status(online_state, "R-204"), 48 "origin_backlog": origin_backlog, 49} 50 51print("offline row:", offline_row) 52print("online row:", online_row) 53print("mismatches:", mismatches(offline_row, online_row))
Output
1same-time retry mismatches: {} 2equal-order conflict parity: quarantined == quarantined 3offline row: {'status': 'gpu_pressure', 'origin_backlog': 2} 4online row: {'status': 'gpu_pressure', 'origin_backlog': 2} 5mismatches: {}

A stale online row produces a visible failure instead of a mysterious model regression.

11-detect-stale-online-state.py
1stale_online_row = { 2 "status": "worker_scaled", 3 "origin_backlog": origin_backlog, 4} 5differences = mismatches(offline_row, stale_online_row) 6print("mismatches:", differences) 7print("release allowed:", not differences)
Output
1mismatches: {'status': ('gpu_pressure', 'worker_scaled')} 2release allowed: False

Publish a compact receipt beside every snapshot and candidate model. It makes replay semantics inspectable instead of leaving them hidden in job code.

12-publish-replay-receipt.py
1receipt = { 2 "feature_definition": "training_sla_features_v1", 3 "replay_boundary": "event_time <= prediction_time and ingested_at <= prediction_time", 4 "offline_online_ordering": "(event_time, source_version); equal-key conflicts quarantine", 5 "aggregate_updates": "atomic event-id dedup; reject negative backlog", 6 "snapshot": "training-sla-train-2026-05-01", 7 "parity_samples_checked": 2, 8 "freshness_policy": "sla-freshness-v1", 9} 10 11assert as_of_status(events, "R-204", dt("2026-05-01T10:00:00")) == "queued" 12assert as_of_status(events, "R-204", dt("2026-05-01T12:00:00")) == "warmup_spike" 13assert not correction_differences 14assert not mismatches(offline_row, online_row) 15 16for key, value in receipt.items(): 17 print(f"{key}={value}")
Output
1feature_definition=training_sla_features_v1 2replay_boundary=event_time <= prediction_time and ingested_at <= prediction_time 3offline_online_ordering=(event_time, source_version); equal-key conflicts quarantine 4aggregate_updates=atomic event-id dedup; reject negative backlog 5snapshot=training-sla-train-2026-05-01 6parity_samples_checked=2 7freshness_policy=sla-freshness-v1

Offline replay says gpu_pressure, while the captured online row says worker_scaled for the same entity and cutoff. Should the model release proceed?

Answer

No. The mismatch shows that replay and serving disagree on definition, ordering, or freshness. Fix that parity failure before using offline model quality as evidence for live behavior.

When the contract breaks

SymptomCauseFix
Training gets better after a backfill but serving doesn'treplay used facts that arrived laterfilter by event time and ingestion time
Online status moves backward after retry or same-time corrections disagreestale or ambiguous event overwrote latest statecompare event timestamp plus source revision; quarantine unresolved ties
Backlog inflates during redeliveryaggregate update ran twicededuplicate by stable event ID in same atomic write as aggregate change
Reliable model emits bad SLA estimates during upstream lagfreshness wasn't part of serving policytrace age and fail to a safer response
Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.For run R-204, a prediction is requested at 10:00. E1 occurred at 08:00 and arrived at 08:02; E2 occurred at 09:30 and arrived at 11:00; E3 occurred at 13:00 and arrived at 13:01. Which status should a faithful replay expose at 10:00?

Correct answer: queued, because E1 both occurred and arrived before 10:00.

A decision-time replay must satisfy both event_time <= prediction_time and ingested_at <= prediction_time. E1 passes both checks. E2 happened before 10:00 but was not available until 11:00, and E3 had not happened yet.

2.At 10:00, R-204 has E1 queued with event_time 08:00 and ingested_at 08:02, and E2 warmup_spike with event_time 09:30 and ingested_at 11:00. An analyst wants a corrected audit of what physically happened by 10:00, while the model training row must replay what the training SLA service knew at 10:00. Which handling is correct?

Correct answer: Keep separate artifacts: the audit view may include queued and warmup_spike, but the training replay exposes only queued.

Corrected event-time history answers what truly happened by 10:00, so it can include a late-arriving 09:30 heartbeat. A training replay for the 10:00 model decision must reproduce the information available at 10:00, so it excludes that heartbeat until after it arrives.

3.A heartbeat arrives on the event stream at 10:31 and the training SLA service asks for a feature row at 10:32. Which division of work matches the pipeline contract?

Correct answer: The stream updater consumes the heartbeat and writes current feature state; the online read retrieves that state for the scoring request.

The stream updater and the online read are separate jobs. The updater processes newly arrived events and maintains current feature state, while the online read serves one low-latency scoring row from that state.

4.A team keeps historical features in a batch store and latest values in a low-latency online store. What condition makes this acceptable before model promotion?

Correct answer: Both paths implement the same versioned feature meaning, and sampled offline rows match captured online rows within declared tolerances.

Different stores are fine because batch replay and online serving have different latency and retention needs. They are not fine if they compute different feature meanings. Promotion should be gated by parity checks on sampled requests, with explicit tolerances where needed.

5.R-204 receives E4 at event_time 10:30 with source_version 1, E4-correction at the same time with source_version 2, then a delayed retry of E4 version 1. Batch replay and the online updater both order by (event_time, source_version). What should both expose at 10:40?

Correct answer: worker_scaled_corrected, because version 2 wins in both paths and the same-time version-1 retry is stale.

Batch and online selection must implement the same feature meaning. Comparing (event_time, source_version) makes the version-2 correction newer than both version-1 copies even though one stale copy arrives last, so the sampled rows remain equal.

6.An aggregate updater for origin_backlog starts at 0 and may retry after worker crashes. It receives Q1 delta +3, duplicate Q1 delta +3, Q2 delta -1, and Q3 delta -5. Which implementation contract preserves the intended published state?

Correct answer: Atomically record each event id with its backlog change; count Q1 once, apply Q2, reject Q3, and publish backlog 2.

Idempotency requires a stable event id so a duplicate Q1 changes the backlog only once. The negative-state guard rejects Q3 because it would move the backlog from 2 to -3. In production, the event-id record and the aggregate mutation must be one durable atomic operation; otherwise a crash between the two steps can lose or repeat an update.

7.With watermark 12:00 and allowed lateness 45 minutes, a stream processor sees events with event_time 12:05, 11:30, and 10:00. How should they be routed under this policy?

Correct answer: 12:05 uses on-time path; 11:30 is accepted as a late update; 10:00 is quarantined for replay.

An event after the watermark is on the on-time path. An event 30 minutes behind a 12:00 watermark is within the 45-minute allowed-lateness window, so it can update. An event 120 minutes behind the watermark exceeds the policy and must wait for controlled replay.

8.A serving policy for training SLA features is: lag <= 15 minutes uses normal scoring, lag <= 45 minutes falls back and logs degraded mode, and larger lag abstains from a narrow SLA estimate. At 12:00 the feature was last updated at 10:30. What should serving do?

Correct answer: Abstain from a narrow SLA estimate and record the 90-minute freshness lag with the prediction trace.

The feature is 90 minutes old at request time, which exceeds the 45-minute degraded-mode limit. A serving policy should fail to a safer response, and tracing the lag helps operators distinguish stale state from model error.

8 questions remaining.

Next Step
Continue to Gradient Boosted Trees in Production

You can now generate point-in-time correct feature rows. Next you'll train a strong tabular baseline and decide when its validation evidence earns deployment.

PreviousFeature Engineering for Production ML
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Feast: Production Feature Store for Machine Learning

Feast Contributors · 2024

https://feast.dev/

MLOps: Continuous Delivery and Automation Pipelines in Machine Learning.

Google Cloud. · 2026 · Official documentation

https://docs.cloud.google.com/architecture/mlops-continuous-delivery-and-automation-pipelines-in-machine-learning

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.