1%가 겪은 버그 고쳐야할까요?
안녕하세요. 토스 QA Platform 팀 문성준 입니다.
지난 글에서 저희만의 플랫폼 토션(Tossion)을 소개했는데요. 오늘은 그 위에 올린 이야기, 핫픽스를 다뤄 보려고 합니다. 기준을 세우고, 뒤집고, 다시 세운과정까지요.
새 버전을 배포한 지 얼마 안 돼 알림이 울립니다. 확인해 보니 특정 조건에서만, 그것도 이제 막 배포가 시작돼 1%의 사용자에게서만 나타나는 문제입니다. 지금 바로 핫픽스를 해야 할까요, 아니면 다음 정기 배포까지 기다려도 될까요?
QA라면 한 번쯤 이 갈림길 앞에 서봤을 겁니다. 빨리 고치는 게 정답 같지만, 급하게 낸 핫픽스가 또 다른 버그를 부르기도 하니까요. 그래서 저희에게 핫픽스는 '빨리 고치는 일'이 아니라, '고칠지 말지부터 판단하는 일'입니다. 오늘은 핫픽스를 어떻게 막고, 또 어떻게 관리하는지 이야기합니다.
심각도 한 줄로 나눌 수 없었습니다
원래 핫픽스에도 기준은 있었습니다. Critical, Major, Minor 같은 흔한 심각도 규칙이었죠. 작은 앱이라면 이 정도로 충분합니다.

그런데 토스에서는 이 규칙이 자꾸 어긋났습니다. 사용자가 많다 보니, 아주 낮은 배포율에서 생긴 문제도 실제로는 수많은 사람이 겪습니다. 게다가 화면 하나에 여러 비즈니스 로직이 얽혀 있어서, 같은 'Major'라도 어떤 건 당장 고쳐야 하고 어떤 건 기다려도 됐습니다. 심각도 한 줄로는 나눌 수 없었습니다. 매번 들어오는 핫픽스 요청이 다 달랐으니까요.
그래서 규칙 자체를 다시 짰습니다. 심각도로 줄 세우는 대신, 핫픽스를 할지 말지로 판단하는 구조로 바꿨습니다. 어떻게 판단하는지는 바로 뒤에서 풀어보겠습니다.

그리고 이 규칙은 한 번 정하고 끝이 아닙니다. 매달 지난 핫픽스를 다시 꺼냅니다. 잘 판단한 건 왜 잘됐고, 애매했던 건 무엇 때문에 애매했는지를요. 그러다 보면 기준이 계속 손질됩니다. 어떤 건 더하고, 어떤 건 덜어냅니다. 지금 이 글에서 소개하는 것도 완성된 규칙이 아니라, 그렇게 다듬어 온 현재의 모습입니다.
점진 배포라는 모순
여기에 더 근본적인 모순이 있었습니다.
토스는 새 버전을 한 번에 100% 내보내지 않습니다. 일부 사용자에게 먼저 배포하고, 문제가 없으면 점점 넓혀갑니다. 사용자의 불편을 빠르게 잡아내려는 안전장치죠.

그런데 이 점진 배포가 핫픽스 판단과 정면으로 부딪힙니다. "1%에서만 발생했으니 심각하지 않다"는 판단은, 사실 아직 이 버전을 받지 못한 나머지 99%의 잠재 위험을 보지 않은 것입니다. 지금 1%인 이유가 문제가 작아서가 아니라, 배포가 거기까지 밖에 안갔기 때문일 수 있으니까요. 반대로, 정말 특정 조건에서 그 1%에게만 생기는 문제라면, 이번엔 핫픽스가 부담이 됩니다. 잘 쓰고 있는 99%에게까지 새 빌드를 내보내는 일이라, 문제 하나를 잡으려다 멀쩡한 다수를 새로운 위험에 밀어 넣을 수 있으니까요.
같은 '1%'가 이렇게 정반대로 읽힙니다. 그래서 저희에게 필요했던 건 더 촘촘한 심각도 표가 아니라, 이 숫자를 맥락에 맞게 해석하고 빠르게 '할지 말지'를 결정할 수 있는 판단 구조였습니다.
신중하게 판단합니다
저희는 핫픽스를 우선순위로 나누지 않습니다. 핫픽스를 진행할 것과, 다음 정기 배포에 반영할 것으로 나눕니다.
진행하는 쪽은 분명합니다. 기본 기능을 아예 쓸 수 없거나, 매출에 직접 영향을 주거나, 규제와 얽힌 문제. 반대로 다음 배포로 넘겨도 되는 쪽도 정해뒀습니다. 이용률이 아주 낮은 기능이거나, 특정 조건에서만 생기거나, 우회할 방법이 분명한 경우입니다. 즉, 우선순위가 높다고 무조건 핫픽스를 하는 것이 아니라, 지금 바로 수정해야할 조건에 해당하는지를 봅니다.
판단은 한 사람이 하지 않습니다. 그 주의 배포를 책임지는 릴리즈 마스터 와 품질을 책임지는 큐에이 마스터가 함께 봅니다. 한쪽은 배포 관점에서, 다른 쪽은 품질 관점에서 검토하고, 둘이 합의해야 핫픽스가 나갑니다. 급한 문제는 빌드를 먼저 돌리는 예외를 두되, 최종 배포 결정만큼은 반드시 양쪽 합의를 거칩니다.

