Why trading.hyena17 runs past 09:30: request timeouts and slow service readiness

I wanted to know which tasks in trading.hyena17 make the morning setup run past 09:30. Sorting by duration was misleading. Some tasks deliberately run until 15:00, while a task that takes only five seconds can still start ninety seconds late.
The Nov 12 run has two main problems. Stock-weight preparation takes 352.6 seconds and leaves only 4.4 seconds before 09:30. Downstream, the slowest shared-service startup takes another 167.3 seconds and finishes at 09:35:15. Redis itself takes 4.8 seconds. Its late start comes from its dependencies.

Open the full-size timeline to read all 252 task rows. The eight sections contain all 26 fund targets, 34 zfu_param generation tasks, 34 transfers to R7, 22 transfers to colos, 34 zfu_check validation tasks, 68 stock account/colo launch tasks for 34 accounts, 22 shared-service startups, and nine futures account launches, plus stock weight, Redis, and futures risk. This chart uses the first recorded morning attempts and includes both stock-market colos for each account.
What I measured
I read the production Airflow metadata and selected task logs without running or clearing any tasks. The snapshot starts at 17:08 UTC+8 on Nov 12, after the trading session.
- Airflow 2.7.3,
LocalExecutor, MySQL metadata,Asia/Shanghaitimezone. - The latest 20 scheduled dates, Oct 16 through Nov 12, 2026. The DAG is scheduled at 09:05 on weekdays.
- 7,301 current task-instance rows and 15,113 task-state audit events. Manual DAG runs are outside the sample.
- Historical duration comparisons use the 17 dates with an observed successful stock-weight run, excluding the known Nov 11 incident. Each task has its own sample count.
All displayed times are UTC+8. The naive timestamps read from MySQL represent UTC. I checked that conversion against task logs, where the 01:05 UTC scheduled run appears at 09:05 local time.
The report uses the original Airflow task IDs, fund codes, account names, and colocation names. Stock target position: ZX7 is zsun_pre_trade_stock_target_pos_ZX7: it requests pre-trade stock target positions for fund ZX7 and waits for completion. Shared services: Colo153 is h17.trading.shared_Colo153, the shared-service startup task on Colo153. Charts abbreviate the task family for readability; tables and CSV downloads retain the Airflow identifiers.
Reruns make the current task table an incomplete history
The current dag_run.start_date for Nov 12 says 16:44 local time. The same run contains tasks that started at 09:05. Someone reran part of the DAG in the afternoon, so the run-level timestamps do not describe the original morning execution.
I reconstructed attempts by pairing each task’s running audit event with its next success or failed event. A second running event without a terminal event leaves the earlier attempt unresolved. When a current task-instance or failure record matches an attempt, its more precise timestamps replace the audit timestamps. Original queue timestamps are used only when that matching task-instance record survives.
For morning duration comparisons, I select the first observed attempt per task between 09:00 and noon on the scheduled date. Failed and unresolved attempts stay in the data, but do not enter successful-runtime percentiles. Empty operators can have metadata-only records. Other metadata-only successes without evidence of a running attempt are excluded, because marking a task successful does not prove that its work ran.
Across the 20 dates, those first morning attempts contain 5,759 successes, 107 failures, and 93 unresolved outcomes. For Nov 12, all 361 observed first morning task or gate instances succeed. That does not mean the application made no retries inside those tasks.
The history chart uses the first successful morning completion per task, including a recovery attempt when necessary. Oct 16 and Oct 19 lack successful stock-weight records in this reconstruction. Gray cells mean missing evidence, not an on-time completion.
The delay begins before 09:30

Stock-weight runtime changes visibly after Oct 23. Its median is 108.8 seconds across Oct 20–23, then 341.2 seconds across the 13 later non-incident dates. This is an observational change, not a controlled benchmark. The sample contains 14 different DAG hashes, and I have not isolated a backend release, input-size change, or configuration change that explains it.

