Skip to content

feat(DM01-6251): decouple blocking K8s API calls from scheduler main loop - #641

Merged
liuwei08 merged 2 commits into
masterfrom
feat/DM01-6251
Sep 9, 2026
Merged

feat(DM01-6251): decouple blocking K8s API calls from scheduler main loop#641
liuwei08 merged 2 commits into
masterfrom
feat/DM01-6251

Conversation

@liuwei08

@liuwei08 liuwei08 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Move GET /api/v1/nodes and GET ibpipelineinvocations from the main scheduler loop into a daemon background thread that refreshes every 10s
  • Main loop reads K8s state from a lock-protected in-memory cache — no more blocking HTTP calls on the critical scheduling path
  • Increase main loop sleep(1)sleep(3) to reduce DB and K8s API pressure

Root Cause

Under K8s API load, the two blocking HTTP calls (10s timeout each) caused the nominal 1s scheduler loop to actually run every 10–20s. With a backlog of queued jobs this multiplied into queue-wait times of hours.

Evidence: generator/di-embedded-tests queue_wait = 10,211s (~2.8h). Normal expected wait: < 5s.

How the fix works

Before:

main loop (every ~20s when K8s is slow):
  HTTP GET /api/v1/nodes          ← blocks up to 10s
  HTTP GET /ibpipelineinvocations ← blocks up to 10s
  schedule()   ← jobs finally get scheduled
  sleep(1)

After:

background thread (every 10s, independent):
  HTTP GET /api/v1/nodes          → writes to _k8s_nodes cache
  HTTP GET /ibpipelineinvocations → writes to _k8s_pipelines cache

main loop (every 3s, stable):
  read _k8s_nodes cache    ← microseconds
  read _k8s_pipelines cache ← microseconds
  schedule()               ← runs 20x/min instead of ~3x/min
  sleep(3)

Risk

  • K8s state is max 10s stale — node capacity and orphan detection delay by at most 10s, acceptable
  • abort/timeout response latency increases by at most 3s, acceptable
  • Scheduling correctness unchanged — schedule(), handle_aborts(), handle_timeouts() logic untouched
  • Background thread exceptions are caught and logged, main loop unaffected

Test plan

  • Deploy to test instance and verify scheduler starts without error
  • Submit a batch of jobs and confirm queue_wait drops to seconds
  • Verify orphaned job cleanup still works
  • Monitor scheduler logs for background thread exceptions

…loop

Move GET /api/v1/nodes and GET ibpipelineinvocations out of the main
scheduling loop into a daemon thread that refreshes every 10s.
The main loop now reads from a lock-protected cache instead of making
synchronous HTTP requests on every tick.

Root cause: under K8s API load these calls blocked for up to 10s each,
causing the 1s scheduler loop to actually run every 10-20s. With a
backlog of queued jobs this produced queue-wait times of hours (confirmed:
generator/di-embedded-tests waited 2.8h before being scheduled).

Also increase main loop sleep from 1s to 3s to further reduce DB and
K8s API pressure. abort/timeout response latency increases by at most 3s,
which is acceptable.
- Add r.raise_for_status() before r.json() in both refresh methods so
  HTTP error responses (401, 503) raise an exception and leave the
  previously valid cache intact, instead of silently overwriting it
  with an empty list
- Run initial synchronous K8s fetch in __init__ so caches are
  populated before the first scheduler tick; prevents update_cluster_state
  from skipping the DB write during the first 10s after startup
- Split nodes and pipelines into independent background threads so a
  slow/hung K8s nodes API call does not delay the pipelines refresh
@liuwei08
liuwei08 merged commit 702f18f into master Sep 9, 2026
2 checks passed
@liuwei08
liuwei08 deleted the feat/DM01-6251 branch September 9, 2026 02:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant