26. 기간제 아이템 TTL 만료 정리 잡과 연장의 경합 (C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# — 기간제 아이템 TTL 만료 정리 잡과 연장(renew)의 경합
// ----------------------------------------------------------------------------
// 시나리오:
// 기간제(임대) 아이템은 DB 테이블 rented_items(item_id PK, owner_id, expire_at)에
// 저장된다. 백그라운드 정리 잡(ExpireSweeper)이 주기적으로 만료된 행을 삭제한다.
// 한편 플레이어는 만료 전에 아이템을 "연장"할 수 있고, 이때 expire_at 이 미래로
// 갱신된다(UPDATE). 정리 잡과 연장 요청은 서로 다른 커넥션/트랜잭션에서 동시에 일어난다.
//
// 요구사항:
// - 만료된 아이템만 삭제한다. 연장으로 유효해진 아이템은 절대 삭제하면 안 된다.
//
// 과제: (A) (B) 지점의 동작을 설명하고 수정안을 제시하라.
// ============================================================================
using System;
using System.Collections.Generic;
public interface IDb
{
List<long> Query(string sql, object args); // id 목록 반환
int Execute(string sql, object args); // 영향받은 행 수
}
public sealed class ExpireSweeper
{
private readonly IDb _db;
public ExpireSweeper(IDb db) => _db = db;
public void SweepOnce()
{
var now = DateTime.UtcNow;
// 1) 만료 후보를 먼저 조회한다.
var expiredIds = _db.Query(
"SELECT item_id FROM rented_items WHERE expire_at < @now",
new { now }); // (A)
if (expiredIds.Count == 0) return;
// 2) 조회된 id 를 삭제한다.
_db.Execute(
"DELETE FROM rented_items WHERE item_id IN @ids",
new { ids = expiredIds }); // (B)
}
}
// 별도 경로: 플레이어의 연장 요청
public sealed class RentalService
{
private readonly IDb _db;
public RentalService(IDb db) => _db = db;
public void Renew(long itemId, TimeSpan extend)
{
var newExpire = DateTime.UtcNow + extend;
_db.Execute(
"UPDATE rented_items SET expire_at = @newExpire WHERE item_id = @itemId",
new { newExpire, itemId });
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ — 기간제 아이템 TTL 만료 정리 잡과 연장(renew)의 경합
// ----------------------------------------------------------------------------
// 시나리오:
// 기간제 아이템은 DB 테이블 rented_items(item_id PK, owner_id, expire_at)에 저장된다.
// 백그라운드 정리 잡이 만료 행을 삭제하고, 플레이어는 만료 전에 연장(expire_at 미래로
// UPDATE)할 수 있다. 정리 잡과 연장은 서로 다른 커넥션/트랜잭션에서 동시에 일어난다.
//
// 요구사항:
// - 만료된 아이템만 삭제한다. 연장으로 유효해진 아이템은 절대 삭제하지 않는다.
//
// 과제: (A) (B) 지점의 동작을 설명하고 수정안을 제시하라.
//
// 빌드: g++ -std=c++17 problem.cpp
// ============================================================================
#include <cstdint>
#include <string>
#include <vector>
// 얇은 DB 추상화(실 구현은 드라이버가 담당).
struct IDb {
virtual ~IDb() = default;
virtual std::vector<int64_t> query(const std::string& sql, int64_t nowUnix) = 0;
virtual int execute(const std::string& sql, const std::vector<int64_t>& ids) = 0;
// 조건부 실행(스칼라 파라미터 바인딩). 수정안에서 사용할 수 있다.
virtual int executeCond(const std::string& sql, int64_t param) = 0;
virtual int64_t nowUnix() = 0;
};
class ExpireSweeper {
public:
explicit ExpireSweeper(IDb* db) : db_(db) {}
void sweepOnce() {
int64_t now = db_->nowUnix();
// 1) 만료 후보를 먼저 조회.
std::vector<int64_t> expiredIds = db_->query(
"SELECT item_id FROM rented_items WHERE expire_at < ?", now); // (A)
if (expiredIds.empty()) return;
// 2) 조회된 id 를 삭제.
db_->execute(
"DELETE FROM rented_items WHERE item_id IN (?)", expiredIds); // (B)
}
private:
IDb* db_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.