what is atomic lock in laravel

Atomic Lock is a way to make sure specific operations just happen by one user during a certain time.

let’s say we have an app that reserves seats for events like theater or conferences.

obviously, we don’t want to mistakenly give one seat to two people.

you might ask how could this happen.

this might happen when two requests hit the controller at the same time. in high-traffic websites, this kind of situation happens.

I will explain this with two examples:

Example 1: Reserving A Seat.

consider we have this in our controller:

<?php

namespace App\Http\Controllers;

use App\Models\Reserve;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class SeatController extends Controller
{
    public function reserveSeat()
    {
     //check if seat number not taken
        $is_seat_taken = (Reserve::where('seat_number', request('seat_number'))->first()) ? true : false;
        if ($is_seat_taken) {
            return back()->with('error', 'the seat is taken');
        }
        $reserve = new Reseve();
        $reserve->user_id = Auth::id();
        $reserve->seat_number = request('seat_number');
        $seat->save();

    }
}

this controller method does the job of saving reserved seats in the database.

if we have two users at the same time trying to reserve a seat, there will be two records in the table with the same seat number. since they hit the controller at the same time $is_seat_taken will be false for both of the requests.

So how we can solve this?

Atomic locks

Laravel has something called atomic locks that can prevent this from happening.

it means we lock the operation and do not allow any other request to pass until it’s completed.

for atomic locks in Laravel, we have to use one of these cache drivers:

  • file
  • array
  • database
  • redis
  • dynamodb
  • memecached

first, we need to create a table to save our locks:

Here is the migration in Laravel:

Schema::create('cache_locks', function (Blueprint $table) {
    $table->string('key')->primary();
    $table->string('owner');
    $table->integer('expiration');
});

then we can use Cache::lock to lock the operation for a certain time:


class SeatController extends Controller
{
    public function reserveSeat()
    {

        Cache::lock('reserve',10)->get(function (){

    $is_seat_taken = (Reserve::where('seat_number', request('seat_number'))->first()) ? true : false;
        if ($is_seat_taken) {
            return back()->with('error', 'the seat is taken');
        }
        $reserve = new Reseve();
        $reserve->user_id = Auth::id();
        $reserve->seat_number = request('seat_number');
        $seat->save();


        });

    }
}

we name the lock (I named it ‘reserve’) and as the second parameter pass a time in seconds.

with this method even if someone else tries to reserve this at the exact same time, inconsistency won’t happen in our database table.

Example 2: Importing Data To Database By User.

We have an import feature where users can import data to the software. the order of the records that are being imported is vital in this app. If two or three users import different files at the same time it could cause inconstancy in the data.

in this situation, we just use the Cache::lock method.

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
use Maatwebsite\Excel\Facades\Excel;

class ImportSurveys implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function handle()
    {
        Cache::lock('import', 20)->get(function () {
            Excel::import(new SurveyImport, 'surveys.xlsx');
        });


    }
}

I gave it 20 seconds because importing data from an Excel file may take more time. I wanted to make sure no other import is happening during this one.

so if others are trying to upload, they just have to wait until this one is finished.

Conclusion

these situations don’t happen often but we have to be aware and know how to handle them.

if you want to read more about atomic locks:

Aron Francis’s post about using atomic locks in Laravel.

This post talks about an issue with caching and how atomic lock helped.

Leave a Comment