Do not start the job: load shedding for scheduled batch work
One heavy batch job on a host that was already busy took our production down. The fix was not a faster job but a rule: if the machine is overloaded or short of disk, a scheduled job does not start, logs why, and tries again on the next tick.
π― For Anyone who runs cron jobs, backfills or agent batches next to a production service
- Why a busy host should refuse new batch work
- A small wrapper that sheds load, locks and lowers priority
- How to read the shed log as a capacity signal
π₯ What happened
On 8 July 2026 a heavy test run started on a host that was already busy. Load reached roughly eight hundred percent on eight cores, and the whole machine, including the production services, hung. Nothing was wrong with the job. It was wrong to start it then.
A second incident on 14 September 2026 had a different cause with the same shape: the disk reached ninety-seven percent because backups had no rotation, and every job that wrote in that window made it worse.
π The rule
A scheduled job does not start when the host is overloaded. It records the skip and exits successfully, and the scheduler starts it on the next tick. Data catches up later, and production keeps breathing. Our wrapper checks two things before running a job: the one-minute load against a limit, six by default on this eight-core host, and the free disk space against a minimum, two gigabytes by default.
If both pass, the wrapper runs the job under an exclusive lock so that two copies cannot overlap, at a lowered CPU priority and with idle disk priority. A second wrapper rule: if the disk check tool itself is broken, the disk check is skipped. A broken tool must not stop every job on the host.
π΅οΈ A watchdog that never kills
A separate watchdog runs every five minutes and writes its status to a file. It derives a soft and a hard load threshold from the number of cores. Past the hard line it lowers the priority of known batch processes, and it never kills anything. On 3 November 2026, 21:53 UTC it reported 8 cores, a soft threshold of 4.8 and a hard threshold of 6.8, with the last action "ok".
We also moved CPU shares: scheduled jobs together may use at most six of eight cores and carry a low weight, while production services carry a higher weight.
π The shed log
The wrapper appends a line each time it declines a job. Since 2026-07-30 the log holds 8,415 lines across 67 different jobs, and the four most frequent jobs account for 4,938 of them. The busiest day was 2026-10-15 with 1,155 declined starts.
Read this as a capacity signal, not as failure. Thousands of declined starts mean that the host is full at the moments when jobs are scheduled, and that the schedule needs spreading or the work needs moving to another machine. They also mean the guard is doing its job: each one was a start that might have hurt production.
π οΈ Practice: wrap your heavy jobs
- List the scheduled jobs and mark the heavy ones: tests, backfills, optimisers, exports.
- Write a wrapper that checks load and free disk and exits quietly with a log line when either is out of range.
- In the same wrapper add an exclusive lock, a lower CPU priority and idle disk priority.
- Put every heavy job behind the wrapper. Do this by file, not by editing a shared schedule table.
- Add a watchdog that records load and lowers priority of batch processes, and never kills them.
- Review the shed log weekly. Move or spread the jobs that are shed most often.
β Check yourself
- Start a CPU burner and then a wrapped job. The job is declined and the log shows the load figure.
- Start the same wrapped job twice. The second copy exits at once.
- Fill the disk past your minimum on a test host. Jobs that write are declined.
- You can say which job was declined most often last week.
π§ When this does NOT work
- Jobs that must run. A job whose deadline matters, such as a backup, should not be shed forever. Add a maximum skip count.
- Persistent overload. If the host is always over the limit, nothing runs. That means you need more capacity, not a lower limit.
- Spiky services. A short spike can decline a job that would have fit. Use a short moving average.
- Priority is not isolation. A low-priority job can still use memory and disk. Limits per group are stronger.
- Silent skipping. If nobody reads the log, you can lose a job for weeks. Alert on a job that has not completed for a day.
π Evidence
| What we measured | Lines and jobs in the shed log, the watchdog status file, and the thresholds in the wrapper |
| When | 3 November 2026, 21:53 UTC |
| Artifact | The shed log and the watchdog status file in our private deployment; the wrapper script and its design note |
| Limit | The log counts declined starts, not the harm they prevented. The two incidents come from our incident notes |
β‘οΈ Next
Plan what happens when something does break in An AI incident playbook for a small team, and read Recovery after a failed, stuck or uncertain run.
Sources
- An AI incident playbook for a small team: response steps.
- Recovery after a failed, stuck or uncertain run: recovery.
- Costs, budgets and limits: limits.
- Agent shell access: decision forks: shell access choices.
π Article connections
Open the full knowledge graph βPublished article links. Select a node to open its article at the heading.
Article links Β· keyboard / text view
Same topic
- A vector base is a map, not a source: what we learned from retrieval
- When you cannot tell what a model call cost: freeze, then reconcile
- Splitting a big task for a cheap model: the 24,000-byte lesson
- Skills, MCP tools and rules: three ways to give an agent capabilities
- Eight shell and git mistakes our agents made, and the rule for each
- Several agents, one repository: ownership rules from real incidents
Next steps
Related reading
A vector base is a map, not a source: what we learned from retrieval
A vector base finds passages that are close in meaning to your question. It does not know which are true or current. Treat every hit as a pointer: open the file it names, check the hash, and take numbers from the artifact, never from the retrieved paragraph.
AI agents and MCP explainedWhen you cannot tell what a model call cost: freeze, then reconcile
Sometimes a model call times out and you do not know whether the provider charged you. Never record that as zero. Mark it uncertain, stop new paid calls, and let a named person reconcile it with the provider's statement. Refusals before generation are the exception.
AI agents and MCP explainedSplitting a big task for a cheap model: the 24,000-byte lesson
A cheap model does well on one small, well-defined job and badly on a big pile. We cap a task at 24,000 bytes and refuse larger ones before any model is called. Split by unit, one file or one country per task, and check the size before you start.
AI agents and MCP explainedSkills, MCP tools and rules: three ways to give an agent capabilities
Agents get abilities in different ways. A rule constrains what they do, a skill tells them how to do a job, and an MCP tool lets them call a function. Each has its own risk, so declare all three in a register and test that nothing exists unlisted.
π¬ Comments