날짜가 가끔 안 바뀐다
이날은 간헐적 버그의 진짜 원인을 찾은 날이다.
그리고 두 달 전의 변경이 뒤늦게 문제를 일으킨 사건도 잡았다.
둘 다
“왜 가끔만 발생하지”에서 시작해서 명확한 원인까지 갔다.
교육 플랫폼에서 테스트 편집 화면에 버그가 있었다.
테스트 유형을 바꾸면 날짜 입력이 리셋되어야 하는데, 가끔 안 됐다.
항상 안 되는 게 아니라 가끔이다.
재현이 어려웠다.
원인: setup 한 번 값이 watchEffect를 영구 막음
원인은 Vue의 setup 단계에서 계산된 값이었다.
initialHasRestrictedDate라는 값이 setup에서 한 번 계산되고 이후에 다시 계산되지 않는다.
이 값이 watchEffect의 가드로 쓰인다.
이 값이 true면 watchEffect가 early return한다.
근데 한 번 true가 되면, 세션 동안 영원히 true다.
그러니까 watchEffect가 영원히 안 돈다.
리셋이 안 일어나는 거다.
왜 간헐적인가
왜 항상 안 되는 게 아니라 가끔인가. setup 값이 초기 로드된 데이터에 의존하기 때문이다.
오늘이나 과거 날짜가 있는 테스트를 열면 유예가 활성화된다.
그러면 버그가 발생한다.
다른 날짜가 있으면 유예가 안 활성화된다.
정상 동작한다.
그러니까 어떤 테스트를 열었느냐에 따라 버그가 나올 수도 안 나올 수도 있다.
이게 “간헐적”으로 보이는 이유다.
해결
해결은 “유예가 한 번 무효화되면 영구 무효”로 만드는 거다.
유형이나 날짜가 초기 값이랑 달라지면 유예를 영구적으로 해제한다.
그러면 정상적으로 제한이 걸리고 리셋이 돈다.
그리고 watchEffect를 명시적 watch로 바꿨다.
암시적 의존성 추적은 가끔 의존성을 놓친다.
명시적으로 의존성을 나열하면 놓칠 일이 없다.
범용 교훈
이건 Vue에만 해당하는 게 아니다.
“한 번 계산된 값이 이후 변경되지 않으면서 동시에 반응형 로직의 가드로 쓰이면” 간헐적 버그가 생긴다.
초기 데이터에 따라 값이 달라지니까, 일관되게 나오지 않는다.
간헐적 버그는 가장 잡기 어렵다.
항상 발생하면 원인을 찾기 쉽다.
근데 가끔 발생하면 “내가 잘못 본 건가”라고 의심하게 된다.
간헐적인 원인이 “초기 상태에 의존하는 고정 값”인 경우가 많다.
두 달 전의 변경이 뒤늦게 터졌다
QA 에이전트에서 모델을 업그레이드했다.
모델을 바꾸려면 도커 이미지를 다시 빌드해야 한다.
이미지를 빌드하니까 노션 MCP가 멈췄다.
원인을 추적하니, 두 달 전의 변경이 원인이었다.
4월에 MCP 전송 방식을 바꿨다.
이 변경이 위험을 도입했다.
근데 바로 터지지 않았다.
캐시가 있어서 정상 동작했다.
이미지를 다시 빌드하면서 캐시가 날아갔다.
캐시가 없으니까, 처음 실행할 때 npx가 패키지를 다운로드한다.
이 과정에서 문제가 생겼다.
잠재 위험의 점화
이게 핵심이다.
변경은 4월에 했다.
근데 문제는 6월에 터졌다.
두 달 동안 캐시가 문제를 숨기고 있었다.
“잘 되다가 갑자기 안 된다”고 해서 최근 변경을 원인으로 보면 틀린다.
원인은 두 달 전에 있었다.
근데 캐시가 숨기고 있었다.
캐시가 사라지면서 비로소 문제가 드러난 거다.
”같은 증상, 다른 원인” 함정
이 문제를 해결하는 과정에서 함정이 또 있었다.
첫 번째 수정 후에 같은 증상이 재발했다.
그래서 “수정이 안 됐나”라고 생각했다.
근데 원인이 달랐다.
첫 번째 원인은 npx가 패키지를 다운로드하면서 발생하는 문제였다.
두 번째 원인은 opencode가 작업 디렉토리를 올라가면서 패키지를 검사하다가 pnpm 심볼릭 링크에서 크래시가 나는 거였다.
같은 증상인데 원인이 다르다.
첫 번째 수정이 맞았지만 두 번째 원인이 별도로 존재했다.
“같은 증상이니까 같은 원인”이라고 가정하면 두 번째 원인을 놓친다.
마무리
이날의 교훈은 두 가지다.
첫째, 간헐적 버그의 원인은 “초기 상태에 의존하는 고정 값”일 수 있다.
항상 발생하지 않는 이유는 초기 상태가 매번 다르기 때문이다.
간헐적이면 “초기 상태에 따라 달라지는 값이 있나”를 확인해야 한다.
둘째, “잘 되다가 갑자기 안 된다”고 해서 최근 변경을 원인으로 보면 틀릴 수 있다.
잠재 위험이 캐시에 숨어 있다가, 캐시가 사라지면 점화될 수 있다.
“언제부터 안 됐나”보다 “무엇이 바뀌었나”를 더 깊이 파야 한다.
그리고 “같은 증상이 다른 원인”일 수 있다.
첫 번째 수정이 맞아도, 같은 증상의 두 번째 원인이 별도로 존재할 수 있다.
수정 후에 같은 증상이 재발하면, 원인이 바뀌었을 가능성을 확인해야 한다.