Skip to content
← All issues

Readyset Weekly: Issue 81

What Your Agent Does to the Database When It Retries

If you are running an AI agent against a database, your query count is shaped as much by what fails as by what succeeds. When a tool call errors, times out, or returns something the model dislikes, the agent tries the query again, often with nearly the same SQL. One user request can turn into a burst of near-identical database hits, driven by two framework behaviors, retrying on error and the reasoning loop, both capped by defaults you may never have set.

Retrying on error is the first. When a tool fails, agent frameworks retry automatically. CrewAI retries on error up to its max_retry_limit, which defaults to 2, and frameworks that scope retries to transient failures count a timeout as one of them. A slow query under load looks like a transient failure, and a query can time out on the agent's side while it is still running to completion on the database. The agent sees the timeout and retries, so the database runs the same expensive query two or three times over while only one result is ever used.

The reasoning loop compounds the first issue. When a query fails without changing anything in the agent's state, the model tends to return the same query and fail the same way. Frameworks cap the loop: CrewAI's max_iter defaults to 20 iterations, though it stops a task after five identical tool calls with the same result. LangGraph halts at its recursion_limit of 25 steps, roughly a dozen tool calls. The caps stop an infinite loop, but a dozen runs of one heavy query, for a task that should have run it once, is still real database work.

This is easy to miss because each attempt looks legitimate on its own, with a valid query, a reasonable plan, and often no single execution slow enough to flag. The signal lives in the call counts: in pg_stat_statements on Postgres or performance_schema on MySQL, one query shape shows a call count far higher than the number of user requests behind it.

A few settings keep this in check:

  • Set a retry ceiling on purpose. Don't rely on the defaults, and make sure a failing query changes something in the agent's state before the next attempt.
  • Give database tools a timeout shorter than the agent's own. Use statement_timeout on Postgres or max_execution_time on MySQL (SELECT statements only), so a slow query is cancelled on the database rather than left running while a retry stacks up behind it.
  • Make write tools safe to retry. A timed-out INSERT or UPDATE may have already committed, so use upserts or idempotency keys to keep a retry from applying the same change twice.

Spotting the jump in call counts is harder than it sounds, because pg_stat_statements and performance_schema keep a running total with no history, so a retry storm that started this afternoon looks the same as one that has run for weeks. The RDST Agent solves this. It runs on your machine, reads your Postgres or MySQL query stats every minute, and keeps a per-query history. Every few minutes, it compares each busy query to its own baseline, flags the ones that changed, and analyzes why, so you can catch a retry pattern the day it starts rather than after a week of inflated database load.

🔍 What We're Watching in the Database Ecosystem

When the application layer is disposable: putting integrity constraints back in the database

Invariants that live in the application layer get bypassed when agents issue DML straight at the database. Kalumin and Deshpande report that LLM writers frequently violate constraints nothing enforces, and build constraint indexes as a Postgres extension that run up to 2.41× faster than the best enforcement methods.

Jepsen: ParticleDB 1.1.4

An LLM-assisted team built a database advertising full ACID and TLA+ proofs. Kyle Kingsbury found 96 issues, all but three on a single healthy node with no faults: SELECT 5 WHERE FALSE returned a row, and aborted transactions committed. Jepsen found the LLM tooling repeatedly claimed bugs were fixed when they weren't.

How a read replica almost took down its primary database

A backfill outran a BI replica, and the WAL held for its replication slot grew from about 5 GB to 31.7 TB, pushing the disk toward a 60 TB ceiling, and shrinking it afterward took downtime. Photoroom now caps retained WAL with max_slot_wal_keep_size, and the post has five more lessons from deploying agent-written code hundreds of times a week.

1M durable workflows/sec on sharded Postgres

Past 8 to 16 shards throughput went metastable: a slow shard drew more pollers, FOR UPDATE SKIP LOCKED dequeues scanned past more locked rows, and the cluster never recovered. A few per-shard advisory dequeue locks fixed it, so a process that can't get one moves on.

In Case You Missed It

New on our blog:

Introducing Automatic Failover and Multi-Server Support: Readyset 1.33 takes a list of database servers instead of one. If the server it follows for change capture goes down, it resumes on another from the last GTID with no rows lost or applied twice, and it spreads proxied reads across your replicas.

We're getting more social, catch the latest on Bluesky, X, dev.to, and Medium too.

Know a database aficionado who'd appreciate this? Forward it their way.

Get Readyset Weekly in your inbox

One email a week — what shipped, what we learned, and what is worth reading.