Hi guys! I am self-hosting Prefect with Postgres backends, recently I've been experiencing DB timeou...
t
Hi guys! I am self-hosting Prefect with Postgres backends, recently I've been experiencing DB timeouts and server crashing, has anyone else experienced their prefect server crashing with the message
PANIC: unprotected error in call to Lua API (table overflow)
? Seems like a redis issue but I am not sure where to look at. @Marvin
m
thought for 287.6 seconds
Short answer: yes, that Redis Lua panic can happen with Prefect—if you’re using the Redis-backed features and they end up operating on very large sets. Let’s quickly triage and isolate whether Prefect is the source, then we can tune or mitigate. Why this can happen - Prefect 3.x only hits Redis if you’ve enabled any of: - Redis-backed concurrency lease storage - Redis-backed events messaging/causal ordering - Docket backed by Redis - Those components use small Lua scripts for atomic operations. Under extreme cardinalities (e.g., very large sets or many keys in a single eval), Redis’s embedded Lua can raise “PANIC: unprotected error in call to Lua API (table overflow)”. Most likely triggers in Prefect - Events causal ordering (Redis): a script reads followers of an event with
SMEMBERS
and returns them in a Lua table. If a single “leader” accumulates thousands of followers, the script can blow up. - Concurrency lease storage (Redis): a create/revoke script loops over a variable number of keys and does `HSET`s/index cleanup. If a single lease references a very large number of limits, the Lua table can grow too large. First, confirm whether Prefect is actually using Redis Run this to see effective settings:
Copy code
prefect config view | grep -i 'redis\|events\|concurrency\|docket'
If you see any of these pointing at Redis, Prefect is using it: -
PREFECT_SERVER_EVENTS_MESSAGING_BROKER
/
PREFECT_SERVER_EVENTS_MESSAGING_CACHE
/
PREFECT_SERVER_EVENTS_CAUSAL_ORDERING
-
PREFECT_SERVER_CONCURRENCY_LEASE_STORAGE
-
PREFECT_SERVER_DOCKET_URL
Quick isolation steps - If events are on Redis, switch them to memory temporarily and restart the server: - Unset or set these back to memory implementations: -
PREFECT_SERVER_EVENTS_MESSAGING_BROKER=prefect.server.utilities.messaging.memory
-
PREFECT_SERVER_EVENTS_MESSAGING_CACHE=prefect.server.utilities.messaging.memory
-
PREFECT_SERVER_EVENTS_CAUSAL_ORDERING=prefect.server.events.ordering.memory
- If concurrency lease storage is on Redis, switch back to memory to test: -
PREFECT_SERVER_CONCURRENCY_LEASE_STORAGE=prefect.server.concurrency.lease_storage.memory
- If Docket points at Redis, set: -
PREFECT_SERVER_DOCKET_URL=memory://
If the panic disappears after any of the above, we’ve found the culprit. What to check in Redis - See which keys are growing large:
Copy code
redis-cli --scan --pattern 'prefect*followers*' | head
redis-cli --scan --pattern 'prefect*concurrency*' | head
# For any suspicious key:
redis-cli MEMORY USAGE <key>
redis-cli SCARD <key>     # for sets
redis-cli ZCARD <key>     # for sorted sets
- Capture the failing script/key names:
Copy code
redis-cli monitor | grep -i eval
That will show the EVAL/EVALSHA calls and keys being touched around the time of the panic. Mitigations if events/ordering is the source - Ensure you’re not accidentally fanning thousands of “followers” onto a single event. This can happen if many flows subscribe to the same trigger pattern without deduplication. - Trim/rotate old event state; the Redis messaging/ordering layer already trims, but if you find a single “followers” key with huge cardinality, you can safely delete it while the server is stopped to recover:
Copy code
# Only after stopping Prefect server
redis-cli DEL <the-large-followers-key>
- If you don’t need cross-process causal ordering, keep events on memory to avoid Redis entirely. Mitigations if concurrency leases are the source - Reduce the number of concurrency limits a single run acquires at once; avoid patterns where a lease covers a large number of “resource_ids”. - If you accidentally created many limits/holders, you can clear stale lease state (while the server is stopped) by deleting obviously stale keys under your concurrency prefix. General hardening - Upgrade to the latest Prefect 3.x and prefect-redis packages (bug fixes land there first).
- Use a modern Redis (6.2+/7.0+), ensure sufficient memory, and set sane limits/evictions for your use case. - Watch Redis logs and metrics (latency, memory, commandstats) and consider: -
lua-time-limit
(default is usually fine; your issue is overflow, not timeout) -
maxmemory
and an eviction policy appropriate for your workload - If you’re also hitting Postgres timeouts, that’s usually separate (pool exhaustion or long-running queries). If you can share the exact Postgres error and timing, I can recommend DB-side tuning next. Can you share: - Which of the Redis-backed features above you’ve enabled - Redis version and memory settings - Rough scale (events per second, number of deployments subscribing to events, number of concurrency limits) - A short snippet of the Redis MONITOR output around the panic (redacting secrets) References - Prefect Redis integration (source): prefect-redis repo - Lease storage implementation: lease_storage.py - Events causal ordering: ordering.py - Messaging: messaging.py - Prefect settings overview: Settings docs - Redis Lua scripting background: Redis Lua scripting
n
jeeeez this is probably my fault somehow but @Marvin you are wayyyy off track here, that repo is archived, look in prefecthq/prefect under src/integrations
m
thought for 396.9 seconds
t
@Nate I was gathering logs for claude and it says it's a combination of my postgres having performance degradation and:
Copy code
PostgreSQL is the root cause, Redis is the victim.
  1. Postgres gets slow. The RecentDeploymentsScheduler was already struggling from day 1 (5s+ on a 5s loop), and it got progressively worse over the 14 days —
   up to 37 seconds. On top of that, there's a connection from 10.7.30.110 that's been idle in transaction since Feb 9, holding a connection slot and
  potentially locks.
  2. docket tasks pile up in Redis. Every time a worker polls for work, Prefect schedules mark_work_queues_ready and mark_deployments_ready tasks into Redis
  via docket. These tasks need to write to Postgres — but Postgres is too slow/unresponsive. You can see them timing out at 29 seconds each:
  17:22:56.275 | ERROR | docket.worker - ↫ [ 29390ms] mark_work_queues_ready(...)
  17:22:56.282 | ERROR | docket.worker - ↫ [ 29385ms] mark_work_queues_ready(...)
  17:22:56.285 | ERROR | docket.worker - ↫ [ 29391ms] mark_deployments_ready(...)
  3. Workers keep polling, docket keeps queuing. Two workers polling every few seconds = a constant stream of new docket tasks being added to Redis. But the
  old ones can't complete because Postgres is choked. So Redis accumulates a growing backlog of pending/failed tasks.
  4. Redis overflows. After enough accumulation → PANIC: unprotected error in call to Lua API (table overflow).
