타임아웃의 장단점

타임아웃이 야기할 수 있는 부작용을 알아봅니다.

 인터넷으로 요청을 보낼 때 응답이 없으면 어떻게 해야 할까요?
 우리의 시간은 무한하지 않습니다. 잠시 기다려보고, 대기 시간이 길어지면 예외 처리를 합니다. 예를 들어 어떤 페이지에 접속 시도를 했다가 10초 이내에 응답이 없으면 에러 페이지를 띄우는 것처럼요. 이것이 타임아웃 처리입니다.
 타임아웃은 클라이언트로 하여금 자신의 요청이 정상 처리됐는지, 에러났는지 판단을 쉽도록 합니다. 하지만 한계점이 있습니다.

"타임아웃 처리를 언제 하지?"

 타임아웃은 하늘의 계시를 받는 게 아닙니다. 개발자가 지정한 시점에, 지정한 시간 동안만 기다립니다. 이제 이러한 발문을 떠올려볼 수 있습니다.

“개발자가 지정한 시간이 여러 문제에 충분히 대응할 수 있는 시간인가?”

 지정 시간이 길면 느린 인터넷에도 정상 처리를 하겠지만 클라이언트가 오래 기다려야 할 수 있고, 지정 시간이 짧으면 정상 요청도 예외 처리될 확률이 높아집니다.

왜 두 번 예약됐지?

 카이트는 항공권 예매 등 항공 서비스를 제공하는 사이트입니다. 2023년 6월, 카이트는 서비스를 리뉴얼하던 중, 테스트 과정에서 예약을 한 번 누르면 두 개의 예약이 생기는 에러를 마주합니다.

얘 왜 이래?

 이 에러는 주에 다섯 건 가량 발생했습니다. 그 상태로 배포했다간 고객의 무수한 컴플레인이 스케줄에 들어가므로 가만히 둘 수 없었습니다.
 개발자는 즉시 로그를 분석했습니다. 대기 시간이 30초가 넘어갈 시점에 자동으로 재시도 요청이 일어나고, 그래서 중복 예약이 된다는 사실을 발견했습니다. 문제는 그 현상이 어디에서 일어나냐는 것이었습니다.
브라우저, axios 설정, AWS, 리버스 프록시, 게이트웨이, gRPC 서버...클라이언트 단부터 오리진 서버까지 순서대로, 단계마다 꼼꼼히 살폈지만 어디에서도 문제를 찾을 수 없었습니다.

대체 왜 이러는 건데...

 사건이 미궁에 빠질 무렵, 개발자는 동료의 도움을 받아 간신히 진실을 찾아냈습니다. 알고 보니 일부 사무실의 보안 네트워크 설정이 30초 타임아웃으로 돼있었고, 그 때 크롬 브라우저가 재시도 요청을 보내서 예약이 중복됐던 것입니다.
 까다로운 전제라고 보여도, 고객이 느린 인터넷을 사용한다면 얼마든지 발생할 수 있는 에러입니다. 개발자는 본질적인 해결이 필요하다고 느꼈고 아래와 같이 처리했습니다.

  1. 예약 생성 전에 미리 예약 키(key) 발급 API를 호출하여 예약 키 값을 받아옴
  2. 기존 예약 생성 API 호출시 1번 단계에서 받아온 키값을 항상 같이 넣어서 전송

 이와 같은 처리를 하면 n번의 요청을 보내도 1개의 결과만 발생합니다. HTTP에선 이러한 성질을 멱등성이라 부릅니다.

거리가 멀면 이메일이 안 보내져요

 "거리가 머니까 이메일을 보낼 수 없다."란 말을 들으면 코웃음치거나 당황할 겁니다. 하지만 실제 상황입니다. 아래는 Trey Harris라는 사람의 경험담을 번역했습니다. 기술적인 묘사가 부정확하다는 지적도 있지만 저자는 알기 쉽고 재미있게 전달하기 위해 그렇게 축약했다고 합니다. 한 번 보시죠.


몇 년 전, 내가 대학 이메일 시스템 운영 업무를 하던 때였다. 통계학과 학과장으로부터 전화가 왔다.

"학교 밖으로 이메일을 보내려고 하면 문제가 있어요."
"무슨 문제죠?"
"500마일 이상 떨어진 곳에 메일이 안 보내져요."
나는 라떼를 마시다가 목이 턱 막혔다.
"뭐라고요?"
"여기서 500마일 이상 떨어진 곳엔 메일이 보내지지 않아요."
그가 다시금 말했다.
"좀 더 정확히는 520마일 정도예요. 그 이상부터 안 보내져요."
"음...이메일 전송 방식은 거리하곤 상관이 없어요. 보통은요."

 나는 당황하지 않으려고 애썼다. 통계학과처럼 약소한 학과라도, 학과장쯤 되는 사람 앞에서 패닉에 빠진 모습을 보여서는 안 됐다.