한 가지 버린 것도 있습니다. 처음엔 "핫픽스를 결정하면 2~4시간 버퍼를 두고 다른 이슈도 같이 검토하자"는 안이 있었어요. 그런데 시간을 기준으로 두면 핫픽스의 목적과 부딪힙니다. 그래서 시간 기준을 없애고, '충분히 검토했는가'에 집중하기로 했습니다. 목표 문구도 ‘어렵고 안전하게’에서 ‘신중하고 안전하게’로 바꿨습니다. 핫픽스를 어렵게 만드는 게 목적이 아니니까요.
크래시가 나면 빠르게 재현합니다
검증 중에 크래시가 나면, 정작 가장 오래 걸리는 건 수정이 아니라 재현입니다. ‘어떤 상황에서 터졌는지’를 모르면 개발자와 이야기를 시작할 수조차 없으니까요.
그래서 crash-path를 만들었습니다. Claude의 스킬(Skill)로 만든 크래시 분석 도구입니다. 크래시 URL 하나를 넣으면, 로그에 남은 사용자의 행동 기록을 시간 순서대로 되짚어 어느 화면에서 무엇을 하다 죽었는지, 어떤 순서로 하면 재현되는지를 한 장으로 정리해 줍니다. 그 크래시가 났던 기기·OS·메모리 같은 환경까지 함께요.
그런데 Bugsnag(Android)와 Firebase Crashlytics(iOS)는 각각 데이터를 다른 방식으로 줍니다. 그래서 crash-path는 URL 주소만 보고 어느 플랫폼인지를 스스로 판단해, 플랫폼에 맞는 통로로 데이터를 가져옵니다. 사람은 "이거 안드로이드예요, iOS예요" 같은 걸 한 번도 고를 필요가 없습니다. 이렇게 판단에 필요한 질문 자체를 줄였고, 크래시가 감지되면 지능형 AI 봇이 crash-path를 자동으로 실행해 분석 결과를 채널에 바로 올려놓게 했습니다.
여기서 저희가 그은 선이 하나 있습니다. 화면을 무엇이라 부를지, 무엇을 회귀로 볼지 같은 판정은 AI의 추측에 맡기지 않았습니다. 화면 이름을 어떻게 읽을지, 로그의 어떤 패턴을 재현 신호로 볼지를 규칙으로 정해 스킬에 넣어 두고, AI는 그 규칙을 적용해 정리만 합니다. AI가 화면을 멋대로 지어내면 재현 가이드를 믿을 수 없으니까요. 덕분에 결과가 매번 일관될 수 있었습니다.

