Optimistic lock: two editors, one offer, one version

optimistic-lock
Mechanism lab

Optimistic lock: two editors, one offer, one version

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.

Understand it step by step (한국어로 이해 → 영어로 말하기)

두 사람이 같은 화면을 동시에 열어 고친다. 둘 다 저장하면 먼저 저장한 사람의 수정이 조용히 사라진다. 이걸 막는다.
  1. 1

    두 명(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…

  2. 2

    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…

  3. 3

    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…

  4. 4

    그럼 줄을 세워서 한 명씩만 고치게 막으면(잠금) 어떨까? 평소엔 충돌이 거의 안 나는데, 안 나는 경우까지 다 기다리게 만든다 → 느리고 낭비.

    ⚖️ 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…

  5. 5

    해결: 읽을 때 본 버전을 같이 들고 가서, 저장할 때 "내가 본 버전이 아직 그대로면 저장, 아니면 거절"로 만든다(버전 비교 후 저장).

    🔧 도구: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…

  6. 6

    그래서 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…

  7. 7

    거절당한 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…

  8. 8

    정리: 락을 잡고 있지 않으니 충돌이 드물 땐 싸다. 비싼 건 드문 충돌 때 한 번 다시 하는 것뿐.

    🗣 영어로 말해

    No lock is held, so it's cheap when conflicts are rare — you only pay on the rare clash.

    checking microphone…

8단계 영어를 다 말하면 → 이 메커니즘 전체를 영어로 설명할 수 있게 된다.

How it evolves — toy → production

The problem

Two transactions read the same row, both edit, both write — the second silently overwrites the first (lost update).

  1. 1No guard (last-write-wins)toy — 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.

  2. 2Optimistic (version column)production default
    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.

  3. 3Pessimistic (FOR UPDATE)frequent conflicts
    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.

  4. 4App-level merge / CRDTcan't lock (offline / distributed)
    // 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.

Explain it in English

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…