Urgent.News

What's breaking now, across thousands of outlets.

Finance & Markets

100 people bought a mug. The stock went from 100 to 99.

I wrote the most normal checkout code there is. Read the stock, check it isn't zero, write stock minus one. Then I sent 100 buy requests at it at the same time, against a real Postgres database. All 100 went through. The stock went from 100 to 99. async function buy ( id ) { const { rows } = await db . query ( " SELECT stock FROM products WHERE id = $1 " , [ id ]); const stock = rows [ 0 ]. stock…

The story begins with a seemingly simple piece of code designed to decrement a product's stock count by one when a purchase is made. However, the code contains a critical flaw that leads to a race condition, resulting in inaccurate stock counts and lost sales. To understand the issue, let's examine the code step by step.

The code starts by querying the database to retrieve the current stock level for the requested product. If the stock count is greater than one, it proceeds to execute an UPDATE query to decrement the stock count by one. The updated stock count is then returned.

At first glance, the code seems to function correctly. However, when multiple requests are made concurrently, problems arise. Imagine two buyers, Alice and Bob, both attempting to purchase the same product. Alice reads the stock count as 5, while Bob also reads the same stock count of 5. In the meantime, both of their queries are sent to the database.

Next, Node.js handles the requests concurrently. Bob's request is processed first, reading the stock count as 5. After that, Alice's request is executed, decrementing the stock count from 5 to 4. Bob's request continues executing, trying to decrement the stock count again from 5, resulting in an incorrect count of 4.

This scenario demonstrates a lost update, where the database operation intended to decrement the stock count fails to update the actual value due to simultaneous requests. In this case, both Alice and Bob's attempts to decrement the stock count were overwritten, leading to a final stock count of 4 instead of the expected 4.

The article then explores several solutions to address this race condition issue. The first solution is to let the database handle the decrement operation directly, eliminating the need for JavaScript to perform the calculation. By updating the stock count within the database using an UPDATE query with the condition `stock = 1`, the decrement operation becomes atomic, and lost updates are avoided.

The second solution involves locking the row when reading the stock count. By adding `FOR UPDATE` to the SELECT query, a lock is placed on the row, preventing other transactions from modifying it until the current transaction is committed. This ensures that the stock count is read consistently and updated atomically.

The third solution introduces a version or timestamp column to the product table. When reading the stock count, the code retrieves the current version or timestamp along with the count. When updating the stock count, the code checks if the version or timestamp has changed since the read. If the version or timestamp matches, the update proceeds; otherwise, the code retries the update operation. This approach prevents lost updates without requiring exclusive locks on the row.

The article concludes by emphasizing the importance of identifying and addressing race conditions in code that involves concurrent reads and writes. The key takeaway is that even single-threaded languages like JavaScript can encounter race conditions when dealing with database operations. By understanding the underlying principles and applying appropriate solutions, developers can ensure data integrity and prevent lost updates in their applications.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Finance & Markets

Is Appian a Buy After Its Latest Earnings Report?

Appian's AI momentum accelerated in the third quarter.

  • Appian's Q3 earnings grew 25% to $234.6 million, exceeding expectations.
  • AI adoption surged 50% sequentially, boosting customer usage.
  • On-premises revenue increased 64%, while cloud revenue grew 20% to $136.4 million.

More from Friday 6 November →