테스트는 사고 전에 죽어야 한다

공유
테스트는 사고 전에 죽어야 한다

탄광 카나리아에서 온 말이 있다. 광부들은 사람이 유독가스를 알아차리기 전에 새가 먼저 반응하기를 바라며 카나리아를 갱도 안으로 데려갔다. 새가 쓰러지면 아직 사람이 쓰러지지 않았다는 뜻이 아니라, 이제 곧 사람이 쓰러질 수 있다는 뜻이었다.

그래서 카나리아는 테스트용 신호의 오래된 이름이 되었다. 무언가가 실제 사고로 커지기 전에, 더 작고 더 민감한 대상이 먼저 이상을 드러내는 장치다.

다만 이 비유를 소프트웨어에 옮길 때는 한 가지를 정확히 해야 한다. 카나리아는 테스트 그 자체가 아니다. 카나리아는 전체 시스템에 변경을 적용하기 전에, 일부 운영 트래픽에 먼저 노출하는 방법이다. 테스트가 닫힌 환경에서 조건을 확인한다면, 카나리아는 아직 예측하지 못한 현실을 제한된 범위에서 만나게 한다.

카나리아는 정말 죽었을까

역사 속 카나리아는 단순히 희생되는 새였다는 이야기보다 조금 복잡하다. 앨버타 정부의 석탄산업 역사 자료는 카나리아의 작은 몸, 빠른 호흡, 높은 대사율이 특히 일산화탄소에 민감하게 반응하게 만들었다고 설명한다. 새가 횃대에서 떨어지면 광부들은 위험을 알아차리고 대피하거나 호흡기를 준비할 시간을 얻었다.

Science and Industry Museum의 자료에 따르면 이 방법은 19세기 말부터 쓰였고, John Haldane이 1896년 광산 폭발의 원인을 조사한 뒤 일산화탄소를 사람이 다치기 전에 감지할 방법을 찾으면서 체계화됐다. 산소통이 달린 우리도 있었다. 카나리아가 중독 증상을 보이면 우리를 닫고 산소를 공급해 되살리는 장치였다.

그러니 “죽어서 알려주는 새”라는 표현은 강력하지만 완전히 정확하지는 않다. 더 정확한 표현은 이렇다. 카나리아는 사람이 위험해지기 전에 먼저 상태가 망가지는, 작고 민감한 조기경보 장치였다.

소프트웨어의 카나리아는 테스트가 아니다

Google SRE의 Canarying Releases는 카나리잉을 변경의 일부이자 시간 제한적인 배포와 그 평가로 정의한다. 새 버전을 전체 운영 환경에 한 번에 배포하는 대신, 작은 사용자 집단이나 트래픽 일부에 먼저 보내고 기존 버전과 비교한다. 나쁘면 멈추거나 되돌리고, 괜찮으면 더 넓은 범위로 확장한다.

Google의 SRE 책은 이 구분을 더 노골적으로 적는다. 카나리 테스트는 실제로는 테스트가 아니다. 정해진 입력에 대해 결정론적인 조건을 확인하는 단위 테스트나 부하 테스트와 달리, 카나리는 예측하기 어려운 실제 운영 트래픽을 일부 받아보는 구조화된 사용자 수용에 가깝다.

이 차이는 중요하다. 테스트가 초록이라는 것은 특정 조건을 통과했다는 뜻이다. 카나리가 초록이라는 것은 아직 작은 현실 노출에서 큰 이상을 발견하지 못했다는 뜻이다. 둘은 서로 다른 종류의 증거를 만든다.

좋은 테스트는 먼저 실패해야 한다

테스트를 모두 통과시키는 것이 목적이라고 생각하면 빨간 테스트는 제거해야 할 장애물처럼 보인다. 그러나 테스트의 진짜 역할은 안심을 생산하는 것이 아니라, 안심할 수 없는 상태를 가능한 한 싸게 드러내는 것이다.

좋은 테스트는 개발자의 컴퓨터에서 실패한다. 배포가 끝난 뒤가 아니라, 변경사항이 아직 작고, 원인을 좁힐 수 있고, 되돌리는 비용이 낮을 때 실패한다. 실패가 빠를수록 테스트는 더 유용하다. 실패가 코드가 사용자에게 닿은 뒤에야 나타난다면 그것은 테스트의 실패라기보다 검증 경계가 너무 늦었다는 신호다.

그렇다고 모든 문제를 테스트가 미리 잡을 수 있다는 뜻은 아니다. 테스트 환경은 운영 환경과 다르고, 테스트가 다루지 않은 입력과 상태와 순서가 언제나 남는다. 그래서 테스트가 초록인 상태에서도 카나리아가 필요하다. 카나리아는 테스트가 놓친 현실의 조각을, 전체 사용자에게 노출하기 전에 가져온다.

테스트와 카나리아는 경쟁 관계가 아니다. 테스트는 변경을 작은 조건으로 쪼개어 확인하고, 카나리아는 변경을 작은 현실로 쪼개어 확인한다. 테스트가 코드의 결함을 먼저 죽인다면, 카나리아는 운영에서만 드러나는 결함을 먼저 죽인다.

여기서 “죽는다”는 말은 시스템을 망가뜨린다는 뜻이 아니다. 빨간 신호가 나왔을 때 전체 확산을 멈출 수 있다는 뜻이다. 실패가 사용자 전체의 장애가 되기 전에, 아직 되돌릴 수 있는 범위에서 관측되어야 한다.

