Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

isarantoglou's avatar

Problem with API auto generating same number due too many requests

Hello,

I am facing a problem with a laravel application, lets assume the API is an invoicing endpoint.

When a POST request is made the application auto-generates the next number, so lets assume there are no invoices. So, 1st request will assign number: 1, next request number: 2 etc. The way the application works, before saving the invoice to the databases, gets the MAX number from the database and assigns the next one.

The problem is when there are too many API requests the application assigns the same number (I suppose cause it did not have time to save at database).

I am not sure how I can fix the problem or what changes I need to make to the application.

I hope I describes the problem well :)

Thank you, Ilias

0 likes
2 replies
LaryAI's avatar
Level 58

Hello Ilias,

The issue you're facing is a common one in concurrent systems where multiple processes are trying to read and write to the same resource—in this case, the invoice number. The problem arises because the read (getting the MAX number) and write (saving the new invoice with the next number) operations are not atomic, leading to a race condition.

To solve this problem, you can use one of the following strategies:

  1. Database Auto-Increment: If you're not already using it, the simplest solution is to let the database handle the incrementing of invoice numbers by using an auto-incrementing field. This is atomic and thread-safe, ensuring that each insert gets a unique number.

    // Your Invoice model would have an auto-incrementing ID or a specific field for the invoice number.
    $invoice = new Invoice();
    // Set other properties
    $invoice->save();
    // The invoice number is automatically set by the database.
    
  2. Optimistic Locking: If you cannot use an auto-incrementing field for some reason (e.g., you have a custom numbering scheme), you can implement optimistic locking. This involves adding a version number or timestamp to the invoice record. When updating, you check that the version or timestamp hasn't changed since you last read it.

    // Fetch the latest invoice with its version number
    $latestInvoice = Invoice::latest()->first();
    $newInvoiceNumber = $latestInvoice->number + 1;
    $version = $latestInvoice->version;
    
    // Attempt to save a new invoice with the next number and incremented version
    $newInvoice = new Invoice();
    $newInvoice->number = $newInvoiceNumber;
    $newInvoice->version = $version + 1;
    // Other properties...
    
    // Use a query that checks the version hasn't changed and updates it atomically
    $success = Invoice::where('number', $latestInvoice->number)
                      ->where('version', $version)
                      ->exists();
    
    if ($success) {
        $newInvoice->save();
    } else {
        // Handle the case where the version has changed (another invoice was created in the meantime)
    }
    
  3. Database Locks: Use database locks to ensure that only one transaction can generate an invoice number at a time. This can be done with a table lock or row-level lock, depending on your database system.

    DB::transaction(function () {
        // Lock the table to prevent other transactions from reading or writing to it
        DB::table('invoices')->lockForUpdate()->get();
    
        $latestInvoiceNumber = Invoice::max('number');
        $newInvoiceNumber = $latestInvoiceNumber + 1;
    
        $invoice = new Invoice();
        $invoice->number = $newInvoiceNumber;
        // Set other properties
        $invoice->save();
    });
    
  4. Queue System: If the API can handle asynchronous processing, you could queue the invoice creation requests and process them one at a time. This ensures that each request is handled in isolation, preventing the race condition.

    // Dispatch the invoice creation to a job queue
    ProcessInvoiceCreation::dispatch($invoiceData);
    

    Then, in your job class:

    public function handle()
    {
        // Logic to create the invoice
        $latestInvoiceNumber = Invoice::max('number');
        $newInvoiceNumber = $latestInvoiceNumber + 1;
    
        $invoice = new Invoice();
        $invoice->number = $newInvoiceNumber;
        // Set other properties
        $invoice->save();
    }
    

Each of these solutions has its trade-offs in terms of complexity, performance, and reliability. You'll need to choose the one that best fits your application's requirements. If you're looking for simplicity and reliability, and your numbering scheme allows it, the auto-increment feature of the database is usually the best choice.

isarantoglou's avatar

Reading Lary's answer I wanted to add that the API is a multitenant one, each user has its own API key and the numbering scheme is per user.

Please or to participate in this conversation.