n
has your claude looked here? https://docs.prefect.io/v3/advanced/database-maintenance#how-to-maintain-your-prefect-database is your events table super bloated?
t
@Nate oh yeah events + event_recourses are 5.4 gigs with 5M rows
m
You’re absolutely right—my earlier reference was to the archived repo. Sorry about that. In Prefect 3.x, the Redis integration lives in the monorepo under src/integrations. Here are the correct pointers and how to triage your crash. Where Prefect 3.x uses Redis (only if you enable it) - Concurrency leases (optional): lease_storage.py - Events causal ordering (optional): ordering.py - Messaging/streams (optional): messaging.py - Distributed locks (optional): locking.py - Integration root: src/integrations/prefect-redis Settings that switch these features on - Events (memory by default): -
PREFECT_SERVER_EVENTS_MESSAGING_BROKER
-
PREFECT_SERVER_EVENTS_MESSAGING_CACHE
-
PREFECT_SERVER_EVENTS_CAUSAL_ORDERING
- Source: server/events settings - Concurrency leases (memory by default): -
PREFECT_SERVER_CONCURRENCY_LEASE_STORAGE
- Source: server/concurrency settings - Docket (in-process memory by default): -
PREFECT_SERVER_DOCKET_URL
- Source: server/docket settings - General settings doc: Prefect settings About the Redis Lua “table overflow” panic - Prefect’s Redis paths do use small Lua scripts (in those files above) for atomic updates. A “table overflow” can happen if those scripts operate on very large collections (e.g., a set with thousands of members returned by
SMEMBERS
), or if you attach an unusually large number of resources/limits to a single lease. - If you haven’t enabled the Redis-backed features above, Prefect won’t be calling Redis at all—so the panic would be from some other component using your Redis. Quick triage checklist 1) Confirm if Prefect is using Redis at all
Copy code
prefect config view | grep -i 'redis\|events\|concurrency\|docket'
If any of the three events settings, the concurrency lease storage, or the docket URL point to Redis, Prefect is using it. 2) If using Redis, see what’s blowing up - Temporarily switch Redis-backed features to memory and restart Prefect to isolate:
Copy code
# events back to memory
export PREFECT_SERVER_EVENTS_MESSAGING_BROKER=prefect.server.utilities.messaging.memory
export PREFECT_SERVER_EVENTS_MESSAGING_CACHE=prefect.server.utilities.messaging.memory
export PREFECT_SERVER_EVENTS_CAUSAL_ORDERING=prefect.server.events.ordering.memory

# concurrency leases back to memory
export PREFECT_SERVER_CONCURRENCY_LEASE_STORAGE=prefect.server.concurrency.lease_storage.memory