그렇다면 작은 실패가 실제로 안전을 만들어내려면 무엇이 필요할까?

먼저 실패의 범위가 작아야 한다. Google SRE가 설명하듯 카나리아 트래픽이 전체의 작은 비율이면 전체 오류율에서는 문제가 희미하게 보일 수 있다. 그래서 카나리아와 기존 버전의 신호를 분리해서 비교해야 한다. 전체 평균이 초록이라고 카나리아가 건강하다고 말할 수는 없다.

다음으로 무엇을 보면 중단할지 미리 정해야 한다. HTTP 오류율, 지연시간, 특정 기능의 성공률, 데이터 무결성, 비용 급증처럼 시스템의 위험을 드러내는 신호가 있어야 한다. “이상하면 멈춘다”는 문장만으로는 부족하다. 어떤 차이를 이상으로 볼지, 얼마 동안 관찰할지, 누가 확산을 멈출지까지 운영 흐름에 들어 있어야 한다.

마지막으로 되돌릴 수 있어야 한다. Martin Fowler가 설명하는 카나리 릴리스의 핵심은 느린 배포 자체가 아니라, 문제가 생기면 사용자를 이전 버전으로 다시 보낼 수 있다는 점이다. 롤백이 없거나 데이터 변경이 이미 공유 상태를 오염시킨다면, 작은 트래픽에 보냈다는 이유만으로 안전해지지 않는다.

카나리아가 죽지 않는 이유

카나리아가 항상 위험을 알려주는 것은 아니다. 첫째, 관측 신호가 잘못되면 카나리아는 살아 있는 것처럼 보인다. 전체 서비스의 오류율만 보면 1%의 카나리아 오류가 사라질 수 있다. 버전별·집단별로 신호를 나누지 않으면, 카나리아는 죽었는데 대시보드만 살아 있을 수 있다.

둘째, 카나리아가 실제 운영을 충분히 만나지 못할 수 있다. 사용자의 특정 지역, 계정 상태, 장바구니 조합, 결제 흐름, 데이터 크기에서만 발생하는 결함은 작은 트래픽에 우연히 포함되지 않을 수 있다. 카나리아는 확률을 낮추는 장치이지, 모든 경로를 통과했다는 증명서가 아니다.

셋째, 상태를 공유하는 시스템에서는 카나리아가 다른 사용자에게 영향을 줄 수 있다. Google SRE도 인공 트래픽과 트래픽 복제만으로는 캐시, 쿠키, 요청 어피니티, 변경 가능한 데이터의 위험을 충분히 재현하기 어렵다고 지적한다. 읽기 전용 경로에서 먼저 확인하거나, 부작용을 분리하거나, 되돌릴 수 없는 작업을 카나리아에서 막아야 하는 이유다.

그래서 카나리아는 작은 배포 비율만으로 완성되지 않는다. 작은 노출, 비교 가능한 기준선, 실패를 알아차릴 신호, 자동 또는 명시적인 중단, 빠른 롤백이 한 묶음이어야 한다. 이 중 하나라도 빠지면 카나리아는 조기경보 장치가 아니라 작은 규모의 장애가 된다.

테스트가 카나리아가 되는 순간

테스트를 카나리아처럼 설계한다는 말은 테스트를 일부러 불안정하게 만들자는 뜻이 아니다. 실패가 나왔을 때 누가 무엇을 알 수 있는지를 설계하자는 뜻이다.

커밋 단계의 테스트는 빠르게 실패하고 원인을 좁혀야 한다. 통합 단계의 테스트는 여러 경계가 합쳐졌을 때 생기는 문제를 찾아야 한다. 배포 직전의 카나리아는 실제 입력과 실제 시간과 실제 운영 제약을 조금 받아야 한다. 각 단계가 같은 초록불을 내는 것이 아니라, 다음 위험으로 넘어갈 자격을 서로 다른 방식으로 증명해야 한다.

Kubernetes의 공식 문서가 안정 버전과 카나리 버전을 나란히 두고 replica 비율로 트래픽을 조절하는 예시를 보여주는 것도 같은 이유다. 카나리는 영구적인 별도 제품이 아니다. 기존 버전과 비교되는 임시 상태이며, 충분히 확인되면 안정 트랙으로 승격되고 제거된다.

이 관점에서 테스트의 빨간불은 나쁜 소식이 아니다. 너무 늦게 켜지는 빨간불이 나쁜 소식이다. 개발 중에 실패하는 테스트, 일부 트래픽에서 실패하는 카나리, 전체 확산 전에 멈추는 배포는 모두 같은 운영 철학을 공유한다. 사고의 크기를 키우기 전에 실패의 크기를 줄이는 것이다.

탄광의 카나리아는 사람을 대신해 위험을 겪었다. 소프트웨어의 카나리아는 사용자를 대신해 변경을 조금 먼저 겪는다. 우리는 더 이상 새가 쓰러지기를 기다릴 필요는 없지만, 시스템이 조용히 초록색을 유지하는 것만으로 안전하다고 믿어서도 안 된다.

좋은 테스트는 사고가 난 뒤 성공을 증명하지 않는다. 사람이 알아차리기 전에 먼저 실패하고, 실패가 아직 작을 때 우리에게 돌아갈 길을 알려준다.

참고: Alberta’s Energy Heritage — Canaries in the Coal Mine; Science and Industry Museum — The canary resuscitator; Google SRE Workbook — Canarying Releases; Google SRE Book — Testing Reliability; Martin Fowler — Canary Release; Kubernetes — Canary deployments.