문제
게시물이나 댓글을 생성하는 버튼을 화면 전환 전에 여러 번 클릭하면 동일한 요청이 중복으로 발생하는 문제가 나타났다.
이로 인해 데이터베이스에는 의도와 상관없이 중복된 값이 저장되었고, 서버로 필요 없는 API 요청이 여러 번 전달되었다.
특히 결제 시스템처럼 중요한 트랜잭션이 처리되는 경우, 동일한 결제가 중복으로 발생할 가능성이 있다. 이는 금전적 손실과 사용자 불만으로 이어질 수 있으며, 심각한 신뢰도 문제 생길 수 있다.
단순히 프런트엔드에서만 처리할 경우, 개발자 도구를 이용한 요청 조작이나 네트워크 지연 등으로 인해 중복 요청이 발생할 수 있다. 이를 방지하기 위해 서버에서도 확실한 검증과 처리가 필요하다. 프런트엔드와 백엔드 모두에서 중복 요청 방지를 구현함으로써 시스템 안정성을 높였다."
해결방법
Frontend
클릭 시 disabled 속성을 추가하여 버튼을 비활성화하고, 요청 완료 후 다시 활성화하도록 구현했다.
이렇게 하면 사용자가 요청 처리 중 버튼을 반복 클릭할 수 없게 된다.
<button className={style.button} onClick={postCreate} disabled={isSubmitting}>
등록
</button>
Backend
- 요청 데이터(본문) 해시 생성
요청 데이터를 해시 처리해 고유한 키를 생성. 동일한 데이터가 들어올 경우 동일한 해시 값이 생성되므로 이를 통해 중복 여부를 판단. - Redis를 활용한 중복 요청 방지
캐시에서 요청 해시를 검색해 중복 여부 확인.- 캐시에 이미 해시가 존재하면 오류 응답 반환.
- 해시가 없으면 캐시에 저장하고 요청 처리.
...
def post(self, request):
data = request.data
# 요청의 본문을 해시하여 고유한 키 생성
request_hash = hashlib.md5(str(data).encode('utf-8')).hexdigest()
cache_key = f'post_request_{request_hash}'
# 캐시에서 해시를 검색
if cache.get(cache_key):
return Response({"error": "Duplicate request"},
status=status.HTTP_400_BAD_REQUEST)
#캐시에 해시 저장 (예: 10초 동안)
cache.set(cache_key, True, timeout=10)
...
결과
중복으로 발생하던 요청이 이제 한 번만 처리되도록 개선했다.
데이터베이스의 무결성이 보장되고, 서버 낭비가 줄어들어 서비스의 안정성이 향상됐다.
Redis를 활용한 중복 요청 방지 외에도 자주 조회되는 데이터를 캐싱해 서버 성능을 최적화할 수 있을거같다.