Adalarasu V N
03/23/2026, 11:36 AMMarvin
03/23/2026, 11:36 AMAdalarasu V N
03/23/2026, 11:50 AMMarvin
03/23/2026, 11:51 AMPREFECT_SERVER_DATABASE_CONNECTION_URL
- PREFECT_SERVER_DATABASE_TIMEOUT (statement timeout, default 10s)
- PREFECT_SERVER_DATABASE_CONNECTION_TIMEOUT (default 5s)
- 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)
- Suggested starting point for your throughput:
export PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_SIZE=20
export PREFECT_SERVER_DATABASE_SQLALCHEMY_MAX_OVERFLOW=40
export PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_TIMEOUT=60
export PREFECT_SERVER_DATABASE_TIMEOUT=30
export PREFECT_SERVER_DATABASE_CONNECTION_TIMEOUT=10
Note: PREFECT_API_DATABASE_* aliases also work; stick to one prefix for clarity.
- Reduce worker pickup latency (poll faster and prefetch)
- These control how quickly workers grab new runs:
export PREFECT_WORKER_QUERY_SECONDS=3 # default 10
export PREFECT_WORKER_PREFETCH_SECONDS=30 # default 10
export PREFECT_WORKER_HEARTBEAT_SECONDS=20 # default 30
- This will increase API traffic slightly, but will reduce “start late” behavior.
- Lower the scheduler loop interval (if you’re running the built-in scheduler service)
- The scheduler loop defaults to 60 seconds; at scale, that can manifest as ~1 minute delays when many runs are due at once.
- Reduce it carefully (it increases DB load):
export PREFECT_API_SERVICES_SCHEDULER_LOOP_SECONDS=10
If your environment prefers the SERVER prefix, PREFECT_SERVER_SERVICES_SCHEDULER_LOOP_SECONDS=10 is accepted too.
- Trim event retention (keeps DB lean under write-heavy loads)
- Events grow fast in busy installs and can stress vacuum/IO:
export PREFECT_SERVER_EVENTS_RETENTION_PERIOD="2d" # default 7d
- Revisit after a few days; if the DB calms down, you can lengthen slightly.
- Monitor and confirm you’re not exhausting DB connections
- On Postgres:
SELECT count(*), state FROM pg_stat_activity GROUP BY state;
SELECT pg_size_pretty(pg_total_relation_size('events'));
- If you increase the server pool sizes, ensure Postgres max_connections has plenty of headroom.
Why this helps your specific errors/delays
- “Failed to submit flow run … _give_worker_labels_to_flow_run … 500 Internal Server Error”: that PATCH is small/fast. When it fails with 500, the server almost certainly hit a DB timeout or could not get a connection from the pool in time. Increasing pool_size/max_overflow/pool_timeout and DB statement/connection timeouts is the most effective mitigation.
- “Some runs start ~1 minute late”: by default, the scheduler runs every 60s and workers poll every 10s. Under a 5‑minute burst (30+ due at once), this shows up as delays. Lowering the scheduler loop and worker query interval, plus prefetch, reduces these delays.
Architecture and capacity tips (when you can)
- Scale server capacityMarvin
03/23/2026, 11:51 AMexport PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_STATEMENT_CACHE_SIZE=0
export PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_PREPARED_STATEMENT_CACHE_SIZE=0
Useful reference and a related issue
- A community report tying intermittent 500s to DB timeouts/pool pressure: Random 500 Internal Server Error in UI
- Their workaround also involved increasing PREFECT_API_DATABASE_CONNECTION_TIMEOUT and PREFECT_API_DATABASE_TIMEOUT.
A few clarifying questions to fine‑tune next steps
- Which Prefect version are you on? If not on the latest 3.x, I recommend upgrading — we ship server/DB performance improvements regularly.
- Rough DB size and Postgres version? Any PgBouncer in front?
- What’s the CPU and IO utilization on the Postgres host when you see the 500s/delays?
- Are all 30+ runs due exactly at the same minute (5‑minute boundary), or slightly staggered?
If you can share a short window of server logs around a 500 failure (from the API VM), I can help pinpoint whether it’s a pool exhaustion vs. statement timeout vs. something else.
Quick checklist to try first
1) Set the server DB pool/timeouts (block above), restart the server.
2) Lower worker query/prefetch intervals (block above), restart workers.
3) Optionally reduce scheduler loop to 10s, watch DB load.
4) Shorten events retention to 2d for now.
5) Watch pg_stat_activity and server logs during the 5‑minute burst.
If the 500s persist after the pool/timeout changes, please share the server traceback around the 500; we’ll dig deeper.Adalarasu V N
03/23/2026, 11:56 AMMarvin
03/23/2026, 12:07 PMMarvin
03/23/2026, 12:08 PMPREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_SIZE=20
PREFECT_SERVER_DATABASE_SQLALCHEMY_MAX_OVERFLOW=40
PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_TIMEOUT=60
These are the knobs that typically stop intermittent 500s on PATCH /flow_runs/{id}/labels under load. Code reference: ServerDatabaseSettings and SQLAlchemySettings
- Reduce worker polling latency and prefetch a larger window
PREFECT_WORKER_QUERY_SECONDS=3 # default 10
PREFECT_WORKER_PREFETCH_SECONDS=30 # default 10
PREFECT_WORKER_HEARTBEAT_SECONDS=20 # default 30
This helps workers pick up due runs faster when you have 30+ runs arriving every 5 minutes.
- Optional: tighten the scheduler loop to reduce “minute-ish” delays
PREFECT_API_SERVICES_SCHEDULER_LOOP_SECONDS=10
Note: lower loop_seconds increases DB load; pair this with the larger DB pool above.
Important correction about this line:
- PREFECT_DEPLOYMENT_SCHEDULE_MAX_SCHEDULED_RUNS='1'
- That’s a Prefect 2.x-style setting and is ignored in Prefect 3.x. If your intent is to limit how many runs the scheduler schedules per loop, the Prefect 3 setting is PREFECT_SERVER_SERVICES_SCHEDULER_MAX_RUNS (default 100). Generally, don’t set this to 1 — it can cause under-scheduling and perceived delays.
A few trade-offs to be aware of:
- PREFECT_API_SERVICES_EVENT_PERSISTER_ENABLED=false will stop persisting events; good for load, but event-driven automations and some reactive UI features won’t have historical events. If you later re-enable it, your 24h retention is sensible for high volume.
- Keep using the SERVER_ prefix consistently (API_ aliases work, but consistency avoids confusion).
After applying:
- Restart the server and workers so settings take effect.
- Watch Postgres during the 5‑minute bursts:
- Connections vs max_connections
- Query latency / timeouts
- events, flow_run_state table growth
If you still see 500s, please grab the server logs around a failing request so we can confirm if it’s connection pool exhaustion vs statement timeout. Related community report for similar 500s under load: Random 500 Internal Server Error in UI.
Optional extra that helps at this scale:
- Stagger schedules a bit (add small offsets/jitter) to avoid all 30+ runs landing on the same second of the 5‑minute boundary.
Want me to propose exact env blocks for your two VMs (server and workers) based on this?