27. 낙관적 버전 충돌 재시도 시 재조회(refresh) 누락으로 갱신 유실 지속 (C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 낙관적 잠금(version 컬럼) 기반 잔액 갱신 (C#)
// - player 테이블: (id, balance, version). 동시 갱신 충돌은 낙관적 버전으로 감지한다.
// - UPDATE ... SET balance=@new, version=version+1 WHERE id=@id AND version=@ver
// 가 0행이면 그 사이 누가 커밋한 것이므로 재시도한다.
//
// 요구사항: 동시 충전/차감이 겹쳐도 최종 잔액은 모든 변경을 반영해야 한다
// (갱신 유실 없음).
//
// 과제: 부하 중 두 요청이 겹치면 한쪽 변경이 사라진다(잔액이 덜 반영됨).
// 충돌 재시도가 도는데도 유실이 지속된다. (A),(B) 를 근거로 원인·수정안을 설명하라.
using System.Data.Common;
public sealed class WalletRepo
{
private readonly DbConnection _db;
public WalletRepo(DbConnection db) { _db = db; }
public void ApplyDelta(int playerId, long delta)
{
// 최초 로드
(long balance, long version) = Load(playerId);
long newBalance = balance + delta;
while (true)
{
int affected = Exec(
"UPDATE player SET balance=@b, version=version+1 WHERE id=@id AND version=@v",
newBalance, playerId, version);
if (affected == 1)
return;
// 충돌: 누군가 먼저 커밋함 → 재시도
version = LoadVersion(playerId); // (A) 버전만 새로 읽음
// newBalance 는 그대로 재사용 // (B)
}
}
// --- 아래는 시나리오용 스텁 ---
private (long, long) Load(int id) => (0, 0);
private long LoadVersion(int id) => 0;
private int Exec(string sql, long b, int id, long v) => 1;
} 결함 코드 · C++
// 시나리오: 낙관적 잠금(version 컬럼) 기반 잔액 갱신 (C++)
// - player 테이블: (id, balance, version). 낙관적 버전으로 충돌 감지.
// - UPDATE ... SET balance=?, version=version+1 WHERE id=? AND version=?
// 가 0행이면 재시도한다.
//
// 요구사항: 동시 충전/차감이 겹쳐도 모든 변경이 최종 잔액에 반영(갱신 유실 없음).
//
// 과제: 부하 중 한쪽 변경이 사라지고, 재시도가 도는데도 유실이 지속된다.
// (A),(B) 를 근거로 원인·수정안을 설명하라. (컴파일 가능)
#include <cstdint>
#include <cstdio>
// 시나리오용 DB 인터페이스 스텁
struct Db {
int64_t balance = 0;
int64_t version = 0;
int64_t Load(int, int64_t& ver) { ver = version; return balance; }
int64_t LoadVersion(int) { return version; }
// 실제로는 원자적 조건부 UPDATE; 여기선 시나리오 설명용
int Update(int, int64_t new_balance, int64_t expect_ver) {
if (version != expect_ver) return 0;
balance = new_balance; ++version; return 1;
}
};
class WalletRepo {
Db& db_;
public:
explicit WalletRepo(Db& db) : db_(db) {}
void ApplyDelta(int player_id, int64_t delta) {
int64_t version = 0;
int64_t balance = db_.Load(player_id, version);
int64_t new_balance = balance + delta;
for (;;) {
int affected = db_.Update(player_id, new_balance, version);
if (affected == 1) return;
version = db_.LoadVersion(player_id); // (A) 버전만 갱신
// new_balance 는 그대로 재사용 // (B)
}
}
};
int main() {
Db db;
WalletRepo r(db);
r.ApplyDelta(1, 100);
std::printf("balance=%lld\n", (long long)db.balance);
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.