惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog
A
About on SuperTechFans
Microsoft Security Blog
Microsoft Security Blog
Y
Y Combinator Blog
罗磊的独立博客
J
Java Code Geeks
人人都是产品经理
人人都是产品经理
MongoDB | Blog
MongoDB | Blog
The GitHub Blog
The GitHub Blog
G
Google Developers Blog
U
Unit 42
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - Franky
Jina AI
Jina AI
F
Fortinet All Blogs
H
Help Net Security
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Last Week in AI
Last Week in AI
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
C
Check Point Blog
GbyAI
GbyAI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
We Replaced Redis with MySQL SKIP LOCKED for Inventory Re...
kirandeepjassal-crypto · 2026-06-07 · via DEV Community

kirandeepjassal-crypto

For two years, our Sponsored Placements service booked limited ad inventory through Redis: a counter in Redis, a Redlock around the decrement, and a TTL key per hold.

It oversold. Not catastrophically — consistently. 40–60 double-booked placements a month, each one a manual refund and an apology email to an advertiser.

The root cause was never one bug. It was the architecture: two sources of truth that could not be made atomic with each other. The count lived in Redis; the ownership lived in SQL. No transaction spans both. The Redlock only ever protected the Redis half.

The one mental shift

SKIP LOCKED turns a contended table into a concurrent work queue. Instead of every request fighting over one counter, each request grabs different rows and ignores the ones someone else is holding.

FOR UPDATE alone serializes — that's the experience that scares people off SQL locking. FOR UPDATE SKIP LOCKED is the opposite: a transaction that would have blocked instead skips the locked row and takes the next free one.

One row per reservable unit, then:

START TRANSACTION;

SELECT id
FROM inventory_unit
WHERE placement_id = 42
  AND (status = 'available'
       OR (status = 'held' AND hold_expires_at < NOW(3)))  -- self-healing expiry
ORDER BY id
LIMIT 2
FOR UPDATE SKIP LOCKED;   -- the whole trick

UPDATE inventory_unit
SET status = 'held', reservation_id = 'uuid', hold_expires_at = NOW(3) + INTERVAL 10 MINUTE
WHERE id IN (1107, 1108);

INSERT INTO reservation (...) VALUES (...);

COMMIT;

Two concurrent requests for the same pool lock different rows. Neither waits. The claim, the hold, and the reservation are one transaction — there is nothing to reconcile because there is nothing else.

The numbers (8 weeks before vs 8 weeks after)

Metric Redis + Redlock MySQL SKIP LOCKED
Oversells / month 40–60 0
Reservation p95 210 ms 34 ms
Reservation p99 540 ms 61 ms
Throughput / instance ~600 RPS 1,400 RPS
Lock-wait timeouts / day ~900 <5
Nightly reconciliation 9–14 min deleted
Redis cluster 3 nodes decommissioned

What made it work (the short version)

  • One row per unit, not a counter. A single counter row + FOR UPDATE is correct but serial — we measured the cliff at ~600 RPS.
  • Self-healing expiry. The claim query also picks up held rows past hold_expires_at, so correctness never depends on a sweeper running on time. (Redis TTL loses holds on failover — async replication.)
  • ORDER BY id + retry on 1213/1205. Deterministic lock order nearly closes the deadlock window; a 3-attempt retry handles the rest. <2 deadlocks/day, all invisible to users.
  • READ COMMITTED, not REPEATABLE READ. Gap locks under the MySQL default widened contention — switching cut deadlocks ~70% on its own.
  • A unique index as the backstop. Even if app logic is wrong, the database refuses to record the same unit sold twice.

When NOT to do this

Row-per-unit explodes for fungible, high-cardinality stock (5M identical SKUs → use a guarded UPDATE ... WHERE available >= qty instead). Flash-sale "1 item, 100k people" still wants a queue in front. And we kept Redis — for caching browse-page counts, where it belongs. Redis for speed, MySQL for truth, never the two confused.


The full write-up has the complete before/after C# handlers, the failover timeline that used to oversell, the index/EXPLAIN work, pool sizing, and a pre-merge checklist:

👉 How We Replaced Redis with MySQL SKIP LOCKED for Inventory Reservation at Scale