"어떤 것 때문에 500마일 이상 떨어진 곳에 메일을 못 보낸다고 생각하시나요?"
"제 생각이 아니에요. 이 문제를 며칠 전에 알고서"
"며칠 전요?"
나는 놀랐다.
"그 동안 메일을 못 보냈어요?"
"보냈어요. 그런데 거리가 어느 정도 멀어지면"
"500마일 정도랬죠."
나는 그를 위해 구체적인 수치로 맞장구쳤다.
"그런데 왜 좀 더 일찍 전화 안 하셨어요?"
"분석할 데이터가 충분하게 모이질 않았었어요."
아차, 이 사람은 통계학과였지.
"어쨌든, 지구통계학과에 조사를 부탁했어요"
"지구통계학과 말이죠..."
"네. 그리고 그녀는 지도에 메일 송신이 가능한 반경을 그렸어요. 반경 근처에선 송신이 불확실하지만 바깥으로 벗어나면 확실하게 이메일이 안 보내져요."
"알겠어요."
나는 대답하곤 머리를 짚었다.
"언제부터 그랬나요? 며칠 전이라고 했는데 그때 시스템에 무슨 변화가 있었나요?"
"음, 컨설턴트가 와서 서버를 패치하고 재부팅했어요. 전화했는데 그는 메일 시스템은 안 건드렸다더군요."
"알겠습니다. 조사해보고 다시 전화드리죠."

 나는 내가 처한 상황을 믿을 수 없었다. 적어도 오늘이 만우절은 아니었다. 누군가 나한테 이런 장난을 칠만한 사람이 있나 고민도 들었다.
 나는 서버에 로그인해서 테스트 이메일을 몇 개 보냈다. 서버는 노스 캐롤라이나 리서치 트라이앵글에 위치해있고 스스로에게 보내는 건 문제없었다.
 리치몬드, 애틀랜타, 워싱턴, 프린스턴(400마일)도 잘 됐다. 하지만 멤피스(600마일)로 보내려는 시도는 실패했다. 보스턴, 디트로이트도 실패.
 나는 주소록을 꺼내어 범위를 좁혀나갔다. 뉴욕(420마일)은 되고 프로비던스(580마일)는 실패했다. 정신을 잃을 것만 같았다. 노스 캐롤라이나에 거주하지만 ISP(인터넷 서비스 제공자, Internet service provider)가 시애틀에 있는 친구에게 이메일을 보내봤고, 다행스럽게도 실패했다.
 만약 문제 원인이 서버가 아니라 수신자가 사는 지역이었다면 펑펑 울었을 것이다. 믿을 수 없게도, 이건 재발 가능한 문제라고 확인됐기에 sendmail.cf 파일을 훑어봤다. 매우 익숙했고 아무 문제 없어보였다. 내 홈 디렉토리의 sendmail.cf와 비교해도 다른 게 없었다. 내가 작성한 sendmail.cf이 맞고, 특별히 내가 "500 마일 넘는 곳에는 전송 못함" 옵션을 붙이지는 않았음이 분명했다. 막막해진 나는 SMTP 포트로 텔넷에 접속했고 서버가 썬OS 센드메일 배너로 나를 반겼다.
 잠깐... 썬OS 센드메일 배너라? 당시 센드메일 8은 충분히 안정된 버전이었지만, 구닥다리 5 버전도 버젓이 배포되고 있었다.
 나는 센드메일 8을 표준으로 채택하여 암구호 같은 구버전 코드 대신 8 버전의 자기 설명식 옵션과 변수명으로 sendmail.cf를 작성했던 사실을 떠올렸다.

모든 의문이 단번에 풀렸고, 나는 식은 라떼를 마시며 다시금 목이 턱 막히는 기분을 느꼈다.
 아마 컨설턴트가 서버 패치를 할 때 썬OS 버전이 업그레이드되면서 센드메일이 다운그레이드됐으리라. 다행히 sendmail.cf 파일까지 건드리진 않았지만, 현재 상황에선 먹통이었다.

 우연하게도, 그 때 썬에서 제공한 5 버전에선 8 버전 sendmail.cf 파일을 읽을 수는 있었다. 많은 규칙이 큰 변경을 거치진 않았기 때문이다. 하지만 5 버전 이후에 도입된 옵션들은 무시됐다.
 센드메일 바이너리에는 그 옵션들의 기본값이 컴파일돼있지 않았기 때문에 0으로 설정됐다. 0으로 바뀐 설정 중 하나는 원격 SMTP 서버에 연결하는 타임아웃이었다. 몇 차례 실험을 해보니 3ms 정도 쯤 지나면 중단되는 현상을 확인했다.

 당시 그 학교 네트워크가 특이했던 점은 100% 스위칭 구조였다는 것이다. 송신 패킷이 먼 PoP(Point-of-Presence)에 도달할 때까지 라우터 지연이 발생하지 않았다. 즉, 원격 호스트로 연결하는 데에 걸리는 시간은 라우터 지연이 아니라 순전히 광속에 영향을 받았다는 뜻이다.


마무리

 두 케이스는 개발자의 실수가 아닐 뿐더러, 타임아웃이 바로 떠오르는 문제도 아니니 난처할 따름입니다. 그럼에도 이처럼 타임 아웃으로 인한 부작용은 여러 곳에서, 여러 이유로 발생 가능합니다. 이 글이 여러분의 휴리스틱에 조금이나마 도움이 되면 좋겠습니다.

참조