동시성 버그를 잡으려고 밤새 로그를 파던 날
가끔 한 번씩, 정말 가끔 한 번씩만 재고 처리가 꼬이는 버그가 몇 주째 재현이 안 돼서 애를 먹고 있었습니다. 오늘은 로그를 밤새 파보기로 했습니다.
동료가 얼마 전 커밋에서 뭔가를 설치해둔 흔적을 발견하고, 왜 로그 형식이 갑자기 달라졌는지 이해가 됐습니다.
도구 기능이 갑자기 하나 늘어나 있을 때, 플러그인
text
1Loaded plugin: log-formatter-pretty
동료의 지난 커밋을 살펴보니, 로그를 더 읽기 좋게 정리해주는 확장 기능 하나를 설치해뒀습니다. 도구 자체의 핵심 기능은 그대로 둔 채, 필요한 기능만 따로 만들어서 나중에 끼워 넣을 수 있게 해주는 방식이었습니다. 본체는 그대로인데 필요한 부품만 갈아 끼우는 것과 비슷했습니다. 덕분에 로그가 훨씬 보기 편해졌습니다.
로그를 더 정리해서 보다가, 에이전트가 뭔가를 실행하기 직전마다 특정 파일을 참고한다는 메시지를 스스로 띄우는 걸 발견했습니다.
에이전트가 특정 방식으로만 일을 처리할 때, 스킬 파일
text
1Using skill: systematic-debugging
화면에 뜬 메시지를 보고 알게 됐습니다. 특정 상황에서 어떤 순서로, 어떤 방식으로 문제를 풀어야 하는지 미리 정리해둔 절차 문서를, 필요할 때마다 불러와서 그대로 따르게 하는 방식이었습니다. 요리사가 상황별 레시피 카드를 미리 만들어두고 그때그때 꺼내 보는 방식이었습니다. 지금은 동시성 버그를 체계적으로 좁혀가는 절차를 따르고 있었습니다.
절차를 따라가다가 파일을 하나씩 열어보는데, 예전에 제가 설정해둔 옵션 하나가 눈에 들어왔습니다.
승인 창이 하나도 안 뜨고 있었던 이유, 자동 승인 모드
json
1{ "autoApprove": ["Read", "Grep", "Bash(git *)"] }
몇 달 전, 매번 읽기 명령까지 일일이 승인하는 게 번거로워서 이렇게 설정해뒀던 걸 까맣게 잊고 있었습니다. 위험이 낮다고 판단한 특정 종류의 작업은, 매번 묻지 않고 바로 실행되도록 미리 허락해두는 설정이었습니다. 덕분에 오늘 같은 밤에도 로그를 읽고 검색하는 과정이 끊기지 않고 이어질 수 있었습니다.
드디어 로그 한가운데서 의심스러운 지점을 찾았습니다. 두 개의 처리 과정이 정확히 같은 순간 멈춰 있었습니다. 비슷한 멈춤이 로그 여기저기서 반복되고 있었습니다.
두 작업이 서로를 기다리며 영원히 멈출 때, 데드락
text
1Thread-A: waiting for lock(inventory)2Thread-B: waiting for lock(order)3Thread-A holds lock(order), Thread-B holds lock(inventory)
같은 패턴이 로그에 몇 번이나 반복되는 걸 보고 나서야 원인을 확신했습니다. 한쪽이 다른 쪽이 쥔 자원을 기다리고, 그 다른 쪽은 또 첫 번째 쪽이 쥔 자원을 기다리면서, 서로 양보 없이 영원히 멈춰버리는 상태였습니다. 외나무다리에서 마주친 두 사람이 서로 비켜주기만 기다리다 둘 다 그 자리에 멈춰버린 것과 같았습니다.
왜 이 버그가 몇 주에 한 번씩만 재현되는지도 그제야 검색해서 알아냈습니다.
어떤 때는 되고 어떤 때는 안 될 때, 레이스 컨디션
같은 코드인데 왜 항상 걸리는 게 아니라 가끔만 걸리는지 궁금해서 찾아봤습니다. 두 작업이 자원에 접근하는 순서가 매번 정확히 똑같지 않다는 게 핵심이었습니다.
두 작업이 아주 근소한 타이밍 차이로 자원에 먼저 도달하는 쪽이 매번 달라지고, 하필 특정 순서로 겹칠 때만 문제가 드러나는 현상이었습니다. 같은 신호에 출발해도 100분의 1초 차이로 순위가 갈리는 달리기처럼, 매번 먼저 닿는 쪽이 바뀌었습니다. 트래픽이 몰리는 특정 순간에만 두 작업이 그 타이밍으로 겹치다 보니, 재현이 그만큼 어려웠습니다.

원인을 확실히 잡고 나니 창밖이 이미 밝아 있었습니다. 락을 거는 순서를 통일하는 수정 하나로 데드락과 레이스 컨디션을 동시에 해결할 수 있었습니다.

자주 묻는 질문
자동 승인 모드는 위험하지 않나요?
읽기 전용 작업처럼 되돌릴 필요가 없는 종류만 허용해두면 큰 위험은 없습니다. 다만 삭제나 배포처럼 되돌리기 어려운 작업까지 자동 승인에 넣는 건 권장하지 않습니다.
데드락은 왜 미리 걸러내기 어려운가요?
코드 자체는 각각 정상이라 문법 검사로는 안 걸러집니다. 실제로 여러 작업이 동시에 실행되는 상황에서만 드러나기 때문에, 실행 흐름을 재현하는 테스트가 따로 필요합니다.
레이스 컨디션은 테스트로 항상 잡아낼 수 있나요?
타이밍에 의존하는 문제라 일반 테스트로는 재현이 잘 안 되는 경우가 많습니다. 동시 요청을 대량으로 발생시키는 부하 테스트나, 의도적으로 타이밍을 흔드는 전용 도구를 쓰는 게 더 효과적입니다.
<a href="/glossary/code-generation" class="glossary-link" title="AI가 자연어 설명이나 기존 코드 문맥을 바탕으로 프로그래밍 코드를 자동으로 작성하는 기술입니다.">AI 코딩</a> 도구 용어 사전 연작의 13편입니다. 다음 편 예고 · 14편 연말 보안 감사를 준비하며 한 해를 되짚어보던 날 감사로그 · 플랜 모드 · 헤드리스 모드 · 세마포어 · 코어 덤프