폴백은 평소에 안 돌면 폴백이 아니었다
알림톡 일일 한도 초과와 대시가 섞인 발신번호 형식이 겹쳐 인증번호가 전면 실패한 장애를 통해, 폴백 경로를 카나리로 강제 실행해야 하는 이유를 정리합니다.
07시엔 멀쩡했는데 10시 24분에 전부 막혔습니다
제가 운영하던 서비스에서 어느 날 아침, 회원가입 인증번호가 한 건도 나가지 않는 일이 있었습니다. 오전 7시 1분에는 인증번호가 정상적으로 발송됐습니다. 로그를 보면 그 시각까지는 아무 이상이 없었습니다. 그런데 10시를 조금 넘긴 시점부터 발송 성공률이 흔들리기 시작하더니, 10시 24분에는 인증 요청이 전면적으로 503을 뱉었습니다. 가입 화면 앞에 선 사람이라면 누구도 다음 단계로 넘어갈 수 없는 상태였습니다.
처음엔 서버가 죽었나 싶었습니다. 하지만 나머지 API는 멀쩡했습니다. 오직 인증번호 발송만 죽어 있었습니다. 로그를 거슬러 올라가며 저는 원인이 하나가 아니라 둘이라는 걸 알게 됐습니다. 그리고 그 둘 중 하나는, 몇 달 전부터 조용히 부러져 있었지만 아무도 몰랐던 것이었습니다.
원인 ①: 월 한도는 3%인데 일일 한도가 먼저 찼습니다
인증번호는 평소 전량이 카카오 알림톡으로 나가고 있었습니다. 그날 오전, 알림톡 발송이 갑자기 429를 내기 시작했습니다. 한도 초과였습니다.
이상했던 건 월 한도였습니다. 그 달 알림톡 월 한도는 30,000건이었는데, 실제로 쓴 건 3% 남짓이었습니다. 숫자만 보면 한도 근처에도 안 간 셈입니다. 그런데도 발송이 막혔습니다.
범인은 일일 한도였습니다. 월 한도와 별개로 하루에 1,000건이라는 일일 상한이 걸려 있었고, 그날은 가입 트래픽이 몰리면서 오전 10시 20분대에 이 1,000건을 다 써 버린 것이었습니다. 월 단위로만 소진율을 보던 저는 “아직 한참 남았다”고 착각하고 있었습니다. 정작 먼저 벽에 부딪히는 건 일일 한도였는데 말입니다.
여기까지였다면 큰 사고는 아니었어야 합니다. 알림톡이 막히면 SMS로 떨어지도록 폴백을 만들어 뒀으니까요. 문제는 그 폴백이었습니다.
원인 ②: 폴백 SMS는 몇 달 전부터 부러져 있었습니다
알림톡이 429를 맞자 코드는 설계대로 폴백 SMS 발송을 시도했습니다. 그런데 SMS API가 이번엔 400을 냈습니다. 잘못된 요청이라는 뜻입니다.
원인은 발신번호였습니다. 발신번호가 환경변수에 대시가 섞인 형식, 그러니까 지역번호-국번-번호처럼 하이픈이 들어간 문자열로 저장돼 있었습니다. SMS API는 발신번호를 하이픈 없는 숫자열로만 받는데, 저장된 값이 그 형식을 벗어나 있으니 요청 자체가 거부된 것입니다.
무서운 건 이 오류의 나이였습니다. 이건 그날 아침에 생긴 문제가 아니었습니다. 아마 몇 달 전 환경변수를 설정하던 순간부터 잘못된 형식이었을 겁니다. 다만 평소 인증번호가 전량 알림톡으로 나가다 보니 SMS 발송 경로는 단 한 번도 실제로 호출되지 않았고, 그래서 이 400은 몇 달 동안 아무에게도 발견되지 않은 채 잠들어 있었습니다.
그날 오전 10시 24분은, 알림톡이 처음으로 막힌 날이자 이 잠재 실패가 처음으로 깨어난 날이었습니다. 주 경로가 살아 있는 한 폴백은 호출되지 않고, 호출되지 않으니 부러진 걸 알 수 없고, 정작 폴백이 필요한 그 순간에 폴백도 함께 부러져 있는 겁니다. 두 개의 실패가 정확히 같은 날 만났습니다.
폴백은 평소에 돌지 않으면 폴백이 아닙니다
이번 일에서 제가 얻은 판단 기준은 세 가지입니다.
첫째, 폴백은 평소에 안 돌면 폴백이 아닙니다. 한 번도 실행되지 않는 대비 경로는 대비가 아니라 검증되지 않은 코드일 뿐입니다. 그날 이전까지 저는 “SMS 폴백이 있다”는 사실만으로 안심하고 있었습니다. 실제로는 그 폴백이 400을 낸다는 걸 몰랐을 뿐입니다. 있다고 믿는 것과 동작한다고 확인한 것은 전혀 다릅니다.
둘째, 주기적인 강제 실행, 즉 카나리 없이는 잠재 실패가 쌓입니다. 하루에 한 번이라도 폴백 SMS 경로를 실제로 한 통 발송해 보고 결과를 확인했다면, 그 400은 몇 달이 아니라 며칠 만에 드러났을 겁니다. 폴백을 신뢰하려면 폴백이 필요 없는 평상시에도 일부러 태워 봐야 합니다. 저는 이후 발신번호 검증과 실제 발송 응답까지 확인하는 점검을 정기적으로 돌리기로 했습니다.
셋째, 한도는 월이 아니라 일 단위부터 봅니다. 월 소진율 3%라는 넉넉한 숫자에 안심하는 동안, 실제 벽은 일일 1,000건에 있었습니다. 한도가 여러 층위로 걸려 있을 땐 가장 먼저 부딪히는 층위를 기준으로 감시해야 합니다. 저는 일 단위, 더 나아가 시간당 소진 추이를 보고 한도 도달 전에 알림을 받도록 바꿨습니다.
폴백을 만들어 두는 것으로 제 할 일이 끝났다고 생각했던 게 이번 장애의 진짜 원인이었습니다. 만들어 두는 것과 살아 있게 유지하는 것은 다른 일이었습니다. 안 돌아본 폴백은, 필요할 때 돌아봐도 늦습니다.
자주 묻는 질문
폴백 경로는 평소에 검증하지 않아도 되나요?
평소에 한 번도 실행되지 않는 폴백은 검증되지 않은 코드와 같습니다. 주 경로가 살아 있는 동안 설정 오류나 자격 증명 만료가 조용히 쌓이다가, 정작 폴백이 필요한 순간 함께 실패합니다. 주기적으로 강제 실행해 실제로 응답을 받아 두어야 합니다.
카카오 알림톡 월 한도만 확인하면 충분한가요?
월 한도가 넉넉해도 일일 한도가 먼저 막힐 수 있습니다. 저는 월 한도의 3%만 쓴 상태에서 일일 한도 1,000건에 걸려 429를 받았습니다. 한도는 월이 아니라 일 단위부터, 가능하면 시간 단위 소진 추이까지 봐야 합니다.
발신번호 형식 때문에 SMS API가 400을 내는 이유는 무엇인가요?
많은 문자 발송 API는 발신번호를 하이픈 없는 숫자열로만 받습니다. 지역번호-국번-번호처럼 대시가 섞인 형식으로 환경변수에 저장돼 있으면 요청이 400으로 거부됩니다. 이런 값은 저장 시점에 정규화하거나 발송 직전 하이픈을 제거해야 합니다.
두 경로가 동시에 실패하는 장애는 어떻게 예방하나요?
주 경로와 폴백이 서로 다른 원인으로도 겹칠 수 있다는 전제로 봐야 합니다. 폴백을 카나리로 정기 실행해 잠재 실패를 미리 드러내고, 주 경로의 소진 추이를 실시간으로 감시해 한도 도달 전에 알림을 받으면 두 실패가 같은 날 만나는 상황을 피할 수 있습니다.