marcoviola13 Jun 2026 10:24

Our Horizon workers start lean and creep upward over hours until the supervisor kills them on the memory limit and restarts. It is not a hard crash, just a steady climb. The jobs themselves are ordinary, dispatch an email, update a few rows, nothing that obviously holds memory.

I know the standard advice is to set a memory limit and let workers recycle, which we do, but I would like to understand the climb itself rather than just paper over it. Where does the slow growth usually come from in a long-running Laravel worker, and how do you actually find the offender rather than guessing?

Replies (5)
ivan_morozov13 Jun 2026 11:04

The usual suspect is anything static or singleton that accumulates per job. The classic is the query log: if you have not disabled it, Laravel keeps every executed query in memory for the life of the process, and over thousands of jobs that array just grows. Disable query logging on the connection in long-running workers and a lot of mystery growth disappears. It is the single most common cause I see.

0
simondev13 Jun 2026 12:44

Event listeners and resolved-once services that hold references are the next layer. If a job resolves something that registers a callback or pushes onto a static collection each time, it never gets released because the container and the statics outlive the job. Recycling the worker after N jobs is not papering over, it is the intended design for exactly this, since PHP was never built to run forever. Keep the recycle, but also hunt the static accumulation.

0
lukaszkrzyz13 Jun 2026 14:14

To find it rather than guess, log memory_get_usage at the start and end of each job and watch the delta. A job that ends a few kilobytes heavier than it started, every time, is your leak, and the per-job delta points straight at which job class. We tag the log with the job name and within an hour it is obvious which handler never gives memory back. Measure the slope, do not eyeball the total.

0
marcoviola13 Jun 2026 15:44

The query log was it. We never disabled it because under FPM the process dies after each request so it never mattered, and moving the same code into a persistent worker turned a harmless array into an unbounded one. Disabled it on the worker connection and the slope went flat. The per-job memory delta logging is now permanent so the next creeper shows up immediately. Thanks all.

0
katedev13 Jun 2026 17:04

Keep the memory-limit recycle anyway even after fixing the slope, because some growth comes from fragmentation in the allocator that no code change fully removes. A worker that recycles every few hundred jobs is healthy, not a defeat. The goal is a flat-enough slope that recycling is a safety net rather than a band-aid hiding a real leak. Sounds like you are there now.

0
Write a reply
Markdown. ```php blocks are runnable.