Repository navigation
Conversation
dabla
force-pushed
the
feature/micro-benchmarks
branch
2 times, most recently
from
October 12, 2026 05:35
1064f40 to
901edcd
Compare
1 task done
dabla
force-pushed
the
feature/micro-benchmarks
branch
from
October 12, 2026 15:46
901edcd to
8640e32
Compare
Hot paths such as CommsDecoder.send/asend have no automated performance check, so a change that makes them slower is only noticed if someone measures it by hand. This adds task-sdk/benchmarks/ with pytest-codspeed benchmarks of one sync round trip, one async round trip and fifty concurrent async round trips, a "benchmarks" dependency group, and a workflow that runs them in CodSpeed's simulation mode, which counts instructions and compares each PR with main. The workflow also runs on a change of the task SDK's dependencies, keeps a run for every commit on main as CodSpeed's baseline, and stops after 15 minutes; the fake supervisor closes its socket whatever ends it, so a benchmark fails instead of hanging when a request is not what it expects.
dabla
force-pushed
the
feature/micro-benchmarks
branch
from
October 13, 2026 21:49
8640e32 to
d916a08
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft, opened to start a discussion on the dev list. Based on
main; the measurements below come from a fix on #62922, but the benchmarks do not depend on it.related: #62922, #74579
#74579 asks agents to measure these paths by hand in the meantime; this PR is the repeatable, automated version of that step.
Why
Airflow has no automated check on the performance of its hot paths. A change that makes them slower is only noticed when someone happens to measure it by hand, often after a release: #71721 reports structured task logging in 3.2.0 multiplying high-volume task runtime by 2.64.
A fix on #62922 shows how much is at stake in a single method.
CommsDecoder.asend, which every async SDK call of a task goes through (a Variable, a Connection, an XCom, a state store key), made two hops to a worker thread per request; it now makes one. Measured with the benchmarks of this PR, oldcomms.pyagainst new, on the same machine, the middle value of three runs (pytest-codspeed, walltime mode, best time per round):send: one sync round trip (code unchanged, the control)asend: one async round tripasend: 50 concurrent coroutines on one loopOn
mainthe benchmarks give the "Before" column; the "After" column is the code of #62922.An iterated async task makes such calls for every item, so the saving repeats thousands of times per task (the supervisor's and API server's own share of each call is not part of this measurement). The opposite change, an extra thread hop added in passing, would cost as much, and nothing in CI would notice today. A separate ad hoc measurement (4000
GetVariableround trips, median of 7 interleaved rounds) agrees: 525 µs to 318 µs per request.The CI job runs CodSpeed in simulation mode, which counts instructions and leaves system calls (the socket reads and writes, the thread wake-ups) out of the measurement (CodSpeed docs). So the same change was measured that way too, old
comms.pyandexecutor.pyagainst new, with CodSpeed's own valgrind (3.26.0.codspeed7) and cycle estimation on as in the action, each benchmark in its own process, the middle value of three runs:send: one sync round trip (the control)asend: one async round tripasend: 50 concurrent coroutines on one loopRuns of the same code differ by less than 0.7%. Simulation sees a smaller share of the saving than wall time (−26% against −47% for one round trip), because the kernel's part of a thread hop is not counted, but the change is still nearly forty times the run-to-run spread, so CI in simulation mode would flag the reverse change on the PR that makes it.
What
task-sdk/benchmarks/: a new directory next totests/, for benchmarks only. The first module,execution_time/test_comms_benchmark.py, measures one sync round trip, one async round trip and fifty concurrent async round trips against a supervisor thread on a socket pair. The task SDK's pytest configuration only collectstests/, so the benchmarks never run with the unit tests. The fake supervisor closes its socket however it ends, so an unexpected request fails the benchmark instead of hanging it.benchmarksdependency group intask-sdk/pyproject.tomlwith pytest-codspeed (MIT). Benchmarks are plain pytest functions using itsbenchmarkfixture. Locally they run withuv run --project task-sdk --group benchmarks pytest task-sdk/benchmarks --codspeed..github/workflows/benchmarks.yml: runs them on pushes tomainand on PRs touchingtask-sdk/src/airflow/sdk/execution_time/, the benchmarks,task-sdk/pyproject.tomloruv.lock, throughCodSpeedHQ/actionin simulation mode. That mode counts CPU instructions instead of measuring time, so the results do not depend on how busy the shared runner is, and CodSpeed comments each PR with the change againstmain. Every commit onmainkeeps its own run, as the baseline CodSpeed compares PRs with; the job stops after 15 minutes. The action is already on the ASF allow-list (CodSpeedHQ/actionv5.2.1, pinned by SHA in apache/infrastructure-actions). It authenticates through OIDC, so no secret is needed for a public repository, but someone with admin rights has to enable the CodSpeed app forapache/airflowbefore the workflow can report anything. Until then the job runs the benchmarks and fails at the upload (401 Unauthorized), so the workflow must not be merged before the app is enabled.Questions for the dev list
<distribution>/benchmarks/, next totests/, with paths that mirror the source module a benchmark covers:src/airflow/sdk/execution_time/comms.pyis measured bybenchmarks/execution_time/test_comms_benchmark.py. That mapping is what lets CI run only the benchmarks covering the files a PR changes, instead of the hand-kept path filter the workflow uses in this draft. Thetest_*_benchmark.pynames let the existing pytest configuration collect them without changes; abench_*.pyconvention would need its ownpython_filessetting.mainon each PR, with results that do not depend on runner load. pytest-benchmark is purely local and compares against a saved baseline with a threshold, but wall-time thresholds on shared CI runners are noisy, so they either fail at random or are set too loose to catch a drift.No newsfragment: nothing here is user-facing.
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 5.5) following the guidelines
{pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.