# docket back to in-process
export PREFECT_SERVER_DOCKET_URL=memory://
If the panic stops, we’ve confirmed Prefect’s Redis path is involved. 3) Inspect Redis around the crash - Identify big keys and script activity:
Copy code
redis-cli SLOWLOG get 20
redis-cli monitor | grep -iE 'EVAL|EVALSHA'    # run briefly; very noisy
redis-cli --scan --pattern 'prefect*' | head
# For suspects:
redis-cli TYPE <key>
redis-cli SCARD <key>       # sets
redis-cli ZCARD <key>       # sorted sets
redis-cli HLEN <key>        # hashes
redis-cli MEMORY USAGE <key>
If you find a massive “followers” set (events ordering) or a giant hash/index (concurrency), that’s a likely root cause. Mitigations if it’s Prefect’s Redis usage - Events/ordering:
- Reduce fan-in (avoid thousands of flows waiting on a single event). - If a single followers set exploded, stop Prefect, delete that specific key to recover, then restart. - Concurrency: - Avoid creating leases that enumerate a very large number of resource IDs/limits. - Clean up obviously stale lease/index keys while Prefect is stopped if they ballooned. - General Redis hygiene: - Upgrade to Redis 6.2+/7.0+, allocate enough memory, set an appropriate
maxmemory
and eviction policy. - Keep
lua-time-limit
default unless you see timeouts (your error is overflow, not time). On your Postgres timeouts Since you also mentioned DB timeouts, double-check pool sizing vs. your server concurrency. Relevant settings: - Pool sizing/timeouts: server/database settings -
PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_SIZE
(default 5) -
PREFECT_SERVER_DATABASE_SQLALCHEMY_MAX_OVERFLOW
(default 10) -
PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_TIMEOUT
(default 30s) -
PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_RECYCLE
(default 3600s) -
PREFECT_SERVER_DATABASE_TIMEOUT
(statement timeout, default ~10s) - Engine creation ref: engine configuration - If you run multiple API workers, scale pool_size accordingly or front with PgBouncer (and set
statement_cache_size=0
in
connect_args
for transaction pooling). Can you share: - Which Prefect Redis settings (if any) are enabled in your env - Redis version and whether other services share that Redis - A snippet of Redis SLOWLOG or brief MONITOR around the panic - An example Postgres timeout message (from server logs) I’ll use that to pinpoint whether the crash is from Prefect’s Redis paths and recommend precise fixes.
n
you may be interested in something like this, depending on what your retention strategy is going to be
t
@Nate thank you! after some digging I think I realized why: I assumed that flow heartbeat behaves similarly to worker heartbeat where only the last beat was recorded, but apparently all flow heartbeats are stored as an event (I need to prune heartbeats). Does it make sense have a seperate configurable TTL for flow heartbeat events so that they can be pruned earlier than the rest
n
that's interesting. if we were going to do a TTL for events i think we'd have a service (background service) handle that and we likely wouldn't special-case heartbeats by default, but one could imagine some
prefect.flow-run.*
syntax for the service to say "clean up these ones" and when to clean them up
t
would you imagine that as a prefect automation or something on the postgres side? just wanna make sure I am doing thing sright
n
it sounds like a background service to me: https://github.com/PrefectHQ/prefect/pull/19280 we had a user start a contribution for this a while ago, which unfortunately was right in the middle of us migrating the background services to docket
it would not be a prefect automation, it would be some docket perpetual task like the other background services
EventPersister
etc that is configured via env vars, gets a db connection directly and starts via
prefect server services start
1
t
@Nate I saw that u left a comment on potentially taking over that PR, is that something you guys would want a PR from contributors? I would love to take a look at it as it would actually help with my system stability.
n
yes! that'd be an awesome thing to get a user contribution for as its not included in our near term roadmap but is something we want to do if you decide to take that up, itd be super appreciated, this would serve as a useful reference (https://dev-log.prefect.io/pop-lock-and-docket) and id be happy to help with any questions that come up
t
Yeah I was trying to look at what docket is, seems like it's something from you guys lol
also as a side question, it would be nice to check all prefect related service health in one place (DB, actual server, redis, worker health) in a dashboard, is that something that could be done easily?
n
its basically a modern version of arq/celery etc that one of our engineers built https://github.com/chrisguidry/docket we use it internally and externally all over
also as a side question, it would be nice to check all prefect related service health in one place (DB, actual server, redis, worker health) in a dashboard, is that something that could be done easily?
another interesting point, today no. but you could imagine heartbeat-style events emitted from background services, enabling a "background service health" dashboard in the new oss ui
t
🚀
as someone who's trying to make similar to marvin for my job I feel that
❤️ 1
n
honestly i just haven't really worked on it enough recently! the core structure of it is a relic of like 2024, its primitive in a lot of ways it serves the fairway 90% QA usecase well enough that i haven't been pushed to update it, but will try to soon
t
I feel like there's a lot of potentials in an agents framework that powers marvin (I guess some level of abstraction built ontop of PyDanticAI/LangChain or whatver's powering marvin)