A 600k-row queue slowed our Mac to a crawl
Load past 30 on a 10-core Mac, swap nearly full, fseventsd pinned for hours. Spotlight was the wrong suspect; a SQLite job queue sorting every row into a temp file was the lead.
Our Mac, a 10-core machine that runs local models and a stack of background jobs, was barely answering. The cause turned out to be a job queue that had grown to 605,034 rows in about 16 hours, and a claim query that sorted every one of them into a temp file each time a worker asked for work. This is how we found it, what we changed, and the part we still cannot prove.
What slow looked like
The machine was not frozen, just drowning. Every reading was well past its normal line:
| Reading | During the slowdown | Normal line |
|---|---|---|
| Load average | 33 to 60 | alarm at 30 (10 cores) |
| Swap in use | 9.4 of 11 GiB | |
| Local API reads | 5 to 35 s | 2 s limit |
fseventsd CPU | 88 to 99%, for hours | |
kernel_task CPU | 110 to 217% |
Two lines stood out. fseventsd is the macOS daemon that turns filesystem changes into events for anything watching a folder, and it should sit near idle. kernel_task climbing past one full core usually means the kernel is busy on someone else’s behalf. Something was touching the disk very hard.
The wrong first suspect
A busy fseventsd and a hot disk point straight at Spotlight, so that went first.
Swap easing was a real improvement, which made the guess feel right for a moment. But the daemon we were worried about did not move, so whatever fed it was still running.
Counting the filesystem calls
Guessing had stopped working, so we measured. fs_usage prints every filesystem call on the machine as it happens; run as root with a filesystem filter, a short sample says who is doing what:
# 25-second sample of every filesystem call, wide outputsudo fs_usage -w -f filesys > fsu.txt # stop it after 25 secondsThat sample held about 2.03M filesystem calls, roughly 80k every second. Grouping the write-type calls by process gave one clear answer:
| What the sample showed | Reading |
|---|---|
| Filesystem calls in 25 s | about 2.03M (about 80k/s) |
pwrite calls from one source | about 24k |
| The source | short-lived Python processes of our local model runner |
| What each one wrote | an unlinked SQLite temp file (etilqs_*), about 43 MB |
etilqs_ is the prefix SQLite gives its temporary files; it is “sqlite” backwards. An unlinked temp file of that size means SQLite was spilling a big intermediate result to disk, again and again. The next question was which query.
Why a queue claim wrote 43 MB
The runner pulls work from a SQLite queue. A job-board fill had queued 605,034 jd-fetch rows, plus 3,513 jd-read rows, in about 16 hours. They were not duplicates: each was one distinct posting URL. Nothing capped how many could sit open.
Each worker claims its next job with a query that picks by tier. The tier order lived in an ORDER BY CASE expression, and SQLite cannot walk an index in the order of a CASE. The (state, tier, added_at) index was there and unused, so every claim did this:
-- the shape of the old claim, simplifiedSELECT id FROM queueWHERE state = 'open'ORDER BY CASE tier WHEN ... THEN ... END, added_atLIMIT 1;-- no index matches the CASE order, so SQLite sorts every open row-- into a temp b-tree before it can return the first oneOn a small queue that sort is cheap. With about 608k rows it became a fresh temp file of about 43 MB, written about every 17 seconds, by a new process each time.
Give your build a human voice
Eleven v4 is ElevenLabs' most emotive voice model yet, for text to speech, voice agents and an API.
Hear Eleven v4Sponsored
Make AI video from inside Claude
Higgsfield has an MCP for Claude, plus Seedance 2.5 and Cinema Studio 4.0. New accounts get up to 50% off for their first 3 hours.
Get up to 50% offSponsored
The fixes
- Drop the backlog. Deleting the 605,034 queued rows took 1.7 seconds.
- Cap the queue. A kind can now hold at most 5,000 open items, so a fill cannot grow it without limit again.
- Rewrite the claim. Instead of one
ORDER BY CASE, the claim probes each tier in turn on the index. On a 600k-row queue it now takes 6.6 ms and builds no temp b-tree. - Restart
fseventsd. It had been running for 23 days. Restarting it as root took it from 90.6% to 0.9% CPU.
The rewritten claim is the durable fix: the cost of finding the next job no longer depends on how long the queue is.
-- the shape of the new claim: one indexed probe per tier, in tier orderSELECT id FROM queueWHERE state = 'open' AND tier = :tierORDER BY added_atLIMIT 1;-- run for the first tier, then the next, until one returns a rowAfter
Once the queue was cleared, capped and claimed on the index, and fseventsd restarted, the machine came back:
| Reading | During | After |
|---|---|---|
| Load average | 33 to 60 | 8.6 (load1) |
| Swap in use | 9.4 of 11 GiB | 3.5 of 5.0 GiB |
fseventsd CPU | 90.6% | 0.9% |
kernel_task CPU | 217% | 30% |
The queue fixes went in first. The reading that dropped fseventsd from 90.6% to 0.9% came right after the restart, and the restart is what cleared the load.
Verdict
A queue that only ever grows and a claim query that sorts the whole queue are each harmless when the queue is small. Together, at about 608k rows, they made a background worker write a 43 MB temp file about every 17 seconds, and the Mac spent its day keeping up. Spotlight was the obvious suspect and the wrong one; one fs_usage sample found the real writer in 25 seconds.
If your Mac is slow and fseventsd is busy, count the filesystem calls before you turn things off. If a SQLite queue shows up, check that its claim can use an index, and give it a cap. And if fseventsd stays high after the writer is gone, a restart may be what finally clears it, as it was here.
Check your own queue: read SQLite’s EXPLAIN QUERY PLAN guide to see whether your claim query uses an index, and how the query planner picks one.
Responses
Responses
Community guidelines
Write about the post. Be kind and be specific.
Responses are plain text. Spam, abuse and anything unlawful are removed, and the person who posted them may be blocked.
A response may wait for the owner to read it before it appears. Use Report on anything that should not be here.
Give your build a human voice
Eleven v4 is ElevenLabs' most emotive voice model yet, for text to speech, voice agents and an API.
Hear Eleven v4Sponsored
Make AI video from inside Claude
Higgsfield has an MCP for Claude, plus Seedance 2.5 and Cinema Studio 4.0. New accounts get up to 50% off for their first 3 hours.
Get up to 50% offSponsored
Get the next one
New posts by email, when they are ready. Confirm once and you are on the list.
Check your inbox
We sent a link to . Click it and you are on the list. Nothing is sent to an address that has not confirmed itself.
Keep reading


Computer use for agents: agent-desktop install and first run
Computer use for agents with agent-desktop: install with npm, grant macOS Accessibility, snapshot an app, click a ref, scope reads and run several agents.

How to optimize your website for SEO with Claude
A measure-first SEO pass on two live sites: field data against Lighthouse, an HTML audit, and the fixes that cut LCP and fixed canonicals. Then what else to check on your own site.


How to duel other players in Aniimo (1v1 PvP)
To duel another player in Aniimo PvP, open their player card in the plaza, choose Fair Match and confirm. See each step and one sparring result.

