When two clients edit the same row concurrently, last-writer-wins silently loses the earlier update. Optimistic locking attaches a version to each read; the write succeeds only if the version is still what you read (compare-and-set), and a stale write is rejected so the caller can refetch and retry. Cheap under low contention because nobody holds a lock — you only pay on the rare conflict.
résumé · Guarded concurrent edits to the same offer with a version column (compare-and-set) so a stale write is rejected instead of silently clobbering the winning update.
You'll predict the failure first, then write the fix and watch your own code drive the behavior.
두 명(T1, T2)이 같은 오퍼(offer)를 동시에 연다. 둘 다 값=100, 버전=1을 본다.
// T1 and T2 both read the same snapshot int v1 = s.read(); // 100 int ver1 = s.version(); // 1 int v2 = s.read(); // 100 int ver2 = s.version(); // 1
“Two editors open the same offer at once; both read value 100, version 1.”
checking microphone…
T1이 먼저 저장한다(값 80). 저장이 끝나면 버전은 2로 올라간다.
// T1 writes first against version 1; CAS succeeds and bumps the version boolean ok1 = s.write(ver1, v1 - 20); // 100 -> 80, version 1 -> 2 // ok1 == true, s.version() == 2 now
“T1 saves first and changes the value, so the row moves to version 2.”
checking microphone…
T2는 아직도 옛날 화면(값 100, 버전 1)을 들고 있다. 그대로 저장하면 T1이 한 일을 덮어쓴다 → 먼저 한 수정이 사라짐(lost update).
⚖️ Trade-off: 그냥 두면 마지막에 저장한 사람만 이기고, 먼저 저장한 사람 일은 말도 없이 없어진다.
// T2 still holds its old snapshot (value 100, version 1) // A naive write here would clobber T1 -> lost update. int staleValue = v2; // 100 int staleVersion = ver2; // 1, but the row is already at version 2
“T2 still holds the old screen, so its save overwrites T1's work and a change is lost.”
checking microphone…
그럼 줄을 세워서 한 명씩만 고치게 막으면(잠금) 어떨까? 평소엔 충돌이 거의 안 나는데, 안 나는 경우까지 다 기다리게 만든다 → 느리고 낭비.
⚖️ Trade-off: 미리 잠그면(비관적 락) 충돌이 없는 대부분의 경우에도 다른 사람을 계속 기다리게 한다.
✅ Fix: 충돌이 드물면 미리 잠그지 말고, 충돌이 진짜 났을 때만 비용을 내자(낙관적).
🔧 도구:SELECT FOR UPDATE (비관적 락 — 여기선 과함)
-- Pessimistic alternative (too heavy here: holds a lock for everyone): SELECT value, version FROM offer WHERE id = :id FOR UPDATE; -- conflicts are rare, so we avoid this and use optimistic CAS instead
“Locking everyone would be slow, since conflicts here are actually rare.”
checking microphone…
해결: 읽을 때 본 버전을 같이 들고 가서, 저장할 때 "내가 본 버전이 아직 그대로면 저장, 아니면 거절"로 만든다(버전 비교 후 저장).
🔧 도구:compare-and-setversion 컬럼UPDATE ... WHERE version = :expected@Version (JPA)
// compare-and-set: apply only if the version I read is still current
boolean write(int expectedVersion, int newValue) {
if (expectedVersion != version) return false; // reject stale write
value = newValue;
version = version + 1;
return true;
}“I attach the version I read, and the write only applies if that version is still current.”
checking microphone…
그래서 T2의 저장은 버전이 안 맞아 거절된다(stale). T1 값은 그대로 안전.
// T2's stale write: expectedVersion 1, but current version is 2 -> rejected boolean ok2 = s.write(ver2, v2 - 10); // ok2 == false; value stays 80, version stays 2 (T1 safe)
“T2's stale write is rejected because its version no longer matches, so T1 stays safe.”
checking microphone…
거절당한 T2는 어떻게? 최신 값을 다시 읽고(refetch) 그 위에서 다시 고쳐 저장한다(재시도).
⚖️ Trade-off: 충돌이 나면 그 사람은 다시 읽고 다시 저장해야 한다 → 한 번 더 일함.
✅ Fix: 대신 평소엔 아무도 안 막아서 빠르고, 이 추가 비용은 드물게 충돌 났을 때만 낸다.
🔧 도구:refetch + retry
// Rejected editor refetches the latest, then retries on top of it
while (true) {
int cur = s.read();
int curVer = s.version();
if (s.write(curVer, cur - 10)) break; // retry until CAS succeeds
}“The rejected editor refetches the latest value and retries on top of it.”
checking microphone…
정리: 락을 잡고 있지 않으니 충돌이 드물 땐 싸다. 비싼 건 드문 충돌 때 한 번 다시 하는 것뿐.
“No lock is held, so it's cheap when conflicts are rare — you only pay on the rare clash.”
checking microphone…
8단계 영어를 다 말하면 → 이 메커니즘 전체를 영어로 설명할 수 있게 된다.
Two transactions read the same row, both edit, both write — the second silently overwrites the first (lost update).
UPDATE account SET balance = :new WHERE id = :id; // whoever commits last wins; the other's change is silently lost
Use when: Never when two writers can touch the same row.
Cost / limit: Silent data loss — no one is told they overwrote a change.
UPDATE account SET balance = :new, version = version + 1 WHERE id = :id AND version = :readVersion; // 0 rows updated -> someone else won -> re-read and retry
Use when: Conflicts are RARE — most updates don't collide.
Cost / limit: Caller must retry on conflict; a hot row can retry-storm.
SELECT * FROM account WHERE id = :id FOR UPDATE; -- hold the row lock, update, commit — others wait
Use when: Conflicts are FREQUENT (hot row) — retrying would storm.
Cost / limit: Writers block; throughput is capped by the lock.
// merge concurrent edits by rule (max, set-union, last-writer-by-timestamp) // or a CRDT so concurrent updates converge without any lock
Use when: Offline-first or multi-region where a global lock isn't possible.
Cost / limit: You must define merge semantics; not all data merges cleanly.
Know the ladder — start at the simplest that meets the need, and say what you'd reach for next and why.
Say it out loud in your own words — hit these terms — then check against the gold answer.
Say it in your own words — but hit these: concurrency · optimistic lock · compare and set · lost update
checking microphone…