Within a Laravel-based platform there are three relevant tables :
- users.
- codes.
- users_codes.
Users can claim a code - in which case, what should happen is that the system gets the next available code from the codes table, marks it as allocated, and then sets up a new entry in users_codes which ties the user to the code.
Nothing special would need to be done whilst the site is under low load, however anticipating high load, when initially written this was done as a Transaction, which I had thought would prevent the same code being allocated twice.
DB::transaction(function () use($user) {
$code = Codes::getNextAvailableCode(); // Not the actual function, but works for the sake of example
$code->allocated = 1;
$code->save();
$uc = new UserCode();
$uc->user_id = $user->id;
$uc->code_id = $code->id;
$uc->save();
});
Now that the site is under high load, a couple of times the same code has been allocated to two different users. So it's clear that a Transaction isn't what I want.
Thinking it through, I initially thought that replacing the Transaction with locking would be an option, but the more I think about it, I can't really lock a table.
Instead, I think I need to be focussing on checking, before creating and saving the new UserCode() to ensure that there is no existing UserCode with the same $code->id?
Any suggestions? Is there a way that I've not considered that will allow this to work smoothly under high load (ie. not continually throw errors back to the user when they try and claim a code that has been taken a millisecond before)?