Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Two guilds can try to spend the same gold at once. One bidder may place a bid while another purchase is being processed; an auction retry may try to settle the same funds again. In a small Go marketplace challenge, Ehsan Mahmoudi treated those races—not CRUD endpoints—as the hard part. His solution made PostgreSQL the coordination point: money-changing operations run in transactions, wallet totals are derived from ledger entries, and row locks serialize conflicting actions.

Why a marketplace can double-spend without coordination

The challenge was a game-currency marketplace where guilds could buy and auction items using in-game gold. The API operations sound familiar—create an auction, bid, purchase—but requests can overlap. If two bids read the same previous high bid and available funds before either commits, both may appear valid. A purchase can also race with a bid that is trying to reserve the same gold, and retries can repeat settlement work.

Mahmoudi’s design puts coordination beside the durable records it protects. He considered process-local mutexes, an in-memory queue for each auction, and a distributed Redis lock, but chose PostgreSQL transactions and row locks for this project. His reasoning was that local coordination does not cover another service instance, and separate lock infrastructure can get out of sync with a database transaction around crashes or rollback. That is a project-specific trade-off, not proof that those other approaches are always unsuitable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why derive the wallet balance from ledger entries?

Instead of updating one mutable wallet balance, the implementation records coin movements as ledger rows and derives the wallet figures from them. The article defines the accounting this way:

  • Total: grants and credits minus debits.
  • Reserved: reserves minus releases.
  • Available: total minus reserved.

This avoids the particular lost-update pattern the author was addressing: two operations independently read a balance, then overwrite one another’s result. Each movement is also represented as a row-level fact rather than disappearing into a changed balance. The ledger does not, by itself, enforce every business rule; application logic, schema constraints, transaction boundaries, and correct rollback and retry handling still matter.

How row locks protect bids and purchases

Placing a bid

For a new bid, the application locks the auction row before checking the current high bid or the bidder’s funds. Competing transactions that need that same auction row wait for the lock, so the second bid checks the state after the first transaction commits or rolls back. The challenge’s implementation also requires a new bid to be at least 5% above the current highest bid. That is the author’s example rule for this implementation in 2026, not an industry standard.

  1. Begin a database transaction.
  2. Lock the relevant auction row with SELECT ... FOR UPDATE.
  3. Read the current highest bid and check the bidder’s available funds.
  4. Apply the bid rules and write the corresponding changes.
  5. Commit the transaction; if it cannot complete, roll back rather than leave partial changes.

Buying an item

Purchase logic locks the relevant guild row before checking the available amount and daily purchase cap. That makes concurrent purchase checks for that guild take turns against the locked row instead of proceeding from the same stale decision. The lock’s scope matters: operations contending for the same protected row serialize, while unrelated rows do not need to share that particular lock.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The trade-off: same-auction bids serialize

Row locks narrow contention to the records being protected, but a popular auction becomes a hot row: bids that need its lock wait on one another. Separate auction rows can proceed independently. Mahmoudi accepted that throughput trade-off for the challenge rather than adding a separate coordination system. The article reports no benchmark, scale test, or measured throughput result, so it supports the design rationale—not a performance comparison.

A rejected bid is not an infrastructure failure

The author’s tests exposed a circuit-breaker mistake: ordinary business rejections, such as an invalid bid, were being counted as breaker failures. After enough such rejections, a subsequent legitimate request received HTTP 500. The fix was to count transaction-begin and transaction-commit infrastructure faults instead, separating expected business outcomes from failures that indicate trouble communicating with or committing through the database.

What PostgreSQL’s isolation rules add to the picture

Row locking and transactions are not a substitute for understanding the transaction isolation level. PostgreSQL 18 documents Read Committed as the default isolation level. In that mode, each command sees a snapshot from the start of that command, so successive commands in one transaction can see commits made between them. PostgreSQL says Read Committed works for many uses, but complex search-and-update logic may need stronger guarantees.

PostgreSQL’s explicit-locking documentation describes the row-locking behavior used to make conflicting operations wait. Repeatable Read and Serializable provide stronger isolation options for cases where the application needs them. Serializable transactions can fail with serialization errors; an application must be prepared to retry those transactions. The guarantee is that successfully committed concurrent transactions have an effect equivalent to some serial order—not that every attempt succeeds automatically.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

What this case study does—and does not—establish

This is a useful pattern for a small marketplace challenge: keep the ledger and coordination in the same database, lock the row whose state determines the decision, and distinguish a business rejection from an infrastructure fault. The author links code, migrations, and end-to-end tests, but the case study does not provide independent production validation, a measured scale result, or a compliance assessment.

Nor does “Postgres holds the money” mean the wallet is a single stored balance or that the database alone guarantees financial safety. A real deployment would also need to address authorization, schema and business invariants, transaction and retry design, backups and recovery, and any applicable security, operational, audit, or regulatory requirements. Those controls are outside what this game-currency project documents.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.