Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 비즈니스적 관점에서 생각하는 개발자
- 개발자와 비즈니스 관계
- 슬랙봇
- public.pem
- 비즈니스
- 웹소켓 api
- MySQL
- slack bot
- django
- private.pem
- 개발자와 비즈니스
- 업비트 웹소켓
- 비즈니스적 관점에서 생각하는 개발자 #개발자 마인드
- 알고리즘
- 백엔드 개발
- ssl.key
- haystack
- add colume
- 숲을 바라보는 개발자
- 개발자의 마인드
- AWS Aurora
- django 슬랙봇
- django slack
- django slack bot
- 개발자에세이
- 서버 개발
- redis lock
- 정렬
- 개발회고
- django #django 5.0 #django 5.0 요약
Archives
- Today
- Total
Info-Tech
[실전 예제로 보는 Race Condition] 중복 보상 지급, 어떻게 막을까? 본문
Race Condition이란?
- Race Condition(경쟁 상태)은 둘 이상의 프로세스나 스레드가 동시에 공통 자원에 접근할 때, 실행 순서에 따라 결과가 달라지는 상황입니다.
- 쉽게 말해, 누가 먼저 처리하느냐의 '경쟁' 상태에서 발생하는 버그로, 특히 데이터 무결성이 중요한 서비스에서 치명적일 수 있습니다.
REST API에서 Race Condition?
- REST API에서도 동시 요청이 들어와 동일 리소스를 처리할 때, 순서가 꼬이면 의도치 않은 결과가 발생할 수 있습니다.
- 예) 중복 결제, 포인트 중복 지급, 중복 예약 등이 대표적인 사례입니다.
실전 예시 – 미션 완료 후 보상 지급
상황 설명
사용자가 미션 완료 후 보상 지급 요청을 보내는 API가 있습니다.
정상적으로는 1회만 지급돼야 하지만, 동시 요청이 발생하면 어떻게 될까요?
- API Endpoint (예시용):
POST /api/v1/missions/complete - Request Body:
{"type":"investment_complete"}
서버 처리 흐름 (예시 코드)
1. API View
@router.post('/missions/complete', auth=AuthBearer())
def complete_mission(request, request_data: MissionCompleteRequest):
user = user_repo.get_by_id(request.user_id)
mission_service.complete(user, request_data.type)
return {"result": "success"}
2. 비즈니스 로직
def complete(self, user: User, mission_type: str):
mission = self.get_mission_by_type(mission_type)
if not mission:
raise ValueError("미션 정보 없음")
if self.is_already_completed(user.id, mission_type):
raise ConflictError("이미 완료된 미션입니다.")
with transaction.atomic():
# 완료 기록 저장
self.log_repository.add_completion(user.id, mission.id)
# 보상 지급
if mission.reward_type == "point":
user.add_points(mission.reward_amount)
elif mission.reward_type == "credit":
self.credit_service.grant(user.id, mission.reward_amount)
else:
raise ValueError("알 수 없는 리워드 타입")
정상 요청 흐름
- 단일 요청 시 정상적으로 보상 지급 후 완료 처리됩니다.
[Client] → POST /missions/complete → [Server] 200 OK
- 같은 요청을 다시 보내면, 이미 완료된 상태이므로 오류 반환:
[Client] → POST /missions/complete → [Server] 409 Conflict ("이미 완료된 미션입니다.")
비정상 요청 흐름 (Race Condition 발생)
- 악의적 사용자 또는 실수로 동시에 여러 요청을 보내면?
시나리오
[Client] → 30개의 동시 요청 발생
↓
[Server] → 중복 요청 모두 처리됨
↓
보상 30회 지급 😱
테스트 스크립트 (예시)
간단한 Python 스크립트를 통해 동시 요청 테스트를 할 수 있습니다.
from concurrent.futures import ThreadPoolExecutor
import requests
def send_request():
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
payload = {"type": "investment_complete"}
response = requests.post("http://localhost:8000/api/v1/missions/complete", json=payload, headers=headers)
print(f"Response: {response.status_code}")
if __name__ == "__main__":
with ThreadPoolExecutor(max_workers=30) as executor:
for _ in range(30):
executor.submit(send_request)
결과
- 요청 30회 중 모두 200 OK 응답, 보상 30회 지급 → Race Condition 발생.
Race Condition 방지법
1. Redis Lock 사용
- 분산 락 메커니즘을 통해 하나의 요청만 처리하도록 제어합니다.
import redis
import redis_lock
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
def complete(self, user: User, mission_type: str):
lock = redis_lock.Lock(redis_client, f"lock:mission:{user.id}:{mission_type}")
with lock:
if self.is_already_completed(user.id, mission_type):
raise ConflictError("이미 완료됨")
...
2. 데이터베이스 락 (select_for_update)
- DB 수준에서 레코드 락을 걸어 동시 처리 방지.
from django.db import transaction
def complete(self, user: User, mission_type: str):
with transaction.atomic():
user = User.objects.select_for_update().get(id=user.id)
if self.is_already_completed(user.id, mission_type):
raise ConflictError("이미 완료됨")
...
3. 단순 트랜잭션 (불충분할 수 있음)
- 트랜잭션만 사용할 경우, 완전한 Race Condition 방지 어렵습니다.
from django.db import transaction
def complete(self, user: User, mission_type: str):
with transaction.atomic():
if self.is_already_completed(user.id, mission_type):
raise ConflictError("이미 완료됨")
...
마무리
- 실제 서비스에서는 Race Condition으로 인해 데이터 오류, 과금 문제 등이 발생할 수 있습니다.
- 특히 포인트/현금과 관련된 로직에서는 반드시 동시성 처리를 고려해야 합니다.
- Redis 락, DB 락 등 상황에 맞는 방식을 선택해 안정적인 서비스 운영을 해봅시다!
💡 참고
- 테스트 도구로는 Locust, JMeter, 간단한 Python 멀티스레드 활용 가능.
- Redis 락 구현 시, 락 타임아웃 설정도 함께 고려하면 더 안전합니다.
🎯 정리 포인트
- Race Condition은 실제 서비스에서 빈번히 발생
- 특히 보상, 결제, 예약 등 한정 리소스 처리에서 중요
- 상황에 맞는 락 메커니즘으로 미리 방지 → 나중에 고통 줄이기!
'프로그래밍 > 파이썬 & 장고' 카테고리의 다른 글
upbit websocket api사용방법 (0) | 2019.02.16 |
---|---|
Cron작업을 통해 특정 거래소의 코인 정보들을 실시간으로 확인해보기 (1) | 2018.10.26 |
Coingecko [코인 거래 사이트]의 정보를 간단한 크롤링 해보기 (1) | 2018.10.25 |
“SSL: CERTIFICATE_VERIFY_FAILED” 에러 (0) | 2018.10.24 |
Comments