Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Retry On Duplicate Key Laravel Package

mpyw/laravel-retry-on-duplicate-key

Automatically retries database operations when a duplicate key error occurs in Laravel. Wrap Eloquent firstOrCreate, updateOrCreate, or insert calls in a retryable transaction to safely handle race conditions and unique constraint conflicts under concurrent writes.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with composer require mpyw/laravel-retry-on-duplicate-key. The service provider is auto-discovered; there is no config file to publish in the released version.
  • Know what you're installing. This repo is archived and its last release was March 2023. Pin it ("mpyw/laravel-retry-on-duplicate-key": "1.*") and plan to vendor or fork the source rather than rely on upstream fixes for Laravel 11/12.
  • Read src/ first, not the README. The whole package is a single small trait/helper. Skim it before wiring it into a model — you want to see exactly which exception codes it considers retryable and what backoff it uses.
  • First use case: a MySQL table with a unique index (say users.email) where you call firstOrCreate() / updateOrCreate() / save() inside a seeder, importer, or job queue worker, and under concurrency you intermittently get SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '...' for key '...'.
  • Minimal wiring — apply the trait to the affected Eloquent model and let the racy save()/create() calls go through it:
use Illuminate\Database\Eloquent\Model;
use Mpyw\LaravelRetryOnDuplicateKey\RetryOnDuplicateKey; // verify namespace against src/

class User extends Model
{
    use RetryOnDuplicateKey;
}

If the version you pull exposes the closure helper instead, the shape is:

RetryOnDuplicateKey::retry(function () use ($payload) {
    return User::firstOrCreate(['email' => $payload['email']], $payload);
});

Pick one integration point (model trait or explicit closure wrapping) and be consistent — mixing both makes behaviour hard to reason about during an incident.

Implementation Patterns

Wrap check-then-insert, not just create(). The whole point is that the read and the write are separate statements. firstOrCreate() is the canonical case; updateOrCreate(), findOrNew()->save(), and manual if (!exists) { create() } blocks are equally valid targets.

Seeders and importers. Long-running db:seed or CSV import commands are where this bites hardest — a single worker hitting a unique index repeatedly, or --parallel runners colliding. Applying the trait at the model level gives you retries without touching the seeder logic.

Queue jobs. Idempotent job patterns (firstOrCreate on an external ID) are a natural fit. The retry here is cheaper than re-dispatching the job, and it keeps the failure out of your failed_jobs table.

Keep it off hot paths. Use it for low-to-moderate contention (admin imports, webhook handlers, one-off migrations). For genuinely hot rows — counters, inventory, anything with hundreds of writes/sec — prefer a single atomic statement (Model::upsert(), raw INSERT ... ON DUPLICATE KEY UPDATE) or an explicit Cache::lock(). Retry loops on a saturated unique index just add load.

Layer with transactions carefully. If the closure touches more than one table, wrap the retried unit in DB::transaction() rather than retrying inside an outer transaction — a duplicate-key error marks the statement failed, and you want a clean transaction boundary on each attempt.

Testing. Assert the retry path explicitly by forcing the duplicate: create the row, then run the code under test in a way that skips the existence check, or mock the model to throw a duplicate-key QueryException on the first call and succeed on the second. Don't rely on --parallel to reproduce races; it will not reliably hit the window.

Gotchas and Tips

  • It only understands duplicate keys. Foreign-key violations, deadlocks, and lock-wait timeouts are not retried. If you see retries "not working," check the SQLSTATE — 23000/1062 is the one it cares about.
  • A retry is not a lock. Under sustained contention the attempt counter will exhaust and you'll get the original exception. Treat retry exhaustion as a real signal (bad index choice, missing upsert, hot row) rather than raising the retry count.
  • Archived status is the biggest gotcha. Since upstream is frozen, the safest move for a new project is to copy the implementation into App\Support\RetryOnDuplicateKey — it's small enough to audit and own. That removes the dependency, the version pin, and the Laravel-compatibility question in one step.
  • No config to publish. Retry counts and sleep intervals are class constants/properties in the source. To tune them, subclass and override — don't expect a config/retry-on-duplicate-key.php.
  • Silent retries hide problems. Add a log line on each attempt (Log::warning('duplicate-key retry', ['model' => ..., 'attempt' => ...])). Without it, a 5× retry rate looks identical to a healthy system until it starts failing outright.
  • Don't use it as a substitute for the schema. If two rows should never coexist, the unique index is doing its job — this package is for expected races, not for papering over missing constraints or application bugs that insert twice.
  • Debugging: reproduce deterministically by wrapping the call in DB::listen() and firing two concurrent artisan tinker processes against a scratch database. Inspect the exception's errorInfo[1] to confirm you're hitting 1062 and not something adjacent.
  • Extension points are thin by design. The trait's protected methods for "is this retryable?" and "how long to sleep?" are the only realistic hooks; if you need per-model retry budgets, override them in a base model rather than editing the vendor copy.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings