29. tombstone·anti-entropy 부재로 인한 삭제 데이터 부활 + LWW 역전 · C#
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 다중 리전 복제 저장소에서 삭제와 재동기화 (C#)
//
// 아이템/우편 데이터를 여러 리전이 각자 복제본으로 보유하고, 쓰기/삭제를
// 비동기로 서로 전파한다. 네트워크 분단이나 지연으로 일부 연산이 유실될 수 있어,
// 주기적으로 peer 의 스냅샷과 자기 데이터를 병합하는 anti-entropy(Reconcile)를 돈다.
//
// 각 값에는 버전(쓰기 시각 ms)이 달려 있고, 병합은 "더 최신 버전이 이긴다"(LWW)로 한다.
//
// 요구사항:
// - 삭제된 아이템은 재동기화 후에도 되살아나면 안 된다.
// - 오래된(늦게 도착한) 쓰기가 최신 상태를 덮어써서는 안 된다.
//
// 아래 저장소의 Delete 와 Reconcile 을 검토하라. 리전 R1 에서 아이템을 삭제했는데
// 그 삭제를 못 받은 리전 R2 와 R1 이 나중에 Reconcile 하는 상황을 생각하라.
//
// 과제:
// 1) (A)(B) 를 중심으로 삭제가 왜 되살아나는지 설명하라.
// 2) 늦게 도착한 오래된 쓰기가 삭제를 덮는 시나리오도 제시하라.
// 3) 수정안을 제시하라.
using System;
using System.Collections.Generic;
public struct VersionedValue
{
public string Value;
public long Version; // 쓰기 시각(ms)
}
public class ReplicaStore
{
private readonly Dictionary<string, VersionedValue> _data = new();
private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
public void Put(string key, string value)
{
long v = NowMs();
if (!_data.TryGetValue(key, out var cur) || v >= cur.Version)
_data[key] = new VersionedValue { Value = value, Version = v };
Replicate();
}
public void Delete(string key)
{
_data.Remove(key); // (A) 항목을 제거
Replicate();
}
// 주기적 anti-entropy: peer 전체 스냅샷을 받아 병합
public void Reconcile(IReadOnlyDictionary<string, VersionedValue> peer)
{
foreach (var kv in peer)
{
// (B) 내게 없거나 peer 가 더 최신이면 peer 값을 채택
if (!_data.TryGetValue(kv.Key, out var cur) || kv.Value.Version > cur.Version)
_data[kv.Key] = kv.Value;
}
}
public IReadOnlyDictionary<string, VersionedValue> Snapshot() => _data;
private void Replicate() { /* 비동기 전파(유실 가능) */ }
} 결함 코드 · C++
// 시나리오: 다중 리전 복제 저장소에서 삭제와 재동기화 (C++ 서버-서버)
//
// 여러 리전이 아이템 데이터를 복제본으로 보유하고 쓰기/삭제를 비동기로 전파한다.
// 유실 보정을 위해 주기적으로 peer 스냅샷과 병합하는 anti-entropy(reconcile)를 돈다.
// 각 값에는 버전(쓰기 시각 ms)이 있고 병합은 "더 최신이 이긴다"(LWW).
//
// 요구사항:
// - 삭제된 아이템은 재동기화 후에도 되살아나면 안 된다.
// - 늦게 도착한 오래된 쓰기가 최신 상태를 덮으면 안 된다.
//
// delete 를 못 받은 peer 와 나중에 reconcile 하는 상황을 검토하라.
//
// 과제:
// 1) (A)(B) 를 중심으로 삭제가 왜 되살아나는지 설명하라.
// 2) 늦게 도착한 오래된 쓰기가 삭제를 덮는 시나리오도 제시하라.
// 3) 수정안을 제시하라. g++ -std=c++17 로 컴파일된다고 가정한다.
#include <string>
#include <unordered_map>
#include <cstdint>
#include <chrono>
struct VersionedValue {
std::string value;
int64_t version = 0; // 쓰기 시각(ms)
};
static int64_t nowMs() {
using namespace std::chrono;
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}
class ReplicaStore {
public:
void put(const std::string& key, const std::string& value) {
int64_t v = nowMs();
auto it = data_.find(key);
if (it == data_.end() || v >= it->second.version)
data_[key] = VersionedValue{value, v};
replicate();
}
void del(const std::string& key) {
data_.erase(key); // (A) 항목을 제거
replicate();
}
// anti-entropy: peer 스냅샷과 병합
void reconcile(const std::unordered_map<std::string, VersionedValue>& peer) {
for (const auto& [key, pv] : peer) {
auto it = data_.find(key);
// (B) 내게 없거나 peer 가 더 최신이면 채택
if (it == data_.end() || pv.version > it->second.version)
data_[key] = pv;
}
}
const std::unordered_map<std::string, VersionedValue>& snapshot() const { return data_; }
private:
void replicate() { /* 비동기 전파(유실 가능) */ }
std::unordered_map<std::string, VersionedValue> data_;
};
int main() { return 0; } 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.