From 18ecd6603b430063f47ce1e50ddfd8f6b5ab95a7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 20 Jun 2025 18:13:22 +0530 Subject: [PATCH] perf: ~2x faster scheduling (#33027) * perf: Use cached settings * perf: Cache parsed crons, ~2x faster scheduling --- .../core/doctype/scheduled_job_type/scheduled_job_type.py | 7 +++++-- frappe/utils/scheduler.py | 4 +--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index 1dac746f18..75be455376 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -4,6 +4,7 @@ import hashlib import json from datetime import datetime, timedelta +from functools import lru_cache import click from croniter import CroniterBadCronError, croniter @@ -14,6 +15,8 @@ from frappe.model.document import Document from frappe.utils import get_datetime, now_datetime from frappe.utils.background_jobs import enqueue, is_job_enqueued +parse_cron = lru_cache(croniter) # Cache parsed cron-expressions + class ScheduledJobType(Document): # begin: auto-generated types @@ -132,10 +135,10 @@ class ScheduledJobType(Document): # A dynamic fallback like current time might miss the scheduler interval and job will never start. last_execution = get_datetime(self.last_execution or self.creation) - next_execution = croniter(self.cron_format, last_execution).get_next(datetime) + next_execution = parse_cron(self.cron_format).get_next(datetime, start_time=last_execution) if self.frequency in ("Hourly Maintenance", "Daily Maintenance"): next_execution += timedelta(minutes=maintenance_offset) - return croniter(self.cron_format, last_execution).get_next(datetime) + return parse_cron(self.cron_format).get_next(datetime, start_time=last_execution) def execute(self): if frappe.job: diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index e9dc7ccb23..12090d629c 100644 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -170,9 +170,7 @@ def is_scheduler_disabled(verbose=True) -> bool: cprint(f"{frappe.local.site}: frappe.conf.disable_scheduler is SET") return True - scheduler_disabled = not frappe.utils.cint( - frappe.db.get_single_value("System Settings", "enable_scheduler") - ) + scheduler_disabled = not frappe.get_system_settings("enable_scheduler") if scheduler_disabled: if verbose: cprint(f"{frappe.local.site}: SystemSettings.enable_scheduler is UNSET")