The Nov 12 task starts at 09:24:02.972 and succeeds at 09:29:55.607. The application log shows an accepted request, a timeout, a second request, and eventually completion:
|
|
These are shortened excerpts. The sanitized original lines preserve the timestamps and messages.
The first request is accepted promptly. Most of the task’s elapsed time is then spent awaiting application completion. The log does not establish whether the backend is computing, queued behind other requests, or failing to deliver a completion response. The 300-second timeout is an observed symptom, not 300 seconds of proven avoidable work.
The selected stock-weight task logs survive only for Nov 11 and Nov 12. Both contain this retry pattern. I cannot use those two logs to claim that the earlier slow dates had the same cause.
Which tasks are slow after the open
Today, 253 of 316 first-attempt setup or gate instances finish after 09:30. This excludes intentional wait tasks and the long-lived trading sessions. That is a deadline-miss count, not 253 separate root causes. Many short tasks simply inherit a late upstream completion.
The following times are from the original morning attempts, even where the current metadata was overwritten by a later rerun.
| Task or family | Starts | Finishes | Runtime |
|---|---|---|---|
zsun_pre_trade_stock_weight |
09:24:02.972 | 09:29:55.607 | 352.6 s |
First successful stock target, zsun_pre_trade_stock_target_pos_ZX7 |
09:29:58.460 | 09:30:02.918 | 4.5 s |
Last successful stock target, zsun_pre_trade_stock_target_pos_ZX14 |
09:29:58.721 | 09:31:31.982 | 93.3 s |
redis_tpc |
09:31:34.769 | 09:31:39.523 | 4.8 s |
h17.trading.shared_Colo153 |
09:32:27.945 | 09:35:15.291 | 167.3 s |
h17.trading.shared_Colo152 |
09:32:27.890 | 09:35:10.548 | 162.7 s |
h17.trading.shared_Colo181 |
09:32:17.767 | 09:34:47.845 | 150.1 s |
The 26 stock-target tasks all start within 0.6 seconds of one another, after stock weight finishes. All 26 finish after 09:30. Their median runtime is 34.3 seconds, with a maximum of 93.3 seconds.
Four stock-target tasks, DC5, ZX14, ZX8, and ZZ1, retry after a 60-second request timeout. Their runtimes range from 80.5 to 93.3 seconds. Airflow records one attempt for each because these are retries inside the application client.

A stock target that runs from 09:29:58 to 09:31:32 contributes about 92 seconds to this chart. Its full runtime is about 93 seconds. These quantities differ from its completion delay relative to 09:30, and none measures CPU consumption.
Shared services spend most of their runtime waiting for readiness
For the three slowest shared-service tasks, I matched the log line where the readiness poll identifies the process log with the first Airflow log line that reports a worker thread:
| Shared-service task | Observed readiness interval | Interval | Whole task |
|---|---|---|---|
Colo153 |
09:32:33.797 → 09:35:12.466 | 158.7 s | 167.3 s |
Colo152 |
09:32:34.089 → 09:35:07.734 | 153.6 s | 162.7 s |
Colo181 |
09:32:26.763 → 09:34:44.486 | 137.7 s | 150.1 s |
The worker-readiness interval dominates these task durations. That narrows the next investigation to service initialization and its inputs. It does not identify the cause inside the remote process. I did not profile CPU, storage, model loading, or network activity on those hosts. The readiness intervals use Airflow’s log timestamps, so they also include polling and log-delivery delay.
For Colo153, the observed downstream sequence is:
| Step on the path | Completed | Task runtime |
|---|---|---|
| Stock weight | 09:29:55.607 | 352.6 s |
Stock target, ZX13 |
09:30:51.194 | 52.3 s |
Parameters, HuaTai_ZX13 |
09:31:17.144 | 22.7 s |
Transfer to R7, HuaTai_ZX13 |
09:31:22.200 | 1.7 s |
Transfer to Colo153 |
09:32:04.378 | 39.0 s |
| Last prerequisite account check | 09:32:24.517 | 16.6 s |
| Shared service ready | 09:35:15.291 | 167.3 s |
The transfer and shared-service tasks have multiple upstream accounts. The table follows the latest prerequisite completion at each step, so the account identifier can change between rows. The corresponding stock dependencies are unchanged by the Redis edit in this session.
Stock algo launch and shared-service startup are sibling tasks after the parameter checks. A successful algo-launch task therefore does not establish that the shared worker is ready. Today all 68 stock algo-launch tasks finish by 09:32:47.583, but the last shared-service task finishes at 09:35:15.291. Actual trading readiness needs both, plus the application’s own health signals.
Redis is waiting, rather than running slowly
The nine futures hedge checks finish by 09:10:40.896. The first stock target succeeds at 09:30:02.918, and the last at 09:31:31.982. Redis starts at 09:31:34.769 and runs for 4.8 seconds.
The morning graph waits for every stock target before Redis can run. The revised graph now expresses two separate conditions:
|
|
This preserves the existing hedge-check policy: every hedge check must finish, even if one fails. Each futures trading task separately requires its own hedge check and Redis to succeed. Airflow’s trigger-rule documentation explains why ONE_SUCCESS needs its own gate when combined with another condition.
Using today’s timings, the first stock success would satisfy the remaining data condition at 09:30:02.918, since the hedge checks were already finished. Waiting for the last stock target adds 89.1 seconds of dependency delay.
That is an estimate of the dependency wait the new graph can remove. It is not a measured 89.1-second improvement in Redis completion, and it would not make Redis start before 09:30. The two new gate tasks have no execution timestamps in today’s run. Their scheduling overhead and the next run’s completion time still need measurement.
The observed executor queue is a smaller part of today’s delay

