아이템 대량 개봉 & 부하 분산

게임이나 이커머스 시스템에서 대량의 아이템을 처리할 때 발생하는 다양한 기술적 문제들을 다룹니다.

RPC 이유와 기술적 특징

RPC를 선택한 이유

  • 동기적 호출 필요성: 아이템 개봉 결과를 즉시 클라이언트에 반환해야 하는 경우
  • 낮은 지연시간: HTTP REST 대비 바이너리 프로토콜로 더 빠른 응답
  • 타입 안정성: Protocol Buffers나 Thrift를 통한 스키마 정의

기술적 특징

service ItemService {
  rpc OpenItems(OpenItemsRequest) returns (OpenItemsResponse);
}
 
message OpenItemsRequest {
  string user_id = 1;
  repeated string item_ids = 2;
}

확률 검증 방법

Redis Set의 SRANDMEMBER 활용

# 확률 기반 아이템 선택
def get_random_item(redis_client, pool_key):
    return redis_client.srandmember(pool_key)

TreeMap 기반 확률 로직

public class ProbabilitySelector<T> {
    private final TreeMap<Double, T> probabilityMap = new TreeMap<>();
    private double totalWeight = 0;
 
    public void add(T item, double weight) {
        totalWeight += weight;
        probabilityMap.put(totalWeight, item);
    }
 
    public T select() {
        double random = Math.random() * totalWeight;
        return probabilityMap.higherEntry(random).getValue();
    }
}

확률 검증 방법

  • 대량 시뮬레이션: 100만 회 이상의 시뮬레이션으로 기대 확률 대비 실제 결과 비교
  • 카이제곱 검정: 통계적 유의성 검증
  • 실시간 모니터링: 실제 운영 환경에서의 확률 분포 추적

MQ 주시/풀링 모델

Push vs Pull 모델

특성Push (주시)Pull (풀링)
지연시간낮음상대적으로 높음
소비자 부하 제어어려움쉬움
구현 복잡도높음낮음

Prefetch Size 설정

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 250  # 한 번에 가져올 메시지 수
  • 너무 작으면: 네트워크 왕복이 잦아 처리량 저하
  • 너무 크면: 특정 컨슈머에 메시지 집중, 장애 시 재처리 부담

MongoDB 트랜잭션 에러

일반적인 트랜잭션 에러 원인

  1. WriteConflict: 동시에 같은 문서 수정 시도
  2. TransactionTooLarge: 16MB 제한 초과
  3. TransactionExceededLifetimeLimitSeconds: 60초 기본 제한 초과

해결 전략

async function executeWithRetry(operation, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const session = client.startSession();
    try {
      session.startTransaction();
      const result = await operation(session);
      await session.commitTransaction();
      return result;
    } catch (error) {
      await session.abortTransaction();
      if (error.hasErrorLabel('TransientTransactionError') && i < maxRetries - 1) {
        continue;  // 재시도
      }
      throw error;
    } finally {
      session.endSession();
    }
  }
}

분산 관련 문제 상황 가정

시나리오: 다중 서버에서 동시 아이템 개봉

문제: 사용자가 가진 아이템 100개를 동시에 여러 서버에서 개봉 요청

해결 방안:

  1. 분산 락: Redis SETNX를 이용한 사용자별 락
  2. 낙관적 락: 버전 필드를 통한 충돌 감지
  3. 큐 기반 직렬화: 사용자별 전용 큐로 요청 직렬화

부하 관점 문제 해결

상황 가정: 이벤트 시작 시 동시 접속자 10배 증가

즉시 대응:

  • Auto Scaling 트리거 조정
  • 캐시 TTL 증가
  • 비필수 기능 서킷브레이커 오픈

구조적 대응:

  • CQRS 패턴 도입으로 읽기/쓰기 분리
  • 이벤트 소싱으로 쓰기 부하 분산
  • 샤딩을 통한 수평 확장

DB 트랜잭션과 락

상황 가정: 재고 차감 시 동시성 문제

비관적 락 (Pessimistic Lock):

SELECT * FROM inventory WHERE item_id = ? FOR UPDATE;
UPDATE inventory SET quantity = quantity - 1 WHERE item_id = ?;

낙관적 락 (Optimistic Lock):

UPDATE inventory
SET quantity = quantity - 1, version = version + 1
WHERE item_id = ? AND version = ?;

데드락 예방

  • 락 획득 순서 통일
  • 락 타임아웃 설정
  • 트랜잭션 범위 최소화