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#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.