Context:
I'm encountering a race condition issue in my Laravel API. The API is used for student attendance tracking in a mobile app. The issue occurs when multiple requests for the same student and session are made simultaneously.
Controller Code:
/**
* Store attendance record
*
* @param AttendanceSaveRequest $request
* @return JsonResponse
*/
public function store(AttendanceSaveRequest $request): JsonResponse
{
info("starting transaction");
DB::transaction(function () use ($request) {
$attendance = Attendance::where('session_id', $request->session_id)
->where('student_id', $request->student_id)
->lockForUpdate() // 🔒 Prevent race condition
->first();
if ($attendance) {
throw new \Symfony\Component\HttpKernel\Exception\ConflictHttpException('Attendance already exists');
}
Attendance::create(array_merge($request->validated(), [
'entry_user_id' => auth()->id(),
]));
});
$resource = (new AttendanceResource($attendance))
->additional(['info' => 'The attendance has been saved.']);
return $resource->toResponse($request)->setStatusCode(201);
}
Database Table:
The attendances table has a composite primary key:
Issue:
When I test with high concurrency (e.g., using Postman Runner), I still get a Unique violation error:
SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "attendances_pkey"
DETAIL: Key (session_id, student_id)=(1799255, 2021010902) already exists.
usually in first 2 request.
my laravel.log look like this
SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "attendances_pkey"
starting transaction
starting transaction
Question:
Is my approach using lockForUpdate() incorrect? How can I properly prevent race conditions in this scenario? Any guidance is greatly appreciated!