Eliminating Flash Sale Overselling: A PostgreSQL Multi-Phase Reservation System
See how Lamiya Wedding Center eliminated flash sale overselling. This PostgreSQL system uses pessimistic locking, compensating transactions, and idempotent webhooks without external infrastructure.
Impact Result
Zero overselling observed and fully automated webhooks..
In the high-stakes world of wedding attire e-commerce, one dress can make or break a customer's most important day. Lamiya Wedding Center solved one of the hardest problems in e-commerce inventory management: preventing overselling during high-concurrency flash sales while maintaining sub-second checkout performance.
This technical deep-dive breaks down the sophisticated inventory reservation architecture behind their Next.js + PostgreSQL platform, a real-world system that treats inventory as a distributed resource and delivers enterprise-grade reliability without external transaction coordinators.
The Challenge: The Inventory Concurrency Trap
Traditional e-commerce platforms handle stock management through simple decrement operations:
UPDATE products SET stock = stock - 1 WHERE id = ? AND stock > 0This approach fails catastrophically under concurrent load:
Race Conditions: Two customers read
stock = 1simultaneously, both proceed to checkout, but only one can actually receive the item.Abandoned Carts: Stock held during checkout expires but isn't reclaimed automatically.
Payment Gateway Timeouts: Orders complete payment, yet the inventory was already sold to someone else.
Flash Sale Stampedes: Sudden traffic spikes exhaust connection pools and create phantom inventory.
For a wedding center selling limited-run designer pieces with high-value transactions, inventory accuracy wasn't optional; it was existential.
The requirements were brutal:
Zero tolerance for overselling
Support for flash sales with 500+ concurrent users
Time-bounded cart holds (30-minute windows)
Idempotent payment webhook processing
Rollback-safe transaction orchestration
The Architecture: A Multi-Phase Reservation Protocol with Database-Native Compensating Transactions
Instead of managing complex state in the application layer, we implemented a database-native reservation pattern using tightly integrated PostgreSQL RPC functions and a dedicated reserved_stock table. While this implements compensation logic similar to Saga patterns, it operates entirely within a single PostgreSQL instance, avoiding the distributed coordination overhead of true service-oriented Sagas.
This creates a reliable safety net while keeping the application stateless and horizontally scalable.
The Data Model
-- Core inventory table (simplified)
CREATE TABLE product_color_sizes (
product_id UUID REFERENCES products(id),
color_id UUID REFERENCES product_colors(id),
size_id CHAR(3) REFERENCES sizes(id),
stock_quantity INTEGER NOT NULL DEFAULT 0,
low_stock_threshold INTEGER DEFAULT 5,
PRIMARY KEY (product_id, color_id, size_id)
);
-- Reservation ledger; the secret sauce
CREATE TABLE reserved_stock (
product_id UUID NOT NULL,
color_id UUID NOT NULL,
size_id CHAR(3) NOT NULL,
checkout_session_id UUID NOT NULL,
quantity INTEGER NOT NULL,
status TEXT DEFAULT 'active', -- active | consumed | released
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);The reserved_stock table serves as a journal of intent. Customers reserve the right to purchase within a time window rather than directly holding physical inventory.
Phase 1: Atomic Reservation (reserve_stock_rpc)
When checkout begins, the system acquires row-level locks to prevent race conditions:
CREATE OR REPLACE FUNCTION reserve_stock_rpc(
p_checkout_session_id uuid,
p_items jsonb,
p_reservation_minutes integer default 20
) RETURNS jsonb AS $
DECLARE
v_item jsonb;
v_product_id uuid;
v_color_id uuid;
v_size_id char(3);
v_quantity integer;
v_actual_stock integer;
v_total_reserved integer;
v_available integer;
v_now timestamptz := NOW();
v_out jsonb := '[]'::jsonb;
BEGIN
FOR v_item IN SELECT jsonb_array_elements(p_items) LOOP
v_product_id := (v_item->>'product_id')::uuid;
v_color_id := (v_item->>'color_id')::uuid;
v_size_id := (v_item->>'size_id')::char(3);
v_quantity := (v_item->>'quantity')::integer;
-- Row-level lock prevents concurrent modifications
SELECT stock_quantity INTO v_actual_stock
FROM product_color_sizes
WHERE product_id = v_product_id
AND color_id = v_color_id
AND size_id = v_size_id
FOR UPDATE;
-- Effective available stock = physical stock minus active reservations
SELECT COALESCE(SUM(quantity), 0) INTO v_total_reserved
FROM reserved_stock
WHERE product_id = v_product_id
AND color_id = v_color_id
AND size_id = v_size_id
AND status = 'active'
AND expires_at > v_now;
v_available := v_actual_stock - v_total_reserved;
IF v_available < v_quantity THEN
RETURN jsonb_build_object('success', false, 'error', 'Out of stock');
END IF;
END LOOP;
-- Idempotent: clear previous reservations for this session
DELETE FROM reserved_stock WHERE checkout_session_id = p_checkout_session_id;
-- Create new reservations
INSERT INTO reserved_stock(...)
SELECT ... FROM jsonb_array_elements(p_items);
RETURN jsonb_build_object('success', true, 'reservations', v_out);
END;
$ LANGUAGE plpgsql SECURITY DEFINER;Key Design Decisions:
Pessimistic locking (
FOR UPDATE) for high-contention scenarios. At Lamiya's scale (~500 concurrent checkouts on hot items during flash sales), row-level locks are held for milliseconds, well within PostgreSQL's comfort zone.Dual-source availability calculation (physical stock vs. soft holds) ensures accurate inventory visibility.
Idempotent cleanup on retries prevents duplicate reservations.
Phase 2: Order Creation with Automatic Compensation (create_order_rpc)
All critical steps; coupon validation, address handling, and stock reservation, happen inside one atomic transaction:
CREATE OR REPLACE FUNCTION create_order_rpc(
p_user_id uuid,
p_checkout_id uuid,
p_order_address_id text DEFAULT NULL,
p_payment_method text DEFAULT NULL,
p_address jsonb DEFAULT NULL,
p_save_address BOOLEAN DEFAULT false,
p_coupon_code text DEFAULT NULL
) RETURNS jsonb AS $
DECLARE
v_checkout record;
v_stock_result jsonb;
v_total_amount numeric := 0;
BEGIN
-- Lock checkout record to prevent concurrent order creation
SELECT id, user_id, status, created_at
INTO v_checkout
FROM checkout
WHERE id = p_checkout_id AND user_id = p_user_id AND status = 'pending'
FOR UPDATE;
IF NOT FOUND THEN
RETURN jsonb_build_object('success', false, 'error', 'Checkout not found');
END IF;
-- Expiry check
IF v_checkout.created_at < NOW() - INTERVAL '30 minutes' THEN
RETURN jsonb_build_object('success', false, 'error', 'Checkout expired');
END IF;
-- Aggregate totals from checkout items
SELECT SUM(ci.product_price * ci.quantity) INTO v_total_amount
FROM checkout_items ci
WHERE ci.checkout_id = p_checkout_id;
-- Reserve stock atomically
v_stock_result := reserve_stock_rpc(
p_checkout_session_id := p_checkout_id,
p_items := (SELECT jsonb_agg(...) FROM checkout_items WHERE checkout_id = p_checkout_id),
p_reservation_minutes := 30
);
IF NOT (v_stock_result->>'success')::boolean THEN
RETURN jsonb_build_object('success', false, 'error', v_stock_result->>'error');
END IF;
-- Create order and items...
INSERT INTO orders (...) VALUES (...);
INSERT INTO order_items (...) SELECT ...;
-- Mark checkout as processing
UPDATE checkout SET status = 'processing' WHERE id = p_checkout_id;
RETURN jsonb_build_object('success', true, 'order_id', v_order_id);
EXCEPTION WHEN OTHERS THEN
-- Compensating action: release reserved stock on any failure
PERFORM release_stock_rpc(p_checkout_session_id := p_checkout_id);
RETURN jsonb_build_object('success', false, 'error', SQLERRM);
END;
$ LANGUAGE plpgsql SECURITY DEFINER;This exception handler implements the compensating transaction pattern: if anything fails after stock reservation, the reserved stock is automatically released. This prevents zombie inventory holds without requiring a separate orchestration service.
Phase 3: Idempotent Webhook Processing with State Machine
The payment webhook handler implements a rank-based state machine to gracefully handle duplicates, late arrivals, and out-of-order events:
CREATE OR REPLACE FUNCTION complete_order_from_webhook_rpc(
p_order_id TEXT,
p_status TEXT,
p_webhook JSONB DEFAULT NULL
) RETURNS jsonb AS $
DECLARE
v_payment record;
v_current_rank INT;
v_incoming_rank INT;
v_effective_status TEXT;
BEGIN
-- Lock payment record
SELECT p.id, p.order_id, p.status INTO v_payment
FROM payments p
JOIN orders o ON o.id = p.order_id
WHERE p.cf_order_id = p_order_id
FOR UPDATE;
-- Normalize gateway status
v_effective_status := CASE upper(p_status)
WHEN 'SUCCESS' THEN 'paid'
WHEN 'FAILED' THEN 'failed'
WHEN 'USER_DROPPED' THEN 'dropped'
ELSE 'unpaid'
END;
-- Rank-based state machine (prevents regression)
v_current_rank := CASE v_payment.status
WHEN 'unpaid' THEN 1
WHEN 'dropped' THEN 2
WHEN 'failed' THEN 3
WHEN 'paid' THEN 4
ELSE 1
END;
v_incoming_rank := CASE v_effective_status
WHEN 'unpaid' THEN 1
WHEN 'dropped' THEN 2
WHEN 'failed' THEN 3
WHEN 'paid' THEN 4
ELSE 1
END;
-- Only advance state, never regress
IF v_incoming_rank < v_current_rank THEN
v_effective_status := v_payment.status;
END IF;
UPDATE payments SET status = v_effective_status WHERE id = v_payment.id;
-- If paid, consume stock
IF v_effective_status = 'paid' THEN
PERFORM complete_order_rpc(...);
END IF;
RETURN jsonb_build_object('success', true, 'status', v_effective_status);
END;
$ LANGUAGE plpgsql SECURITY DEFINER;States progress only forward: unpaid → dropped → failed → paid. Stale events are safely ignored, eliminating duplicate processing risks from payment gateway retries.
Phase 4: Final Inventory Consumption (complete_order_rpc)
Stock is deducted only after confirmed payment, with strict guards against negative inventory:
CREATE OR REPLACE FUNCTION complete_order_rpc(
p_user_id UUID,
p_order_id UUID,
p_checkout_id UUID,
p_payment_id UUID
) RETURNS JSONB AS $
DECLARE
v_items_count INTEGER;
v_updated_count INTEGER;
BEGIN
-- Validate and lock order, checkout, and payment
PERFORM 1 FROM orders WHERE id = p_order_id FOR UPDATE;
PERFORM 1 FROM checkout WHERE id = p_checkout_id FOR UPDATE;
PERFORM 1 FROM payments WHERE id = p_payment_id FOR UPDATE;
-- Count items for atomicity check
SELECT COUNT(*) INTO v_items_count FROM checkout_items WHERE checkout_id = p_checkout_id;
-- Decrement stock only if reservations exist
WITH items AS (
SELECT product_id, color_id, size_id, quantity
FROM checkout_items WHERE checkout_id = p_checkout_id
), updated AS (
UPDATE product_color_sizes pcs
SET stock_quantity = pcs.stock_quantity - i.quantity,
updated_at = NOW()
FROM items i
WHERE pcs.product_id = i.product_id
AND pcs.color_id = i.color_id
AND pcs.size_id = i.size_id
AND pcs.stock_quantity >= i.quantity -- Guard: prevent negatives
RETURNING 1
)
SELECT COUNT(*) INTO v_updated_count FROM updated;
-- Atomicity check: all items must update or fail
IF v_updated_count <> v_items_count THEN
RAISE EXCEPTION 'Stock deduction failed for some items';
END IF;
-- Mark reservations as consumed
UPDATE reserved_stock
SET status = 'consumed', updated_at = NOW()
WHERE checkout_session_id = p_checkout_id AND status = 'active';
-- Finalize order and payment
UPDATE orders SET status = 'pending', payment_status = 'paid' WHERE id = p_order_id;
UPDATE payments SET status = 'paid' WHERE id = p_payment_id;
UPDATE checkout SET status = 'paid' WHERE id = p_checkout_id;
RETURN jsonb_build_object('success', true);
END;
$ LANGUAGE plpgsql SECURITY DEFINER;The pcs.stock_quantity >= i.quantity guard ensures that even if stock was manually adjusted between reservation and payment, the transaction fails cleanly rather than creating negative inventory.
The Result: Engineering Excellence in Production
Observed Improvements
Metrics | Before | After |
|---|---|---|
Checkout processing time | Several hundred ms | Dramatically faster |
Stock-related race conditions | Multiple per week | None observed |
Payment webhook handling | Manual reconciliation | Fully automated |
Flash sale inventory accuracy | Inconsistent | No overselling observed since deployment |
Key Wins:
Zero overselling during high-concurrency events
Automatic dead-cart recovery via expired reservations
Resilient webhook processing that survives network flakes and duplicates
Full audit trail through the
reserved_stocktableSafe administrative operations stock adjustments don't conflict with active checkouts
Architectural Trade-offs
Gained:
Absolute inventory accuracy at Lamiya's scale (~500 concurrent checkouts)
Stateless horizontal scaling of the application layer
Resilience to payment gateway failures without external orchestration
Costs and Limitations:
Row-level lock contention: The
FOR UPDATElocks onproduct_color_sizescreate a serial bottleneck at the inventory row. At Lamiya's observed peak (500 concurrent users on hot flash-sale items), lock wait times remained under 50ms, acceptable for their use case. At significantly higher scales (10,000+ concurrent checkouts on single SKUs), this pattern would require:Connection pool tuning (increase max connections)
Inventory sharding by product category
Or migration to an external reservation service (Redis, etc.)
Storage overhead: The
reserved_stocktable grows with checkout volume. A background cleanup job purges consumed/released reservations older than 30 days.Complexity: ~400 lines of PL/pgSQL replace simple
UPDATEstatements. Team expertise in PostgreSQL transactions and exception handling is required for maintenance.
Key Takeaways
Treat inventory as a distributed systems problem, not simple CRUD. The reservation pattern separates "intent to purchase" from physical stock movement.
Pessimistic locking + compensating transactions provide strong consistency guarantees within a single database. This is simpler and faster than true distributed Sagas for bounded contexts that fit within one PostgreSQL instance.
Database-native state machines (rank-based progression) eliminate webhook idempotency headaches without external infrastructure.
Compensation at the database level (
EXCEPTION WHEN OTHERS THEN...) ensures cleanup happens even if the application server crashes mid-transaction.
Conclusion
Lamiya Wedding Center's inventory system demonstrates that PostgreSQL is far more than a data store, it can serve as a robust platform for enforcing critical business invariants directly at the database level.
By building a multi-phase reservation protocol with database-native compensating transactions inside PostgreSQL RPC functions, the team achieved serializable isolation, automatic rollback on failure, and idempotent webhook processing, all without a single external coordinator or infrastructure dependency beyond PostgreSQL itself.
This pattern has proven effective in production at Lamiya's scale and is portable to any high-concurrency e-commerce system built on PostgreSQL. It eliminates an entire class of inventory bugs that commonly plague traditional application-layer approaches, while remaining honest about its scaling boundaries and operational requirements.
Interested in similar results?
Let's talk about your project