D1 retries reads while checking for database writes
D1 adds up to two automatic retries for eligible read-only queries. A second check rolls back writes from retry attempts that pass its conservative SQL classification.
Retrying a failed database request is safe only when repeating it preserves the intended effect. A transient transport error does not prove that the database never executed the first attempt. Cloudflare's 11 September D1 release note adds automatic retries for read-only queries and describes two checks around that decision. D1 first classifies the SQL, then checks whether a retry actually modified the database.
Eligible failures can receive up to two retries. That means the initial execution and its retries form a bounded operation, rather than an indefinite loop. The response exposes the execution-attempt count through total_attempts, so a successful response need not hide that the service performed extra work to obtain it.
I like SQLite's small operational model, but D1 adds a remote request path around the engine. Retry policy is part of that path's behavior. Having the provider handle a narrow, well-defined class of retries can remove repeated application code without making every database operation safe to repeat.
Classification is deliberately conservative
The announcement limits automatic retries to queries using the SQLite keywords SELECT, EXPLAIN and WITH, without keywords that lead to writes. It calls the detection a simple heuristic with room for improvement. This is a useful limitation to state because a text-level classifier and the full semantics of a SQL statement are different things.
The purpose is to select operations the service expects to execute without changing database state. A query that falls outside this class does not automatically become unsafe under all circumstances. It simply does not receive this automatic policy. An application can know more about the effect of a particular operation than a general SQL classifier can infer.
For example, a business operation that records a desired final state can be designed to tolerate repetition. Another operation that increments a counter may apply its effect again when repeated. Both are writes. Their safety depends on the application's meaning and state model, which the presence of a write keyword alone cannot establish.
Cloudflare accordingly recommends retaining application retries for non-read-only operations that are idempotent under the application's business logic. That statement does not authorize blindly repeating every failed write. It assigns the semantic decision to the code that knows what repeating the operation means.
Execution gets a second check
The release note says D1 checks for modifications after query execution. If a retry attempt writes despite passing the read-only detection, D1 rolls that attempt back. This addresses a different question from the initial classification. The first check predicts whether retrying is appropriate, while the second checks the database effect of the attempted execution.
That arrangement avoids treating the heuristic as an absolute proof. A classification mistake should not silently commit database changes during an automatic retry. The rollback check protects the specific state boundary the announcement describes. It should not be expanded into a general claim that D1 can undo any external side effect an application performs around a query.
The distinction also matters when reading telemetry. The number of executions and the number of accepted state transitions are not interchangeable. A repeated read can execute more than once and return one response. A rejected write-producing retry can execute work that the service then rolls back. Counting client calls alone does not describe those internal attempts.
Retry success depends on the failure's duration
Cloudflare reports a wide range of retry success ratios, depending on the underlying error and how long it lasts. Its release note gives a range of 5% to 95% among read-only retryable errors. Those figures describe observed error categories, not a promised recovery rate for an arbitrary D1 application.
The mechanism explains why duration matters. A short interruption may end before another attempt reaches the service. A persistent failure can remain present throughout the bounded retry sequence. Retrying gives a transient condition another opportunity to clear, but does not repair the underlying failure or make a long outage disappear.
An application that already retries should also distinguish its own attempts from D1's internal attempts. Repeating a client call can ask the service to perform another bounded sequence. The relevant request budget therefore includes the policy at both layers. This follows from composing retries, even though the release does not prescribe one universal application limit.
Retrying and replica consistency answer different questions
D1's April replication design introduced sessions and bookmarks to preserve sequential consistency across replicas. That mechanism tells a database copy how far through the committed history it must progress before answering a dependent read. Automatic retries address failures to execute eligible queries. One mechanism does not replace the other.
A successful retry is still required to satisfy the consistency requirement attached to the request. Conversely, a correctly carried bookmark does not ensure that every network attempt succeeds. Keeping those concerns separate makes the service's behavior easier to reason about. The application expresses its logical history through sessions, and the service can repeat eligible work after transient failures.
I prefer this limited policy because the limits identify where application reasoning remains necessary. D1 supplies bounded retries and checks retry attempts for writes. The application still defines idempotence for its write operations and retains the session information required by dependent reads. The new attempt count lets a successful query remain observable as an operation that may have needed recovery work.