Among 308 setup or gate instances with surviving queue timestamps, median queued-to-running delay is 2.7 seconds, p90 is 3.2 seconds, and the maximum is 3.5 seconds. Those values are much smaller than the 353-second stock-weight run or the 150–167-second shared-service startups.
| Family | Today’s tasks | Median runtime | Maximum runtime | Finish after 09:30 |
|---|---|---|---|---|
| Stock targets | 26 | 34.3 s | 93.3 s | 26 |
| Parameter generation | 34 | 22.0 s | 22.8 s | 34 |
| Host transfer | 22 | 6.4 s | 46.2 s | 22 |
| Parameter checks | 34 | 10.9 s | 22.5 s | 34 |
| Shared service startup | 22 | 46.5 s | 167.3 s | 22 |
| Stock algo launch | 68 | 12.4 s | 31.0 s | 68 |
| Futures algo launch | 9 | 5.3 s | 5.7 s | 9 |
| Hedge checks | 9 | 6.7 s | 7.0 s | 0 |
Queue time only starts when queued_dttm is set. A task may have waited for a dependency, a scheduler decision, or capacity before that timestamp. These measurements do not rule out every scheduling delay or historical capacity problem.
The current global parallelism and per-DAG active-task limit are both 128. Today’s numbers do not make another limit increase the first optimization. The Nov 11 outage was different: configuration drift and restarts disrupted the session. Its 127 recorded failed morning attempts are shown separately from the duration baseline.
Session-long tasks need a start-time measure
The live stock and futures target-position services intentionally stay alive until about 15:00. Their roughly 330-minute durations are expected behavior, not the slow setup work identified above.

