Logic
User profiles are not created during user registration. Let’s write the
getProfile($userId)
function to retrieve user profile information. If the
profile doesn’t exist yet, we’ll create it with default parameters and return
the profile information. Note that the profiles
table has a unique key
constraint on user_id
.
Implement
To solve this, we can use the firstOrCreate
function, which is a convenient
way to retrieve the first record matching the attributes or create it if
it doesn’t exist.
|
|
Life is not easy
However, life is not always that simple. After testing locally, committing, pushing, and deploying, you suddenly receive errors from the database via Sentry:
|
|
Upon checking the profiles
table, you find that a record with
user_id=45661934453770
already exists. You wonder how to insert when the
record already exists, given the behavior of the firstOrCreate
function.
|
|
While contemplating where to start fixing the bug, you decide to check the Nginx log:
|
|
Aha! You’ve got it. There were two simultaneous requests, and both requests
evaluated is_null($instance = $this->where($attributes)->first())
as true
.
However, when $instance->save()
was called, only one of the two requests
succeeded, resulting in the error for the other request.
Solution
Instead of using the firstOrCreate
function and considering transactions and
table locks, which may not be suitable for systems with many simultaneous
requests, I suggest using the upsert
function introduced in Laravel version 8.10.
|
|
If you’re using Laravel version prior to 8.10, you can use an alternative
package like laravel-upsert.
When two requests come simultaneously, the upsert
function will not throw an
error, even if the profiles
table already contains a record for that user ID.
The behavior of the upsert
function may vary depending on the database
management system being used. The following is an example of the underlying
SQL statement (for MySQL), but keep in mind that the actual command may differ
for different database systems:
|
|
Conclusion
In cases where duplicate entries are acceptable and there is no unique key constraint, you can continue using firstOrCreate as usual. However, I recommend limiting its use and considering other solutions like upsert when appropriate.