아이템 대량 개봉 & 부하 분산
게임이나 이커머스 시스템에서 대량의 아이템을 처리할 때 발생하는 다양한 기술적 문제들을 다룹니다.
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 트랜잭션 에러
일반적인 트랜잭션 에러 원인
- WriteConflict: 동시에 같은 문서 수정 시도
- TransactionTooLarge: 16MB 제한 초과
- 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개를 동시에 여러 서버에서 개봉 요청
해결 방안:
- 분산 락: Redis SETNX를 이용한 사용자별 락
- 낙관적 락: 버전 필드를 통한 충돌 감지
- 큐 기반 직렬화: 사용자별 전용 큐로 요청 직렬화
부하 관점 문제 해결
상황 가정: 이벤트 시작 시 동시 접속자 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 = ?;데드락 예방
- 락 획득 순서 통일
- 락 타임아웃 설정
- 트랜잭션 범위 최소화