Skip to content

fix(backend/executor): stop unpickling paused/fired schedules on every get_execution_schedules read - #14439

Open
Bentlybro wants to merge 2 commits into
devfrom
bently/autogpt-server-9nb-slow-db-query-on-get-graph-execution-schedules-u
Open

fix(backend/executor): stop unpickling paused/fired schedules on every get_execution_schedules read#14439
Bentlybro wants to merge 2 commits into
devfrom
bently/autogpt-server-9nb-slow-db-query-on-get-graph-execution-schedules-u

Conversation

@Bentlybro

Copy link
Copy Markdown
Member

Why / What / How

Why: Sentry flagged /get_graph_execution_schedules for a slow, unbounded DB query (AUTOGPT-SERVER-9NB). Scheduler.get_execution_schedules() — which backs both that RPC and the /schedules REST routes — reads through a 5s process-wide cache. On a cache miss it called APScheduler's stock SQLAlchemyJobStore.get_all_jobs(), which runs SELECT id, job_state FROM apscheduler_jobs ORDER BY next_run_time with no WHERE clause — scanning and unpickling every row in the table. That includes paused schedules and already-fired one-shot jobs (APScheduler marks both with next_run_time = NULL), neither of which anything ever deletes, and both of which the caller immediately discards in Python for every caller except the two pause/resume lifecycle lookups. As that dead-row backlog grows, the query only gets slower.

What: Added a second cache, _get_active_jobs_cached, that pushes a next_run_time IS NOT NULL filter down to SQL via the jobstore's existing next_run_time btree index (already present on APScheduler's stock table definition — no schema change or migration needed). get_execution_schedules now reads through it by default; the two include_paused=True lifecycle callers (pause/resume) keep reading the original unfiltered cache, since they need to find paused rows too.

How: Kept the SQLAlchemyJobStore instance backing the EXECUTION jobstore as a named self._execution_jobstore reference (instead of only living inline in the jobstores= dict) so the new cache method can query its table directly with a server-side filter, reusing the same engine/connection pool the scheduler already uses (no extra DB connections). The SCHEDULER_JOBS gauge (labeled status="scheduled") now updates from the active-only count, which is the more accurate reading for that label.

Changes 🏗️

  • backend/executor/scheduler.py: new _get_active_jobs_cached() method with SQL-level next_run_time IS NOT NULL filtering; get_execution_schedules() now dispatches to it unless include_paused=True.
  • backend/executor/scheduler_test.py: new integration test test_paused_schedule_excluded_unless_include_paused (add → list → pause → list excludes it → list with include_paused=True finds it → resume → list finds it again), run against a real Postgres-backed apscheduler_jobs table.
  • backend/executor/scheduler_unit_test.py, backend/api/features/orgs/regression_test.py: updated two existing mocks that only stubbed the old unfiltered _get_jobs_cached path to also stub the new active-jobs path.

Agents and large language models used

Claude Code worker on tester VM, Sonnet/Opus 5.

Checklist 📋

For code changes:

  • I have clearly listed my changes in the PR description
  • I have made a test plan
  • I have tested my changes according to the test plan:
    • poetry run test backend/executor/scheduler_test.py — 56 passed (real Postgres + real APScheduler jobstore, including the new pause/resume regression test)
    • poetry run test on the 8 other files calling get_execution_schedules/get_graph_execution_schedules (scheduler_unit_test.py, v1_test.py, experts/scheduling_test.py, home/service_test.py, experts/experts_db_test.py, copilot/tools/session_context_test.py, copilot/tools/manage_schedules_test.py, orgs/regression_test.py) — 498 passed, 12 xfailed
    • poetry run ruff check / isort --check / black --check / pyright on all touched files — clean

For configuration changes:

  • .env.default is updated or already compatible with my changes
  • docker-compose.yml is updated or already compatible with my changes
  • I have included a list of my configuration changes in the PR description (under Changes)

…y read

get_execution_schedules() (and get_graph_execution_schedules(), the RPC
Sentry flagged as a slow, unbounded query) backed every non-lifecycle read
with Scheduler._get_jobs_cached(), which calls APScheduler's stock
SQLAlchemyJobStore.get_all_jobs() -- a SELECT with no WHERE clause that
scans and unpickles every row in apscheduler_jobs, including paused
schedules and already-fired one-shot jobs that nothing ever deletes and
that the caller immediately throws away in Python.

Add a second cache, _get_active_jobs_cached(), that pushes a
next_run_time IS NOT NULL filter down to SQL via the jobstore's existing
next_run_time btree index (already present on the stock APScheduler
table -- no migration needed). Every caller except the two
include_paused=True pause/resume lifecycle lookups now reads through it.
Follow-up to the previous commit: the comment above the original
_get_jobs_cached still described it as the primary cache; note that
_get_active_jobs_cached is now what everything but the two
include_paused=True lifecycle lookups actually calls.
@Bentlybro
Bentlybro requested a review from a team as a code owner September 8, 2026 10:14
@Bentlybro
Bentlybro requested review from 0ubbe and Abhi1992002 and removed request for a team September 8, 2026 10:14
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Sep 8, 2026
@github-actions github-actions Bot added cla: pending CLA not yet signed by all contributors size/l cla: signed CLA signed by all contributors platform/backend AutoGPT Platform - Back end and removed cla: pending CLA not yet signed by all contributors labels Sep 8, 2026
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: fc15fdb3-7343-4194-82a7-d3c50ed9f1f5

