버그는 왜 금요일 오후에 터질까
목차9

개발자들 사이에는 오래된 격언이 하나 있다. 금요일에는 배포하지 말라. 이 격언은 꽤 진지하게 지켜지는데, 어떤 팀은 아예 금요일 오후부터 배포 파이프라인을 잠가두기도 하고, 어떤 팀은 금요일 배포에 리드의 승인을 하나 더 받게 하기도 한다.
이 격언에는 다들 하나씩 사연이 있다. 금요일 오후 다섯 시에 가벼운 마음으로 배포한 수정 사항이, 토요일 아침 알림 폭탄이 되어 돌아온 이야기 말이다. 그리고 그런 이야기를 한 번 겪고 나면, 금요일은 저주받은 요일이라는 믿음이 꽤 단단하게 자리를 잡는다.
그런데 조금만 생각해보면 이상한 점이 있다. 코드는 오늘이 무슨 요일인지 모른다. 서버도, 데이터베이스도, 사용자의 브라우저도 금요일이라고 해서 특별히 더 예민해지지 않는다. 그렇다면 버그는 정말 금요일을 좋아하는 걸까, 아니면 우리가 그렇게 느끼는 것뿐일까?
그래서 이번 포스팅에서는 금요일 배포 금지라는 오래된 규칙을 확률과 피해 기대값으로 한번 계산해보려고 한다. 금요일에 정말 버그가 더 많이 생기는지, 같은 버그가 왜 금요일에는 더 아프게 느껴지는지, 그리고 금요일 배포를 막는 것이 정말 정답인지를 차례대로 이야기해보자.
버그는 요일을 모른다
먼저 가장 단순한 모델에서 시작해보자. 배포 한 번이 사고로 이어질 확률을 라고 하고, 이 확률이 요일과 상관없이 일정하다고 가정하자. 그렇다면 배포를 번 했을 때 사고가 한 번 이상 날 확률은 이렇게 계산할 수 있다.
여기서 은 번의 배포에서 일어난 사고의 횟수이고, 는 배포 한 번이 무사히 지나갈 확률이고, 이걸 번 곱한 은 번 모두 무사할 확률이다. 1에서 그걸 빼면, 적어도 한 번은 사고가 날 확률이 된다. 수식은 이게 전부다.
숫자를 넣어보자. 배포 한 번의 사고 확률이 5%라면, 14번째 배포쯤에서 사고가 한 번 이상 났을 확률은 51%로 절반을 넘긴다.
// 배포 n 번 중 사고가 한 번 이상 날 확률
const atLeastOne = (p: number, n: number) => 1 - (1 - p) ** n;
atLeastOne(0.05, 1); // 0.05
atLeastOne(0.05, 14); // 0.512...
atLeastOne(0.05, 60); // 0.953...이 모델에는 요일이 들어갈 자리가 없다. 그런데도 금요일에 사고가 더 많이 나는 것처럼 보인다면, 이유는 가 아니라 쪽에 있을 가능성이 크다. 스프린트가 금요일에 끝나는 팀이라면, 스프린트 안에 끝내려는 작업들이 금요일에 몰려서 배포된다. 금요일의 배포 횟수 이 다른 요일보다 많으니, 버그가 요일을 가리지 않아도 사고는 금요일에 더 자주 난다.
게다가 금요일에 몰리는 배포는 보통 급하게 마무리된 작업들이다. 스프린트 리뷰 전에 어떻게든 끝내려고 테스트를 조금 덜 돌리고, 리뷰를 조금 덜 꼼꼼히 받은 변경들이다. 그렇다면 금요일 배포의 자체가 조금 높아질 수는 있다. 하지만 이것도 요일 탓이라기보다 마감 탓이라고 보는 편이 정확하다.
정확히 말하자면 이건 금요일이 위험하다는 증거가 아니라, 금요일에 배포가 몰린다는 증거에 가깝다. 같은 데이터로도 원인을 어디에 두느냐에 따라 전혀 다른 규칙이 나온다. (그리고 대부분의 팀은 더 기억하기 쉬운 쪽을 고른다)
같은 버그, 다른 피해
배포 횟수만으로는 설명이 안 되는 부분도 있다. 똑같은 버그가 화요일 오전에 터지면 커피 한 잔 식는 동안 해결되는데, 금요일 오후에 터지면 주말이 통째로 날아간다. 버그는 같은데 피해가 다르다.
일상에서 비슷한 경험을 찾자면 긴 여행을 떠나기 전의 집이 그렇다. 평소 출근할 때는 가스 밸브를 한 번 대충 보고 나가도 크게 불안하지 않다. 저녁이면 누군가 돌아와서 이상한 냄새를 맡을 테니까. 하지만 일주일짜리 여행을 떠나기 전에는 밸브를 두세 번씩 확인하게 된다. 밸브가 열려 있을 확률은 같은데, 아무도 알아차리지 못하는 시간이 길어지기 때문이다.
이 차이를 설명하려면 사고가 날 확률이 아니라, 사고가 났을 때의 피해를 계산해야 한다. 사고 한 번의 피해는 대략 "시간당 피해 × 사고가 지속된 시간"이고, 사고가 지속되는 시간은 다시 "알아차리기까지 걸린 시간 + 되돌리기까지 걸린 시간"으로 나눌 수 있다.
는 배포 한 번이 가져올 피해의 기대값이다. 는 앞에서 본 사고 확률이고, 는 사고가 났을 때 시간당 발생하는 피해다. 는 문제가 생긴 뒤 누군가 알아차리기까지 걸린 시간, 는 알아차린 뒤 원래대로 되돌리기까지 걸린 시간이다.
여기서 금요일이 정말로 다른 건 가 아니라 다. 금요일 오후 다섯 시에 배포한 코드에서 다섯 시 20분에 문제가 생겼다고 해보자. 모니터링 알림이 잘 갖춰져 있지 않다면, 이 문제는 월요일 아침 아홉 시 반에 누군가 출근해서 고객 문의를 확인할 때까지 아무도 모른다. 반면 화요일 오전 열한 시에 배포한 코드라면, 팀원 누군가가 40분 안에 이상한 그래프를 발견할 가능성이 크다.
두 경우의 피해를 같은 공식으로 계산해보면 차이가 확실히 드러난다.
// 사고 한 번의 피해, 시간당 피해 lambda 와 지속 시간의 곱
const damage = (lambda: number, detectHours: number, recoverHours: number) =>
lambda * (detectHours + recoverHours);
damage(1, 64.2, 0.5); // 금요일 배포, 약 64.7
damage(1, 0.7, 0.5); // 화요일 배포, 약 1.2같은 버그인데 피해는 50배 넘게 차이가 난다. 금요일 배포가 위험하다는 직관은 틀리지 않았다. 다만 그 위험은 버그가 더 잘 생겨서가 아니라, 버그를 지켜볼 사람이 사라지는 데서 온다.
주말의 람다는 서비스마다 다르다
공식에서 아직 이야기하지 않은 항이 하나 있다. 시간당 피해 다. 이 값은 서비스의 성격에 따라 주말에 커지기도 하고 작아지기도 한다.
배달이나 여행, 콘텐츠 서비스처럼 사람들이 쉬는 날 더 많이 쓰는 서비스라면 주말의 는 평일보다 크다. 금요일 저녁에 생긴 버그가 주문이 가장 몰리는 토요일 점심까지 이어진다면, 지속 시간도 길고 시간당 피해도 큰 최악의 조합이 된다. 반대로 회사 업무용 서비스라면 주말에는 사용자가 거의 없어서 가 작다. 대신 월요일 아침 아홉 시, 모두가 한꺼번에 접속하는 순간 쌓여 있던 문제가 한 번에 터진다.
그래서 같은 금요일 배포라도 어떤 팀에게는 정말로 위험하고, 어떤 팀에게는 생각보다 덜 위험하다. 필자는 금요일 배포를 두고 팀마다 의견이 갈리는 이유가 여기에 있다고 생각한다. 다들 자기 서비스의 를 머릿속에 그리면서 이야기하고 있었던 것이다.
기억은 금요일 편이다
여기에 한 가지 요인이 더 겹친다. 우리의 기억이다. 심리학자 아모스 트버스키(Amos Tversky)와 대니얼 카너먼(Daniel Kahneman)은 1973년, 사람들이 어떤 일의 빈도를 판단할 때 그 일의 사례가 얼마나 쉽게 떠오르는지에 기대는 경향이 있다는 가용성 휴리스틱을 발표했다. 쉽게 떠오르는 일일수록 실제보다 자주 일어난다고 느낀다는 것이다.
화요일 오전의 사고는 점심 메뉴를 고르기 전에 끝나고, 일주일만 지나도 기억에서 흐려진다. 반면 금요일 밤의 사고는 취소된 약속, 노트북을 펴야 했던 토요일 아침, 단체 대화방의 다급한 메시지와 함께 저장된다. 같은 사고 한 건이라도 금요일 사고는 훨씬 선명한 이야기가 되고, 선명한 이야기는 쉽게 떠오른다.
그 결과 우리는 금요일에 사고가 실제보다 훨씬 자주 난다고 믿게 된다. 그리고 그 믿음은 다음 금요일의 사고를 볼 때마다 강화된다. 화요일의 사고는 "그럴 수도 있지"로 넘어가고, 금요일의 사고는 "역시 금요일이야"로 기록되기 때문이다. 필자는 금요일 배포 금지라는 규칙의 상당 부분이 이 기억의 편향 위에 서 있다고 생각한다.
재미있는 건 비슷한 편향이 월요일에도 작동한다는 점이다. 월요일 아침에 터진 장애는 "역시 월요일이라 다들 정신이 없어서"라는 이야기와 함께 기억되고, 수요일 오후의 장애는 그냥 장애로 기억된다. 우리는 사건에 이야기를 붙이는 데 능숙하고, 요일은 가장 붙이기 쉬운 이야기 중 하나다. 그러니 요일별 장애 횟수가 정말 궁금하다면, 기억 대신 장애 기록을 요일별로 한 번 세어보자. 생각보다 평평한 그래프가 나올 수도 있다.
금요일 배포 금지의 역설
그렇다면 금요일 배포를 막으면 문제가 해결될까. 여기서 조금 재미있는 일이 생긴다. 금요일에 나갈 예정이던 변경들이 사라지는 게 아니라 목요일로 몰리는 것이다.
변경 하나가 사고로 이어질 확률을 3%라고 해보자. 변경 3개를 묶은 배포의 사고 확률은 약 8.7%이고, 변경 6개를 묶은 목요일 배포의 사고 확률은 약 16.7%가 된다. 그런데 한 주 전체로 보면 어떨까? 주당 변경이 15개로 같다면, 한 주 안에 사고가 한 번 이상 날 확률은 어느 쪽이든 약 37%로 똑같다.
// 변경 하나의 사고 확률 q, 한 배포에 묶인 변경 k 개
const deployRisk = (q: number, k: number) => 1 - (1 - q) ** k;
deployRisk(0.03, 3); // 0.087...
deployRisk(0.03, 6); // 0.167...
deployRisk(0.03, 15); // 0.366..., 한 주 전체는 묶는 방식과 무관즉, 금요일 배포를 막아도 한 주의 사고 확률은 줄지 않는다. 대신 달라지는 게 하나 있다. 사고가 났을 때 원인을 찾는 시간이다. 변경 3개가 묶인 배포에서 문제가 생기면 범인 후보는 셋이지만, 변경 6개가 묶인 배포에서는 범인 후보가 여섯이 된다. 변경을 반씩 나눠 되돌려보며 범인을 찾는다면 필요한 시도 횟수는 대략 로 늘어나고, 되돌리는 범위도 넓어진다. 앞의 공식으로 말하면 가 커지는 것이다.
| 묶인 변경 수 | 범인을 찾는 데 필요한 되돌리기 횟수 |
|---|---|
| 1 | 0회, 되돌리면 끝 |
| 3 | 약 1.6회 |
| 6 | 약 2.6회 |
| 15 | 약 3.9회 |
시도 한 번이 배포 한 번이고, 배포 한 번에 10분씩 걸린다고 해보자. 변경 하나만 내보낸 배포는 문제가 생기면 그냥 되돌리면 끝이지만, 15개를 묶은 배포는 범인을 찾는 데만 40분 가까이 걸린다. 그 40분 동안에도 피해는 시간당 씩 계속 쌓인다.
배포를 덜 하면 생기는 문제가 하나 더 있다. 배포라는 행위 자체가 어색해진다는 것이다. 하루에도 몇 번씩 배포하는 팀에게 배포는 커밋만큼이나 평범한 일이라서, 롤백 절차도 손에 익어 있다. 반면 일주일에 한 번, 목요일에만 배포하는 팀에게 배포는 긴장되는 행사가 되고, 롤백은 1년에 몇 번 써보지 않은 낯선 절차가 된다. 근육은 쓰지 않으면 줄어드는데, 배포 근육도 예외는 아니다.
그래서 금요일 배포 금지는 의도와 달리 피해 공식의 한쪽 항을 키우는 결과를 낳을 수 있다. 데브옵스 성과를 연구해온 DORA 프로그램과 니콜 포스그렌(Nicole Forsgren) 등이 쓴 『Accelerate』(2018)는 배포 빈도, 변경 리드 타임, 변경 실패율, 복구 시간을 함께 보는 네 가지 지표를 제안했는데, 이 연구에서 성과가 좋은 팀들은 배포를 자주 하면서도 변경 실패율이 낮고 복구도 빠른 경향을 보였다. 작게 자주 내보내는 쪽이 오히려 안전하다는 이야기다.
격언이 태어난 시절
사실 금요일 배포 금지라는 격언이 처음 생겼을 때는 지금보다 훨씬 합리적인 규칙이었을 가능성이 크다. 배포가 서버에 직접 접속해서 파일을 하나씩 올리는 일이었고, 롤백은 백업해둔 파일을 다시 덮어쓰는 수작업이었던 시절을 떠올려보자. 그 시절의 는 분 단위가 아니라 시간 단위였고, 모니터링 대시보드도 지금처럼 흔하지 않았으니 도 길었다.
이런 환경에서 배포는 하루를 통째로 비워두고 치르는 행사였다. 문제가 생기면 다음 날까지 붙잡고 있어야 하니, 다음 날이 주말인 금요일에 배포하지 않는 건 아주 상식적인 선택이었다. 격언은 그 시절의 피해 공식을 정확하게 반영하고 있었던 셈이다.
그 뒤로 배포 방식은 꽤 많이 바뀌었다. 제즈 험블(Jez Humble)과 데이비드 팔리(David Farley)가 2010년 『지속적 배포』(Continuous Delivery)에서 정리한 것처럼, 배포를 자동화하고 작게 자주 내보내는 방식이 널리 퍼졌다. 버튼 하나로 이전 버전으로 돌아가는 일이 흔해졌고, 오류율이 튀면 몇 분 안에 알림이 오는 것도 당연해졌다. 피해 공식의 두 시간 항이 줄어든 만큼, 요일이 차지하는 비중도 함께 줄어든 것이다.
필자는 오래된 격언을 무작정 버리자는 쪽은 아니다. 다만 격언이 왜 생겼는지를 알면, 지금 우리 팀에 그 격언이 아직 유효한지를 스스로 판단할 수 있게 된다. 격언을 지키는 이유가 "원래 그래왔으니까"라면, 그건 규칙이라기보다 습관에 가깝다.
그래서 만능일까
여기까지 읽고 나면 이런 반론이 나올 수 있다.
그래서 금요일에도 마음껏 배포하라는 말인가? 주말에 장애가 나면 누가 책임지나?
맞다. 사실 이 반론은 꽤 정당하다. 위의 모든 계산은 문제가 생겼을 때 빠르게 알아차리고 빠르게 되돌릴 수 있다는 전제 위에 서 있다. 그 전제가 없는 팀이라면 금요일 배포 금지는 여전히 합리적인 규칙이다.
알림이 없어서 사용자가 신고해야 문제를 알 수 있는 팀, 되돌리는 데 한 시간 넘게 걸리는 팀, 주말에 대응할 사람이 정해져 있지 않은 팀을 떠올려보자. 이 팀에게 와 는 금요일 저녁부터 월요일 아침까지 늘어난다. 그렇다면 금요일 배포를 막는 건 피해 공식의 가장 큰 항을 직접 줄이는 정직한 선택이다.
그러니 금요일 배포 금지는 틀린 규칙이라기보다, 더 근본적인 규칙의 대리인에 가깝다. 진짜 규칙은 "지켜볼 수 없고 되돌릴 수 없을 때는 배포하지 말라"이고, 금요일은 그 조건이 가장 자주 성립하는 요일일 뿐이다. 대형 출시나 결제처럼 피해 가 큰 변경이라면, 요일과 상관없이 사람이 지켜볼 수 있는 시간을 골라 배포하는 게 맞다.
요일 대신 물어야 할 것
그렇다면 배포 버튼을 누르기 전에 무엇을 물어보면 좋을까. 피해 공식의 각 항을 하나씩 질문으로 바꿔보면 꽤 쓸 만한 점검표가 된다.
먼저 에 대한 질문이다. 이번 배포에는 변경이 몇 개나 묶여 있는가. 묶인 변경이 많다면, 요일과 상관없이 쪼개서 내보내는 편이 낫다.
다음은 에 대한 질문이다. 배포 후 몇 분 안에 문제를 알 수 있는가. 오류율, 응답 시간, 핵심 기능의 성공률 같은 지표에 알림이 걸려 있다면 이 시간은 분 단위로 줄어든다. 알림이 없다면, 우리는 사용자가 신고해줄 때까지 기다리는 수밖에 없다.
여기에 를 줄이는 방법도 하나 더할 수 있다. 새 버전을 처음부터 모든 사용자에게 내보내지 않고, 1%나 5%의 사용자에게만 먼저 내보내는 카나리 배포다. 새 버전을 받는 사용자의 비율을 라고 하면, 문제가 생겼을 때의 시간당 피해는 대략 로 줄어든다. 탄광의 카나리아가 광부보다 먼저 유독 가스에 반응했던 것처럼, 소수의 사용자에게서 먼저 이상 신호를 보고 전체 배포를 멈출 수 있는 것이다.
마지막은 에 대한 질문이다. 문제를 알았을 때 몇 분 안에 되돌릴 수 있는가. 이전 버전으로 한 번에 돌아가는 롤백 경로가 있는지, 새 기능을 배포와 분리해 끄고 켤 수 있는 기능 플래그가 있는지가 이 시간을 결정한다.
// 배포 전 점검, 세 질문 중 하나라도 아니오면 요일과 무관하게 보류
type DeployCheck = {
changes: number; // 묶인 변경 수
alertMinutes: number; // 이상을 알아차리는 데 걸리는 시간
rollbackMinutes: number; // 되돌리는 데 걸리는 시간
};
const canDeploy = ({ changes, alertMinutes, rollbackMinutes }: DeployCheck) =>
changes <= 3 && alertMinutes <= 10 && rollbackMinutes <= 10;물론 위 숫자는 팀마다 다르게 정해야 하는 예시일 뿐이다. 다만 이 세 질문에 자신 있게 "그렇다"고 답할 수 있는 팀이라면, 금요일 오후 네 시의 배포는 화요일 오전의 배포와 크게 다르지 않다. 반대로 하나라도 자신이 없다면, 화요일 오전의 배포도 생각보다 위험하다.
마치며
처음 이야기로 돌아가 보자. 금요일 오후 다섯 시의 배포가 토요일 아침의 알림 폭탄이 된 이유는 금요일이 저주받은 요일이어서가 아니었다. 그 시간에 아무도 지켜보고 있지 않았고, 월요일이 되기 전까지는 되돌릴 사람도 없었기 때문이다. 그리고 그 기억이 너무 선명해서, 우리는 원인을 요일에 돌렸다.
그래서 필자는 "금요일에 배포해도 될까"라는 질문이 사실 "지금 배포한 걸 지금 되돌릴 수 있을까"라는 질문에 더 가깝다고 생각한다. 요일은 그 질문의 답을 대신 알려주는 신호였을 뿐, 질문 그 자체는 아니었던 것이다.
다음에 금요일 배포를 두고 의견이 갈린다면, 요일을 놓고 다투기 전에 알림과 롤백 경로부터 함께 확인해보자. 그 두 가지가 갖춰진 팀에게 금요일은 그냥 주말 전날일 뿐이다.
이상으로 버그는 왜 금요일 오후에 터질까 포스팅을 마친다.