Skip to content

feat: add retry attempts to database transactions#10197

Open
memleakd wants to merge 1 commit into
codeigniter4:4.8from
memleakd:feat/transaction-retry-attempts
Open

feat: add retry attempts to database transactions#10197
memleakd wants to merge 1 commit into
codeigniter4:4.8from
memleakd:feat/transaction-retry-attempts

Conversation

@memleakd
Copy link
Copy Markdown
Contributor

Description

This PR proposes adding optional retry attempts to the closure-based database transaction() helper.

This is meant for the common case where a whole transaction can safely be tried again after a retryable concurrency failure, such as a deadlock or serialization failure:

$result = $db->transaction(static function ($db) use ($order) {
    $db->table('orders')->insert($order);

    return $db->insertID();
}, attempts: 3);

Retries are intentionally conservative. They only happen for RetryableTransactionException failures while the callback is running, including query and prepared-query execution failures classified by the database drivers. Other exceptions keep the existing behavior: the transaction is rolled back and the exception is rethrown.

A few boundaries are documented in the user guide:

  • attempts is the total number of tries, including the first run.
  • Retry attempts only apply when transaction() starts the outermost transaction.
  • Nested transaction callbacks still run once.
  • Retries run immediately without delay or backoff.
  • Commit-time failures are not retried.
  • Non-transactional side effects should be registered with afterCommit().

Tests cover successful retries, exhausted retries, suppressed query and prepared-query failures, transException(true), nested transactions, rollback callback behavior, rollback failures, and stale transaction exception state.

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

- Add an optional attempts parameter to transaction().
- Retry whole transactions for RetryableTransactionException failures.
- Preserve existing behavior for nested transactions and non-retryable failures.
- Document retry behavior, caveats, and side-effect guidance.

Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
@github-actions github-actions Bot added the 4.8 PRs that target the `4.8` branch. label May 13, 2026
@patel-vansh
Copy link
Copy Markdown
Contributor

Really nice feature. One thing that could be added is, similar to cURL retry config, we could have similar retrying config for db transactions too. We could let users pass delay so that the DB is not overloaded with same operations and has some time to breathe.

This is just an idea, would love to hear everyone's thoughts on this.

@memleakd
Copy link
Copy Markdown
Contributor Author

Really nice feature. One thing that could be added is, similar to cURL retry config, we could have similar retrying config for db transactions too. We could let users pass delay so that the DB is not overloaded with same operations and has some time to breathe.

This is just an idea, would love to hear everyone's thoughts on this.

Thanks for the recommendation. That makes sense as a possible follow-up, and it is actually already in my backlog.

I'm trying to keep this PR intentionally small with only the core primitive, attempts, so the first step is just opt-in whole-transaction retry for RetryableTransactionException.

I'd be happy to work on delay/backoff support separately once this foundation is in place.

@michalsn
Copy link
Copy Markdown
Member

As for the potential follow-up PR... Most retryable transaction failures, especially deadlocks, are often resolved naturally once the database aborts one of the competing transactions. In many cases an immediate retry of the whole transaction is enough, so adding a built-in delay does not seem necessary IMO.

It also does not really fit the database component responsibility. Delay, backoff are application/workload policy, not transaction management.

@memleakd
Copy link
Copy Markdown
Contributor Author

Thanks for clarifying the boundary. My initial thought was mostly DX-driven, since retry policy is something many users may otherwise need to rediscover and implement themselves. But I see the point about keeping this in application/workload policy, so I'll keep transaction() focused on retry attempts only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.8 PRs that target the `4.8` branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants