跪拜 Guibai
← All articles
Backend

The Order Timeout Question That Fails Most Backend Interviews

By 神奇小汤圆 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

System design interviews increasingly filter on reliability thinking, not feature completion. A candidate who only describes the happy path signals they have never operated a service where tasks get lost, duplicated, or backlogged under load.

Summary

The naive answer—a cron job scanning the orders table every minute—collapses under millions of rows, introduces unacceptable timing jitter, and loses tasks when the scheduler crashes. The core shift is from polling the database to letting expired orders surface themselves through a delay queue. Redis ZSets store order IDs scored by expiration timestamp, shifting query pressure off MySQL and processing only the small slice of actually-expired records. The real differentiator is handling failure: atomically moving tasks into a processing queue via Lua scripts, deleting only after successful execution, and making every cancellation idempotent with conditional SQL updates. At tens of millions of orders, shard the ZSets by order ID hash; beyond that, evaluate MQ delayed messages or persistent queues with time wheels. A low-frequency fallback scan still runs to guarantee eventual consistency when everything else fails.

Takeaways
Scanning the orders table with a cron job creates unbounded database pressure as unpaid orders grow, and the polling interval directly determines cancellation timing error.
Redis expiration listeners are too unreliable for the main pipeline because expired-key events fire at-most-once and can be lost under memory pressure or during failover.
A Redis ZSet delay queue stores the cancellation timestamp as the score and the order ID as the member, letting consumers range-query only the records that have actually expired.
Atomic transfer of tasks from the delay queue to a processing queue, using a Lua script, prevents task loss when a consumer crashes mid-execution.
Idempotent cancellation is enforced with a conditional SQL UPDATE that checks status='WAIT_PAY', so repeated delivery never corrupts already-paid or already-canceled orders.
Sharding the delay queue by order ID hash prevents any single ZSet from becoming a hot big key and allows parallel consumption across threads and instances.
A low-frequency fallback scan of the database remains in place as a final safety net, ensuring eventual consistency when the delay-queue path misses tasks.
Conclusions

Most candidates fail this question not because they lack the technical knowledge, but because they answer the surface problem instead of the system-design problem the interviewer is actually probing.

The jump from 'it works' to 'it works when things break' is what separates a mid-level engineer from a senior one in backend interviews, and this question exposes that gap in minutes.

Redis ZSet is the answer that lets a candidate demonstrate breadth—data structure choice, atomicity, failure recovery, and sharding—all within a single coherent design.

The two-phase processing pattern with a separate processing queue is a general-purpose reliability technique that applies far beyond order cancellation, yet few candidates volunteer it unprompted.

Concepts & terms
Redis ZSet delay queue
A pattern where a sorted set stores tasks with their execution time as the score. Consumers poll with ZRANGEBYSCORE using the current timestamp to retrieve only tasks that are due, avoiding full-table scans.
Two-phase task processing
Instead of deleting a task immediately upon dequeue, it is first atomically moved to a processing queue. The task is only removed after successful execution, allowing a compensation job to recover tasks abandoned by a crash.
Idempotent cancellation
A cancellation operation that produces the same result no matter how many times it executes, typically enforced by a conditional SQL UPDATE that checks the current status before applying the change.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