분석 결과는 개발자와 나누도록 한 덩어리로 보냅니다. 덕분에 재현 방법을 찾느라 시간을 흘려보내지 않고, 바로 개발자와 원인을 논의합니다. 재현이 빨라진 만큼, 고칠지 말지 판단하는 것도 빨라집니다.
남아 있어야 돌아봅니다
여기서부터는 막은 다음의 이야기입니다. 신중하게 판단하고, 크래시를 빠르게 재현하고, 배포 뒤를 지켜보는 데까지가 문제를 막는 과정이었다면, 이제부터는 같은 사고가 두 번 나지 않게 관리하는 일입니다.
그런데 관리를 하려면 먼저 돌아볼 게 남아 있어야 합니다. 그날 왜 그렇게 판단했는지, 무엇으로 고쳤는지가 사라지면 다음 달엔 기억에 기댈 수밖에 없으니까요. 이 일을 처음 시작했을 때 가장 먼저 부딪힌 게 바로 이것이었습니다. 핫픽스 기록이 한군데에 있지 않았습니다. iOS와 Android가 서로 다른 곳에, 서로 다른 형식으로 적고 있었고, 지난 사고 하나를 찾으려면 여기저기를 뒤져야 했습니다.
그래서 흩어져 있던 기록을 한 틀로 맞춰 토션에 모았습니다.
어떤 버전에서 언제 발생했고, 사용자가 겪은 증상은 무엇이며, 기술적 원인과 원인 종류는 무엇이고, 무엇으로 고쳤고 어떻게 막을지까지. 제각각이던 기록을 같은 항목으로 정리하고 나니, 비로소 한자리에 놓고 견줘 볼 수 있게 됐습니다. 그리고 이걸 특정 담당자만 보는 게 아니라 누구나 열어 볼 수 있게 했습니다. 기록은 쌓아두는 것보다 함께 보는 게 중요하니까요.
토션에 쌓는 건 진행한 핫픽스만이 아닙니다. 하지 않기로 한 것도 함께 쌓습니다.

앞에서 저희 기준은 우선순위가 아니라 진행하느냐 다음 배포로 넘기느냐라고 했는데요. 그래서 넘기기로 한 건에는 왜 넘겼는지 사유를 함께 남깁니다. 이용률이 낮아서, 특정 조건에서만 생겨서, 우회할 방법이 있어서처럼요. 언뜻 안 한 일은 남길 게 없어 보이지만, 저희에겐 이게 다음 판단의 교보재가 됩니다.
비슷한 상황이 또 왔을 때, 지난번엔 이런 이유로 넘겼다는 근거를 꺼내 볼 수 있으니까요. 판단이 사람 머릿속에만 있으면 그 사람이 자리에 없을 때 흔들리지만, 사유가 쌓이면 기준이 됩니다. 안 한 판단도 판단이고, 그 판단에도 근거가 남아 있어야 다음 판단도 일관될 수 있습니다.
그런데 이 기록을 어떻게 남기느냐에서, 저는 한 번 크게 틀렸습니다. 핫픽스가 끝나면 왜 생겼고 어떻게 막을지를 남겨야 하는데, 그 방식을 잘못 잡았습니다.
처음 세운 가설은 이랬습니다. 사후기록을 일부러 번거롭게 만들면, "이거 굳이 핫픽스까지 해야 하나?" 하는 자각이 생겨서 핫픽스가 줄지 않을까?
결과는 정반대였습니다. 기록이 번거로워지자 사람들은 행동이 아니라 기록을 줄였습니다. 핫픽스는 그대로인데 기록만 부실해졌어요. 나중에야 알았습니다. 마찰을 행동 앞이 아니라 기록 앞에 둔 게 실수였다는 걸요. 마찰은 언제나 가장 가까운 것부터 회피하게 만듭니다. 품질 문화는 장치 하나로 움직이지 않는데, 저희는 사후기록 하나만 조이려 했던 겁니다.

그래서 방향을 뒤집었습니다. 기록을 어렵게 만드는 대신, 쉽게 만들고 맥락을 풍부하게 채우기로요. 핫픽스는 대부분 채널의 대화에서 시작되니, 그 논의 맥락을 지능형 AI 봇이 사후기록 초안에 미리 채워둡니다. 사람은 처음부터 쓰는 게 아니라, 채워진 초안을 검토하고 빠진 것만 보탭니다. 기록의 부담은 낮아지고, 논의 맥락은 오히려 더 잘 남습니다.
같은 사고를 두 번 내지 않으려면
기록을 모으는 건 시작일 뿐입니다. 쌓아두기만 해서는 핫픽스가 저절로 줄지 않으니까요. 줄이려면 현황을 보고 무엇을 바꿀지를 정해야 합니다. 그래서 매달 한 번, Android, iOS, QA가 함께 모이는 자리를 만들었습니다.