📥 Commits

Reviewing files that changed from the base of the PR and between 30ea0d2 and 5d13ded.

📒 Files selected for processing (4)
  • autogpt_platform/backend/backend/api/features/orgs/regression_test.py
  • autogpt_platform/backend/backend/executor/scheduler.py
  • autogpt_platform/backend/backend/executor/scheduler_test.py
  • autogpt_platform/backend/backend/executor/scheduler_unit_test.py

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (9)
  • GitHub Check: check API types
  • GitHub Check: Build, smoke, and scan (linux/amd64)
  • GitHub Check: Build, smoke, and scan (linux/arm64)
  • GitHub Check: end-to-end tests
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
Format Python code with `poetry run format`

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • autogpt_platform/backend/backend/executor/scheduler_unit_test.py
  • autogpt_platform/backend/backend/api/features/orgs/regression_test.py
  • autogpt_platform/backend/backend/executor/scheduler_test.py
  • autogpt_platform/backend/backend/executor/scheduler.py
🧠 Learnings (2)
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/api/features/orgs/regression_test.py
  • autogpt_platform/backend/backend/executor/scheduler.py
📚 Learning: 2026-08-13T22:09:30.099Z
Learnt from: dexhunter
Repo: Significant-Gravitas/AutoGPT PR: 13749
File: autogpt_platform/backend/backend/util/test_json.py:767-771
Timestamp: 2026-08-13T22:09:30.099Z
Learning: In the AutoGPT backend, do not report missing mutable class-attribute annotations as required lint fixes for Ruff rule RUF012, because Ruff is pinned to version 0.15.0 and the repository configuration does not select RUF012. Reconsider this guidance if the pinned Ruff version or configured rule selection changes.

Applied to files:

  • autogpt_platform/backend/backend/executor/scheduler.py

Walkthrough

The scheduler now uses a dedicated active-job cache with a SQL-level next_run_time IS NOT NULL filter. Schedule retrieval includes paused jobs only when requested. Tests cover filtering, resuming, cache invalidation, gauges, and organization visibility.

Changes

Schedule cache filtering

Layer / File(s) Summary
Execution jobstore and cache paths
autogpt_platform/backend/backend/executor/scheduler.py
The execution jobstore is stored as self._execution_jobstore. Active jobs use a separate cache and direct filtered query. Cache invalidation clears both cache variants.
Schedule selection and validation
autogpt_platform/backend/backend/executor/scheduler.py, autogpt_platform/backend/backend/executor/scheduler_test.py, autogpt_platform/backend/backend/executor/scheduler_unit_test.py, autogpt_platform/backend/backend/api/features/orgs/regression_test.py
get_execution_schedules selects the active cache by default and the unfiltered cache for include_paused=True. Tests cover paused schedules, resume behavior, gauges, invalidation races, and mocked organization visibility.

Priority: ⬇️ Low — Impact reflects low issue severity.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to 5d13d

Routine schedule reads now exclude paused schedules at the database layer while explicit paused-schedule lifecycle operations remain supported. Filtering, resume behavior, cache invalidation, and gauges are covered, with no remaining merge-readiness risk identified.

Suggested reviewers: ntindle

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the scheduler change: it prevents repeated unpickling of paused and fired schedules during get_execution_schedules reads.
Description check ✅ Passed The description directly explains the performance issue, the active-job cache implementation, affected callers, tests, and validation results. It is clearly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bently/autogpt-server-9nb-slow-db-query-on-get-graph-execution-schedules-u

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.23077% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.34%. Comparing base (6dc5fec) to head (5d13ded).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #14439      +/-   ##
==========================================
- Coverage   81.34%   81.34%   -0.01%     
==========================================
  Files        3515     3515              
  Lines      263403   263445      +42     
  Branches    24413    24416       +3     
==========================================
+ Hits       214278   214305      +27     
+ Misses      43780    43721      -59     
- Partials     5345     5419      +74     
Flag Coverage Δ
platform-backend 86.35% <94.23%> (+<0.01%) ⬆️
platform-frontend-e2e 28.53% <ø> (-0.24%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Platform Backend 86.35% <94.23%> (+<0.01%) ⬆️
Platform Frontend 62.72% <ø> (-0.03%) ⬇️
AutoGPT Libs ∅ <ø> (∅)
Classic AutoGPT 28.43% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: signed CLA signed by all contributors platform/backend AutoGPT Platform - Back end size/l

Projects

Status: 🆕 Needs initial review

Development

Successfully merging this pull request may close these issues.

1 participant