On Nov 12:
- The 34 live stock-session tasks start between 09:30:05.374 and 09:31:34.781.
- The nine live futures-session tasks start between 09:30:10.114 and 09:31:35.351.
For these services, I would track launch time, application readiness, heartbeat coverage, and unexpected exits. A short runtime could actually indicate a failure.
What I would optimize next
1. Explain the stock-weight delay in the backend
This task delays every stock target and both downstream setup branches. I would start by comparing backend changes and input sizes around Oct 23–26, when the recorded runtime rises from about 109 seconds to about 341 seconds.
Add or inspect timestamps for request acceptance, backend work start, work completion, and response delivery, using the same request identifier. Those four timestamps distinguish queue delay, computation, and a lost completion response. Profile the component that occupies the time, rather than increasing Airflow parallelism.
The next change depends on that evidence. Reuse stable intermediate results if repeated computation dominates. Reduce storage or network work if reads dominate. If a completion response is lost, fix response delivery and retry handling.
Shortening the client timeout is not a demonstrated speedup. It may send more duplicate requests while the backend is still working. Before changing retry behavior, verify that the same logical request cannot execute its effects twice.
2. Overlap shared-service preparation with account setup where its inputs allow it
The slowest shared task spends 158.7 seconds between readiness polling and its first observed worker message. I would profile startup on Colo153 and Colo152, separating model reads, initialization, external waits, and thread startup.
Then inspect which inputs actually require completed account parameters. If some startup work uses only stable model files, prepare that work earlier and keep a final validation gate before trading uses it. Keep the existing freshness checks and worker-readiness check. A process merely existing is not sufficient readiness.
The potential overlap is bounded by the work that is truly independent. The observed 158.7 seconds is time to investigate, not a promise that all of it can disappear.
This matters for the schedule budget. Today, 319.7 seconds pass between stock-weight completion and the last shared-service success. Keeping that downstream delay unchanged would require stock weight ready around 09:24:40 to finish setup by 09:30. A task that starts after the 09:24 gate and then takes 352.6 seconds leaves too little time. Meeting the deadline needs faster work, safe overlap, or a justified change in input availability, not just a different Redis trigger.
3. Validate the Redis gate on the next scheduled run
The graph change is already in the DAG. Check that Redis starts only after both the first successful stock target and all finished hedge checks, then verify that each futures trading task still waits for its own successful hedge check.
Record gate completion, Redis queue time, Redis start, and futures startup completion. Compare dependency wait separately from runtime. Today’s 89.1-second gap is the estimate to test, not a result to publish as a measured speedup.
4. Investigate the four slow stock targets and the longest transfers
For DC5, ZX14, ZX8, and ZZ1, correlate the 60-second client retries with backend request records. The nearly simultaneous task starts and staggered completions justify checking backend serialization or contention, but do not prove either.
The path to Colo153 also includes 22.7 seconds of parameter generation and 39.0 seconds of host transfer. Measure bytes transferred, remote command time, and repeated work for that path. Reuse unchanged files or intermediate results where freshness and validation remain intact. Do not remove account checks just to make the graph shorter.
The inspected image-pull log lines report already-current images within fractions of a second. Those selected pulls are a lower priority than the application waits. There is no evidence here for a large gain from changing image-pull policy.
5. Measure joint readiness and retain attempt history
Record a readiness timestamp per account and host that requires both the algo launch and its shared service. Alert on missed readiness at 09:30, and distinguish an unresolved task from a successful task that ran late. Add application-level first-use and heartbeat signals so task state is not the only evidence of a working service.
Retain attempt history and task logs outside the container’s writable layer. Today’s reconstruction recovered earlier attempts, but 93 first morning outcomes remain unresolved across the historical sample. Better records would make the next comparison more reliable.
Keep startup capacity visible while the 43 session tasks occupy slots. Airflow pools can cap related tasks, but a pool alone does not reserve global executor capacity. Change limits only after measuring runnable work waiting for capacity. With LocalExecutor, restarting the scheduler can also orphan running tasks, so a restart is not a harmless way to test an optimization during the session.
For each proposed change, collect several comparable scheduled runs with the same input-freshness requirements. Report per-stage readiness and failures together. The next observation I want is whether stock weight still consumes almost the entire six-minute interval from 09:24 to 09:30, and whether shared workers become ready earlier without weakening validation.
Reproduce the figures
The bundle includes analyze.py, attempts.csv, and metadata.json. Download them into the same directory and run:
|
|
The script needs pandas, numpy, and matplotlib. It reads the recorded snapshot locally and does not connect to production. It regenerates all six figures and the following tables:
- Daily readiness, in seconds relative to 09:30.
- Family summary, with historical sample counts and queue coverage.
- Every observed first morning setup task on Nov 12.
- Headline numbers.
The supporting evidence includes request phases, readiness-poll intervals, selected original log lines, and current run-level timestamps. The last file deliberately retains the misleading afternoon run timestamps so the rerun limitation is visible.
The production inspection was read-only. I did not profile the backend or remote trading hosts, rerun production tasks to benchmark them, or measure a before-and-after improvement from the new Redis gates. Those are the remaining checks behind the optimization suggestions.
Complete reproduction code and guide
Download the complete reproduction bundle, which contains the scripts, README.md, the original timing snapshot, and log evidence. The runner reproduces the saved results without credentials. Its optional --refresh mode reads Airflow over SSH; a password, when needed, comes only from AIRFLOW_SSH_PASSWORD in the environment.
|
|
The analysis script, read-only exporter, and Bash runner are also available separately. Install the recorded dependencies as described in README.md before running. All source files follow below.
analyze.py
|
|
export_airflow.py
|
|
run.sh
|
|
README.md
|
|
相关内容
支付宝
微信

william