A pipeline that runs for an hour does not necessarily do an hour of useful work. It may spend most of that time waiting for a worker, a dependency, or a retry window. Before adding compute, build a timeline.
Start with the reader’s clock
Ask when the output needs to be ready. A job finishing in twelve minutes means little if it starts two hours after its input arrives. Measure the interval between input availability and a usable output, then break it into parts.
For a sequential example, the timeline might contain twenty minutes in a queue, eight minutes of execution, and two minutes of validation. Cutting execution in half saves four minutes. Removing the queue saves twenty.
Separate work from waiting
Record timestamps at the boundaries you actually control. Keep their meaning consistent across runs.
| Boundary | What it tells you |
|---|---|
| Input ready | Upstream data is available |
| Job submitted | Orchestration has requested work |
| Worker started | Capacity is available |
| Output validated | The result is usable |
A small measurement model
This Python example assumes timezone-aware timestamps captured from one run. A negative interval should fail validation instead of quietly entering a dashboard.
from datetime import datetime
def elapsed_seconds(start: datetime, end: datetime) -> float:
if start.tzinfo is None or end.tzinfo is None:
raise ValueError("Use timezone-aware timestamps")
seconds = (end - start).total_seconds()
if seconds < 0:
raise ValueError("Timeline boundaries are out of order")
return seconds
Do not add parallel task durations and call the result wall-clock latency. Overlapping work needs a critical-path view. Start with the end-to-end interval, then use task spans to explain it.
Look beyond the average
Keep observations from both ordinary and slow runs. An average can hide a queue that gets much worse during your busiest hour. Compare runs with similar input size, resource settings, and scheduling conditions.
When a slow run appears, inspect its timeline before changing a setting. Was input late? Did the job wait for capacity? Did one partition dominate execution? These are different problems and deserve different changes.
Change one constraint
Choose the largest repeatable source of delay. Write down your expectation before making a change. If queue time dominates, test a scheduling or capacity change. If execution dominates, inspect the stage that takes the longest.
- Save the baseline and the input characteristics.
- Change one factor you can explain.
- Compare equivalent runs and validate the output.
- Keep the change only if it improves the outcome you care about.
Build a run record you can explain
Treat the timeline as a small data product. Give each logical run a stable identifier, record its input partition or watermark, and distinguish the logical run from its individual attempts. Without that distinction, retries can make one delayed result look like several independent observations.
Start with a record that answers four questions:
- Which output is this? Identify the dataset, partition, and logical run.
- What was the expected input? Record the input boundary and when it became available.
- What happened during execution? Keep submitted, started, completed, and validated timestamps for each attempt.
- Was the output usable? Store the validation result, not just the scheduler’s success flag.
Keep failed attempts in the record. If a successful attempt took five minutes after two failures and a long backoff, five minutes is not the time the reader waited. The logical run’s end-to-end interval includes that recovery.
A scheduler timestamp may not be the same as a worker timestamp. If two systems supply the boundaries, check their clock synchronization and timestamp definitions. A negative interval is useful evidence of a measurement problem, not a value to clip to zero.
Define each interval before you chart it
For a simple sequential run, use these definitions:
| Interval | Start | End | Typical question |
|---|---|---|---|
| Orchestration delay | Input ready | Job submitted | Did the schedule notice the input promptly? |
| Capacity wait | Job submitted | Worker started | Was an eligible worker available? |
| Execution | Worker started | Work completed | Which stage consumed the time? |
| Validation | Work completed | Output validated | When could a reader trust the result? |
These intervals explain the example because the stages are sequential. A real dependency graph may overlap input ingestion, transformation, and validation. In that case, keep the spans and their relationships. Do not force overlapping intervals into a stacked chart that implies they happened one after another.
Walk through a concrete diagnosis
Consider a fictional daily dataset needed at 09:00. Input is ready at 08:00. The scheduler submits the job at 08:10, a worker starts at 08:35, execution finishes at 08:47, and validation finishes at 08:50.
The result is on time, but the fifty-minute end-to-end interval contains only twelve minutes of execution. There are ten minutes of orchestration delay, twenty-five minutes of capacity wait, and three minutes of validation.
You have several possible changes. They solve different problems:
- Halve execution time: In this simplified example, twelve minutes becomes six. The output is ready at 08:44 if every other interval stays unchanged.
- Reduce capacity wait to five minutes: The output is ready at 08:30 if execution and validation stay unchanged.
- Submit immediately when input is ready: Removing the ten-minute orchestration delay would make the output ready at 08:40, assuming the same capacity wait.
The arithmetic gives you a hypothesis. It does not guarantee the system will behave that way. Earlier submission might place the job in a busier queue. Increasing workers might overload the source. A faster transformation might shift the bottleneck to validation.
Write the dependency behind the prediction next to the prediction itself. For example: “Reducing queue wait should improve readiness by twenty minutes, provided the new worker pool has equivalent execution performance.” That sentence tells you what to verify after the change.
Choose a comparison that survives scrutiny
A before-and-after comparison is weak when the input also changed. Record enough context to explain the difference: input bytes, record counts, partition counts, retry count, worker configuration, and competing workload when available.
Choose a small group of comparable runs rather than one unusually fast run. Show the distribution of end-to-end latency and the proportion of outputs ready by their deadline. A lower average is useful, but it does not compensate for more missed deadlines when timeliness is the objective.
Keep correctness alongside speed. Compare row counts where meaningful, test key constraints, check partition completeness, and reconcile business totals using the dataset’s actual contract. Equal row counts alone do not prove two outputs are equivalent.
Write a decision record
After the experiment, leave a short note that a teammate can read without reconstructing the entire investigation:
- Problem: Which output arrived late, and how often?
- Baseline: Which runs and workload conditions did you measure?
- Hypothesis: Which interval would the change improve, and why?
- Change: What exactly did you alter?
- Result: What happened to readiness, correctness, and resource use?
- Decision: Keep, revise, or revert, with a reason.
This record matters when the workload changes again. It preserves the reasoning behind a setting, which is more useful than a comment saying that the setting makes the job faster.
Avoid three measurement traps
Confusing freshness with job duration
A short job can still produce stale data if it reads an old input boundary. Track the age of the data represented by the output separately from the time spent processing it. A job starting now does not imply that its contents describe now.
Treating a failed run as missing data
A dashboard containing only successful runs may look healthier as failures increase. Keep explicit failure and incomplete states. Report whether an expected output is still missing instead of silently excluding it from the latency view.
Optimizing away necessary validation
Validation consumes time because it establishes whether the output is usable. If it dominates, inspect duplicated work or a poorly scoped check. Removing checks simply to reduce the runtime changes the meaning of “ready” and invalidates the comparison.
The takeaway
Your first performance tool is a trustworthy timeline. Measure when data becomes useful, identify the waiting, and optimize the part that matters.
In plain language: Find out where the pipeline stops moving before buying a faster engine.