이 자리에서 하는 일은 세 가지입니다. 지난달 핫픽스를 함께 돌아보고, 되풀이되는 원인에는 재발방지 액션을 정하고, 앞서 정했던 액션이 실제로 지켜졌는지 확인합니다. 이번 달에 새 다짐 하나를 더 얹고 끝나는 게 아니라, 저번 달 다짐이 지켜졌는지를 매번 되짚습니다. 핫픽스를 줄이는 건 장치 하나가 아니라, 이렇게 매달 돌아보는 반복이 하는 일이었습니다.
그 확인이 가장 크게 걸리는 데가 재발 방지입니다. 재발 방지는 문서에 "이렇게 막겠습니다"라고 적는 걸로 끝나기 쉽습니다. 적어두고 아무도 안 하면 그만이니까요. 저희는 여기서 한 걸음 더 갑니다. 실제로 이행됐는지까지 추적합니다.
토션에서 재발 방지는 핫픽스와 별개의 항목으로 관리됩니다. 어떤 원인을 겨냥한 조치인지, 무엇을 했는지, 증적은 어디 있는지가 남습니다. 그리고 그 조치를 이슈 티켓으로 연결해두면, 티켓이 실제로 닫혔는지를 시스템이 주기적으로 확인해 이행 상태를 자동으로 갱신합니다. 사람이 "했다"고 체크하는 게 아니라서, 이 상태는 화면에서 손으로 바꿀 수도 없습니다.
이 기록들은 타임라인 위에 쌓입니다. 정기 배포와 핫픽스, 재발 방지 조치가 한 축에 나란히 놓여서, 언제 무엇이 터졌고 그 뒤에 무엇을 했는지가 한눈에 보입니다. 하겠다고 적어놓고 미룬 게 있으면, 다음 달 그 자리에 그대로 미이행으로 올라옵니다. 재발 방지가 '적어둔 다짐'이 아니라 '끝까지 확인하는 약속'이 되는 이유입니다.

이 모든 기록은 결국 지표가 됩니다. 핫픽스는 감으로 관리할 수 없으니, 남긴 기록을 숫자로 봅니다.
원인을 여섯 가지(코드 결함, 의존성·SDK, 배포 실수, 환경, 외부 API, 기타)로 나눠 어디서 자주 터지는지 봅니다. 어떤 원인이 되풀이되는지가 쌓이면, 다음에 무엇을 먼저 막아야 할지가 보입니다.
프로세스를 정비하고 이 관리 체계를 갖춘 뒤로, 토스 앱의 핫픽스는 줄어들고 있습니다.

완성된 규칙이 아니라, 계속 다듬는 규칙
지금까지 소개한 것들은 처음부터 이 모습이 아니었습니다. 시간 버퍼를 두자던 규칙도, 사후기록을 번거롭게 만들자던 시도도, 앞에서 이야기한 대로 부딪히고 뒤집으며 지금 자리에 왔습니다. 그래서 이 프로세스에는 '완성'이 없습니다. 매달 조금씩 더하고 덜어내며, 지금도 바뀌고 있습니다.
돌아보면, 글머리에서 모순이라 불렀던 것도 마찬가지였습니다. 점진 배포와 핫픽스는 애초에 모순이 아니었어요. 점진 배포는 문제를 좁은 곳에서 먼저 만나게 하고, 핫픽스는 그렇게 드러난 문제를 빠르게 막습니다. 사용자의 피해를 줄인다는 같은 목적을, 앞과 뒤에서 나눠 맡을 뿐입니다. 정말 부딪혔던 건 배포 방식이 아니라, '1%'라는 같은 신호가 상황에 따라 정반대로도 읽힐 수 있다는 점이었습니다. 저희가 만든 건 그 둘을 잇는 판단이었습니다.
핫픽스는 없는 게 가장 좋습니다. 하지만 매주 새 버전을 내보내는 이상, 0이 되기는 어렵습니다. 그래서 저희의 일은 세 가지로 정리됩니다. 굳이 안 해도 될 핫픽스는 하지 않고, 해야 할 핫픽스는 신중하고 안전하게 하고, 한 번 난 사고는 두 번 나지 않게 만드는 것. 빠르게 고치는 것보다 먼저 필요한 것은, 지금 고쳐야 하는 문제인지 제대로 판단하는 일입니다.
