상황

300명 이상 수신자에게 예약/대량 메일 발송 시 두 번 이상 발송되는 문제

 

문제

첫 번째 요청이 끝나기도 전에 동일한 요청이 발생됨.

그 시간이 딱 1분.

WEB Server 로그 분석 (이중화 환경)

클라이언트 요청이 중복으로 들어온 것을 확인.

그 시간 간격이 딱 1분이었다.

어떤 상황에서는 1번기에 동일하게 중복 요청이 들어왔고 어떤 상황에서는 1번기, 2번기 각각 중복 요청이 들어왔다.

해결 과정

  • 원타임토큰 문제?

처음엔 메일 중복 발송을 막기 위한 OneTimeToken 의 문제로 파악했다.

사용자가 [보내기] 버튼 혹은 네트워크 장애로 인해 두 번 이상 호출되어 중복 메일 발송되는 문제를 예방하는 역할을 한다. 그러나 로그 심어 확인해 보니, 원타임토큰은 동일했다.

  • ajax 호출 응답 시간?

메일 발송은 ajax 이용하여 비동기 처리로 응답시간 최대 timeout 1분 설정을 해 놓은 상태였다.

대량 수신자 메일 발송 처리에 대한 시간이 지연되는 것으로 생각하여 이를 최대 3분으로 늘렸다.

결과는 동일했다.

  • 예약메일 로직 쓰레드 방식으로 개선

대량 수신자의 예약메일의 경우 예약메일을 관리하는 테이블에 데이터를 넣는데 그 시간이 오래 걸려 발생되는 문제로 파악했다. 이를 개선하고자 사용자한테는 예약메일 발송 시, 바로 응답을 해주고 백단에서 멀티스레드 방식으로 큐테이블에 데이터 삽입하도록 개선하였다.

그럼에도 동일한 문제가 발생….

  • L4 로드밸런서 문제를 의심

이중화 된 웹서버 로그가 수상했다.

위에서도 말했듯이 어떤 상황에서는 1번기에 동일하게 중복 요청이 들어왔고 어떤 상황에서는 1번기, 2번기 각각 중복 요청이 들어왔다.

 

중간에 거치는 게 뭐가 있을까?

L4 의 어떤 설정 때문에 그런가?

 

원인 : 로드 밸런서 유휴 시간 초과

클라우드 인프라 L4 설정 중 Connection Idle Timeout 설정의 문제였다.

디폴트 1분(60초)으로 설정되어 있었다. ( 그래서 로그에서도 딱 1분이 걸렸었구나...)

로드밸런서가 target 에 대해 요청하는 동안 target 이 TCP 연결을 닫았다.

개발기에서 3초로 설정하고 테스트 해보았더니 이슈와 같이 동일한 요청이 들어왔다.

찾았따!!!!!

**1. Connection Idle Timeout 이 뭔가?**

Http 통신할 때, client 에서 아무런 데이터 보내지 않을 경우 idle timeout 이 지난 후 
connection 을 close 한다.

즉, http 통신 연결이 자동으로 닫힐 때까지의 시간을 말한다.
이때 502/504 에러 응답코드가 나올 수 있다.

연결 유휴 시간 제한(기본값은 60초)은 "표준" 시간 제한으로 작동한다.
따라서 Idle Timeout 경과할 때까지 데이터가 전송되거나 수신되지 않은 경우 로드 밸런서가 연결을 닫는다.
애플리케이션의 Idle Timeout 을 로드밸런서 Idle Timeout 보다 크게 설정하는 것을 권장한다.

**2. Idle Timeout 과 request timeout 의 차이가 뭔가?**

Connection Idle Timeout 은 tcp 연결이 유휴한 상태로 있을 수 있는 최대 시간이다.
요청 시간 제한과 동일하게 동작한다.

Connection Timeout 은 tcp 연결을 지속하는 최대 시간을 말한다.

여기서 Idle Timeout 과 request timeout 중 Idle Timeout 이 우선순위로 지정된다.
그래서 Idle Timeou 이 request timeout 보다 낮아도 우선순위로 적용된다.

출처 : <https://repost.aws/questions/QUqP4BHC9iQ0uIB69M7QrjSg/502-errors-with-application-load-balancer-idle-timeout-apache2-keep-alive-timeout>

해결

60 초로 설정되어 있는 idle timeout 시간을 600초(10분) 으로 늘려주었다.


그 외 원타임토큰에 대한 문제

첫 번째 요청이 들어오면 해당 토큰은 세션에서 remove 된다.

그런데 두 번째 요청이 들어왔을 때 세션에 해당 토큰이 살아있었다.

테스트 해보니, 하나의 요청이 완전히 끝나기 전까지는 세션 정보가 변경되지 않았다.

그래서 두 번째 요청이 들어왔을 때 변경된 세션 정보가 아닌 기존 정보를 가지고 있었던 것이다.

원타임토큰을 세션에서 관리하는 것이 아닌 DB 에서 관리는 하는 것으로 변경하는 것이 좋을 것 같다.

+ Recent posts