16. 채팅 스팸/도배 레이트리밋과 정상 메시지의 경합
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 채팅 스팸/도배 레이트리밋과 정상 메시지의 경합
// ----------------------------------------------------------------------------
// 시나리오:
// - 채팅 도배를 막으려고 세션(플레이어)마다 "1초당 최대 N개" 레이트리밋을 둔다.
// - 메시지가 들어오면 Allow(sessionId) 로 허용 여부를 판정하고, 허용이면 브로드캐스트,
// 아니면 드롭(또는 경고)한다.
// - 한 플레이어의 채팅 패킷이라도 여러 IO 스레드에서 거의 동시에 처리될 수 있다
// (매크로/도배 클라이언트는 짧은 시간에 패킷을 몰아 보낸다).
//
// 요구사항:
// - 1초 윈도 안에서 허용 개수는 절대 N 을 넘으면 안 된다(도배가 새어 나가면 안 됨).
// - 정상 속도의 메시지가 잘못 드롭되면 안 된다(과도 차단 금지).
// - 동시 처리에도 카운터/윈도 자료구조가 손상되거나 예외로 죽으면 안 된다.
// - 시각 판정은 NTP 보정/서머타임 점프에 흔들리면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 깨지는지(동시 인터리빙 포함)
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public class ChatRateLimiter
{
private const int LimitPerSec = 5;
private class Bucket
{
public int Count;
public long WindowStartMs;
}
// 세션 -> 버킷
private readonly Dictionary<long, Bucket> _buckets = new();
private Bucket GetOrCreate(long sessionId)
{
// (A)
if (!_buckets.TryGetValue(sessionId, out var b))
{
b = new Bucket { Count = 0, WindowStartMs = NowMs() };
_buckets[sessionId] = b;
}
return b;
}
public bool Allow(long sessionId)
{
var b = GetOrCreate(sessionId);
long now = NowMs();
// (B) 윈도 경과 시 리셋
if (now - b.WindowStartMs >= 1000)
{
b.WindowStartMs = now;
b.Count = 0;
}
// (C) 한도 검사 후 증가
if (b.Count >= LimitPerSec)
return false;
b.Count++;
return true;
}
private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 채팅 스팸/도배 레이트리밋과 정상 메시지의 경합
// ----------------------------------------------------------------------------
// 시나리오:
// - 채팅 도배를 막으려고 세션(플레이어)마다 "1초당 최대 N개" 레이트리밋을 둔다.
// - 메시지가 들어오면 Allow(sessionId) 로 허용 여부를 판정하고, 허용이면 브로드캐스트,
// 아니면 드롭(또는 경고)한다.
// - 한 플레이어의 채팅 패킷이라도 여러 IO 스레드에서 거의 동시에 처리될 수 있다
// (매크로/도배 클라이언트는 짧은 시간에 패킷을 몰아 보낸다).
//
// 요구사항:
// - 1초 윈도 안에서 허용 개수는 절대 N 을 넘으면 안 된다(도배가 새어 나가면 안 됨).
// - 정상 속도의 메시지가 잘못 드롭되면 안 된다(과도 차단 금지).
// - 동시 처리에도 카운터/윈도 자료구조가 손상되거나 정의되지 않은 동작이 나면 안 된다.
// - 시각 판정은 시스템 시계 점프(NTP 보정 등)에 흔들리면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 깨지는지(동시 인터리빙 포함)
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <unordered_map>
#include <chrono>
class ChatRateLimiter {
public:
static constexpr int kLimitPerSec = 5;
bool Allow(std::int64_t sessionId) {
Bucket& b = GetOrCreate(sessionId); // (A)
std::int64_t now = NowMs();
// (B) 윈도 경과 시 리셋
if (now - b.windowStartMs >= 1000) {
b.windowStartMs = now;
b.count = 0;
}
// (C) 한도 검사 후 증가
if (b.count >= kLimitPerSec)
return false;
b.count++;
return true;
}
private:
struct Bucket {
int count = 0;
std::int64_t windowStartMs = 0;
};
Bucket& GetOrCreate(std::int64_t sessionId) {
auto it = buckets_.find(sessionId);
if (it == buckets_.end()) {
it = buckets_.emplace(sessionId, Bucket{0, NowMs()}).first;
}
return it->second;
}
static std::int64_t NowMs() {
using namespace std::chrono;
return duration_cast<milliseconds>(
system_clock::now().time_since_epoch()).count();
}
std::unordered_map<std::int64_t, Bucket> buckets_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.