'Laravel Database Transactions with Locking for Flash Sale system (Concurrent Requests)

I'm very new to this concept of Database locking/isolation. I watched some videos on the Topic of ACID properties.

The concept is pretty much clear, but I'm not sure how I can implement this in Laravel.

I'm trying to make a "Flash Sale" system, like suppose 50,000 users trying to order 1,000 products, where most requests would be concurrent.

As I understood DB::transaction will ensure if anything goes wrong, then everything will be rolled back, but it won't solve the concurrent requests problem, right?

I read about laravel's Pessimistic Locking - Laravel Docs, but very confused between ->sharedLock() and ->lockForUpdate() I couldn't find much detailed article or vids in this topic, even the Laravel documentation is very short in this topic.

How do I use these in my case. I think I would be able to explain better by pasting the code and explaining each line via comments.

Thank you so much in advance.

So, here it is...


        DB::transaction(function () use ($request, $productIdsQty, $address) {
            
            // Create the Order
            $order = $request->user()->orders()->create([
                'payment_method' => $request->payment_method,
            ]);

            $isOnlinePayment = $order->payment_method != 'cod';
            
            // assign Address for the Order
            $order->address()->create($address->toArray());
            
            // Here the problem occurs, suppose when this was queried that time all products had enough stock. 
            // but when we later attaching these products to our Order, 
            // what if one or more items had some change in stock at that point.
            $products = Product::whereIn('id', Arr::pluck($request->products, 'id'))->get();
            
            foreach ($products as $product) {
                
                // User demanded qty
                $qty = $productIdsQty[$product->id];

                // max_qty is just a accessor 
                // which returns the qty user can order right now 
                // based on available stock and admin set max_qty_per_order - whichever is lower, 
                // I'm using this line as a where clause. 
                // I dont know if this is the right way, but it just works for now
                $product = $product->max_qty >= $qty ? $product : null;

                // Decrement the product stock
                if ($product->stock_mode == 'manual') {
                    $product->decrement('stock', $qty);
                }

                // Add the product to ordered items
                $order->items()->create([
                    'qty' => $qty,
                    'product_id' => $product->id,
                    'unit_price' => $product->price,
                    'unit_mrp' => $product->mrp,
                    'status' => $isOnlinePayment ? 'payment_pending' : 'order_placed',
                ]);
            } // Products foreach loop ends

            // Process prepaid orders
            if ($isOnlinePayment) {
                // Add payment expiry for prepaid orders
                $order->update(['payment_expires_at' => now()->addMinutes(20)]);
                return Inertia::location(route('order.pay-now', $order->id));
            }
        });


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source