Rain Lag

종이 프로토타입 디버거: 손으로 그린 타임라인으로 레이스 컨디션을 배포 전에 잡아내기

단순한 손그림 타임라인만으로도 코드를 쓰거나 배포하기 훨씬 전에 레이스 컨디션, 데드락, 비동기 버그를 찾아낼 수 있는 방법을 소개합니다.

종이 프로토타입 디버거: 손으로 그린 타임라인으로 레이스 컨디션을 배포 전에 잡아내기

팀이 비동기적인 무언가를 만든다면—백그라운드 잡, 마이크로서비스, 실시간 협업 기능, 리액티브 UI 등—이미 한 번쯤은 레이스 컨디션이 섞인 코드를 배포했을 가능성이 높습니다. 이런 버그는 보통 프로덕션, 그것도 부하가 걸릴 때, 일주일에 한 번쯤… 그러다 가장 중요한 데모 날에 꼭 모습을 드러립니다.

대부분의 팀은 코드가 만들어진 뒤에야 로그, 테스트, 프로파일러로 이런 문제를 잡으려고 합니다. 하지만 그 시점에는 설계가 이미 굳어 있고, 수정 비용이 크며, 디버깅 사이클도 고통스럽습니다.

이런 버그와 싸우기 훨씬 싼 타이밍이 있습니다. 바로 코드를 쓰기 전, 종이와 펜, 그리고 몇 분의 집중된 생각만으로도 가능합니다.

이게 바로 종이 프로토타입 디버거의 아이디어입니다. 손으로 그린 타임라인으로 동시 실행 상황을 시뮬레이션해, 레이스 컨디션을 초기에 드러나게 만드는 것이죠.


동시성 버그가 그렇게 어려운 이유

레이스 컨디션, 데드락, 비동기 글리치(async glitch)는 악명 높게 까다로운 버그입니다.

  • 타이밍에 의존합니다. 두 연산이 특정 순서로 일어날 때만 나타나는 버그가 많습니다.
  • 재현이 거의 안정적이지 않습니다. 로그를 넣거나 브레이크포인트를 걸면 타이밍이 바뀌어 버그가 숨기도 합니다.
  • 정신적인 모델이 눈에 보이지 않습니다. 실제 read/write, lock, 메시지 순서는 여러 추상화 레이어에 가려져 코드에서 잘 드러나지 않습니다.

디버거, 프로파일러 같은 도구는 유용하지만, 모두 이미 작성된 코드 위에서만 동작합니다. 그 시점에는 API, 데이터 플로우, 락 구조 같은 설계 결정이 이미 굳어 있어 바꾸기가 어렵습니다.

종이는 정반대입니다. 싸고, 언제든 버릴 수 있고, 어떤 프레임워크나 언어에도 얽매이지 않습니다. 구현이 아니라 설계 자체를 디버깅하기에 딱 좋은 매체입니다.


종이 프로토타입 디버거란 무엇인가?

종이 프로토타입 디버거는 어떤 툴이나 제품이 아닙니다. 하나의 **실천 방식(practice)**입니다.

종이(또는 화이트보드)에 손으로 빠르게 타임라인을 그려, 동시 실행되는 동작을 한 단계씩 시뮬레이션하고, 코드를 쓰기 전에 타이밍 관련 버그를 드러나게 한다.

핵심 흐름은 다음과 같습니다.

  1. 서로 동시적으로 움직이는 컴포넌트(스레드, 서비스, 워커, UI + 백엔드 등)의 타임라인을 그립니다.
  2. 이벤트를 표시합니다: read, write, lock, 메시지, 콜백, 상태 변화 등.
  3. 현실적인 시나리오를 단계별로 따라가며 실행 과정을 시뮬레이션합니다.
  4. 나쁜 interleaving을 찾습니다: 레이스, 데드락, 놓친 신호, 불일치 상태 등.

특별한 표기법은 필요 없습니다. 박스, 화살표, 낙서 수준의 선, 그리고 몇 개의 기호면 충분합니다.


손으로 그린 타임라인이 잘 통하는 이유

1. 코드를 쓰기 전에 레이스 컨디션을 드러낸다

동시성을 가지는 시스템을 설계할 때, 이미 머릿속에는 어떤 암묵적인 모델이 있습니다.

  • “이 워커는 잡이 큐에 들어가면 그걸 가져간다.”
  • “다음 요청이 들어오기 전에 캐시가 무효화된다.”
  • “락은 항상 write 뒤에 풀린다.”

종이에 그리면 이런 정신적 모델을 강제로 외부로 꺼내게 됩니다. 그리고 이렇게 묻게 됩니다.

  • 이 두 가지가 반대 순서로 일어나면 어떻게 될까?
  • 이 메시지가 지연되면?
  • 타임아웃이 응답보다 먼저 발생하면?

이런 순서를 눈으로 보면서 탐색하다 보면 다음과 같은 문제를 잡게 됩니다.

  • write 이전에 발생하는 read
  • 공유 상태를 둘 이상의 writer가 동시에 갱신하는 상황
  • listener가 붙기 전에 signal/event가 먼저 보내지는 상황

종이 위에서 이상해 보이면, 프로덕션에서는 훨씬 더 나쁩니다.

2. 복잡한 비동기 동작이 훨씬 이해하기 쉬워진다

콜백, Promise, 메시지 큐, 여러 서비스가 얽히기 시작하는 순간, 코드는 더 이상 위에서 아래로 순차적으로 읽히지 않습니다. 컨트롤 플로우가 컴포넌트와 스레드 사이를 튀어 다닙니다.

손으로 그린 타임라인은 여러 액터에 걸친 일을 다시 선형적인 이야기로 돌려줍니다.

  • 각 액터마다 가로 줄을 하나씩 그립니다.
  • 시간은 왼쪽에서 오른쪽으로 흐릅니다.
  • 화살표는 메시지나 호출을 나타냅니다.
  • 짧은 메모로 상태 변화를 표시합니다.

머릿속에서 상태를 juggling하는 대신, 눈으로 다음을 볼 수 있습니다.

  • 각 이벤트가 언제 일어나는지
  • 누가 무엇에 의존하는지
  • 어디에 순서에 대한 가정이 숨어 있는지

3. 순서를 시각화하면 미묘한 타이밍 버그가 드러난다

대부분의 레이스 컨디션은 이벤트가 일어나느냐 마느냐의 문제가 아니라, 언제 그리고 어떤 순서로 일어나느냐의 문제입니다.

다음과 같은 순서를 시각화하면:

  • 공유 데이터에 대한 read와 write
  • 락 획득과 해제
  • 큐나 소켓을 통한 메시지
  • signal, callback, event

이런 질문을 구체적으로 할 수 있습니다.

  • “어떤 interleaving에서는 우리가 stale data를 읽게 되지 않을까?”
  • “두 워커가 동시에 이 리소스를 자기 것이라고 생각할 수 있지 않을까?”
  • “모두가 서로를 기다리고 있는 순간이 생겨서 서로가 서로를 block 하지는 않을까?”

타임라인이 있으면 이 질문들은 추측이 아니라, 그림을 보며 스캔하는 작업이 됩니다.

4. 저충실도 스케치가 빠른 반복을 가능하게 한다

디지털 다이어그램 도구는 좋지만, 마찰도 만만치 않습니다.

  • 어떤 표기법을 쓸지 정해야 하고
  • 다이어그램을 세팅해야 하고
  • 예쁘게 정렬하고 관리해야 합니다.

종이는 이런 오버헤드가 거의 없습니다. 특히 동시성 설계는 여러 아이디어를 시도해 보고, 대부분을 버리는 과정이기 때문에 이게 중요합니다.

저충실도 스케치라면:

  • 타임라인을 몇 초 만에 다시 그릴 수 있고,
  • 하나의 액터를 둘로 쪼개어 어떤 변화가 생기는지 볼 수 있고,
  • 메시지 방향을 바꾸거나 락을 하나 추가해 봐서 영향을 바로 확인할 수 있습니다.

반복 속도가 빠를수록 더 많은 설계를 탐색하게 되고, 그만큼 단순하면서도 올바른 구조를 발견할 가능성이 커집니다.


종이 타임라인 세션을 진행하는 방법

엄격한 프로세스가 필요한 건 아니지만, 팀에 잘 맞는 간단한 레시피를 소개하면 다음과 같습니다.

1. 구체적인 시나리오를 고른다

타이밍 이슈가 생기기 쉬운 현실적인 엔드 투 엔드 시나리오를 고릅니다. 예를 들면:

  • 두 사용자가 같은 문서를 동시에 편집하는 경우
  • 백그라운드 잡이 아직 도는 동안 결제 webhook이 도착하는 경우
  • 롤링 배포 중에 캐시 무효화가 발생하는 경우

“동시 편집 전반” 같은 추상적인 것보다는, “A 사용자가 저장을 누르는 동시에 B 사용자의 연결이 끊기는 경우”처럼 상황을 구체적으로 잡는 편이 훨씬 유용합니다.

2. 액터를 식별한다

페이지 위쪽에 동시적으로 움직이는 컴포넌트들을 가로로 나열합니다. 예를 들면:

  • 브라우저 탭 A
  • 브라우저 탭 B
  • API 서비스
  • 데이터베이스
  • 백그라운드 워커

각각은 자기만의 타임라인을 갖게 됩니다.

3. 이벤트와 화살표를 그린다

각 액터별로, 시간의 흐름에 따라(왼쪽에서 오른쪽으로):

  • write(foo=1), acquire(lock), enqueue(job)처럼 이벤트를 짧은 표시나 박스로 그리고 레이블을 붙입니다.
  • HTTP 요청, 큐 메시지, WebSocket, RPC 등 액터 간 메시지나 호출은 화살표로 연결합니다.
  • cache: stale, job: done, session: expired처럼 중요한 상태 변화는 간단한 메모로 적어 둡니다.

깔끔하게 그리려고 애쓸 필요는 없습니다. 중요한 건 보기 쉬운 것, 이해되는 것이지, 예쁜 다이어그램이 아닙니다.

4. 다른 interleaving을 탐색한다

이제 팀원들에게 이렇게 물어봅니다. “타이밍이 다르면 어떻게 될까?”

  • 어떤 이벤트를 더 앞이나 뒤로 옮겨 보고
  • 응답을 지연시키고
  • 메시지를 한 번 떨어뜨려 보고(drop)
  • 독립적인 두 write의 순서를 바꿔 봅니다.

각 변형을 타임라인에 다시 그리거나 수정해서 표현합니다. 이때 다음을 찾아봅니다.

  • 데이터가 준비되기 전에 일어나는 read
  • 두 타임라인 모두 자기만 성공했다고 생각하는 상황
  • A는 B를 기다리고, B는 C를, C는 다시 A를 기다리는 데드락 체인

5. 문제를 표시하고 설계를 반복한다

문제가 보이면 동그라미를 쳐 두고, 이렇게 질문합니다.

  • 더 안전한 순서를 강제할 수 있을까?
  • **추가 ack(확인 메시지)**나 idempotency key가 필요할까?
  • 이 부분은 어설픈 단계 나열 대신 트랜잭션 경계로 묶어야 하지 않을까?
  • 여기서는 아예 공유 mutable state를 제거할 수 없을까?

그다음 수정된 설계를 다시 스케치하고, 같은 과정을 반복합니다. 종이 위에서 아키텍처를 테스트 주도 설계하듯이 굴리는 셈입니다.


종이 타임라인이 실제로 드러내는 것들

현실적인 시나리오를 종이 위에서 따라가다 보면 흔히 다음과 같은 것들이 드러납니다.

  • 데드락 – 모든 참여자가 서로가 리소스를 풀어 주거나 signal을 보내주기만을 기다리고 있는 상태
  • 놓친 신호 / 유실된 이벤트 – listener가 구독하기 전에 이벤트가 먼저 발생하거나, retry 로직 때문에 메시지가 두 번 처리되는 경우
  • 공유 상태 손상(shared-state corruption) – 두 writer가 락이나 버전 체크 없이 같은 레코드를 갱신하는 경우
  • stale read – 캐시나 replica가 primary write보다 뒤처져 있는 상태에서 read가 발생하는 경우
  • 불일치한 invariant – 전체 시스템 수준에서 항상 참이어야 하는 조건이, 부분 업데이트로 인해 잠시 깨지는 상황

이런 것들은 코드가 이미 존재한 뒤에는 재현하기 가장 어렵고, 설계가 아직 선과 화살표에 불과할 때가 이해하고 고치기 가장 쉬운 시점입니다.


공유되는 시각적 모델은 팀 커뮤니케이션을 개선한다

동시성 버그는 기술적인 문제이면서 동시에 팀 커뮤니케이션 문제이기도 합니다. 각자 머릿속 모델이 다르기 때문입니다.

  • 백엔드 엔지니어는 프론트엔드가 retry한다고 가정하고,
  • 프론트엔드는 API가 idempotent하다고 가정하고,
  • Ops 팀은 잡을 여러 번 돌려도 안전하다고 가정합니다.

종이 타임라인은 모두가 동시에 보고, 직접 수정해 볼 수 있는 공유 시각적 모델을 만들어 줍니다. 그 결과:

  • 숨겨진 가정이 표면으로 드러나고,
  • 비전문가도 설계 논의에 참여할 수 있으며,
  • 백엔드, 프론트엔드, 인프라 사이의 오해가 줄어듭니다.

추상적인 논쟁 대신, 특정 화살표를 가리키며 이렇게 말할 수 있습니다. “여기서 고객이 두 번 청구될 수 있어요.


얻을 수 있는 효과: 인시던트 감소, 디버깅 감소, 더 싼 수정 비용

설계 단계에서 레이스 컨디션을 잡아내면, 그 효과는 복리처럼 쌓입니다.

  • 디버깅 시간 감소: 초반에 몇 시간을 쓰고, 프로덕션에서 간헐적으로 터지는 문제를 며칠씩 쫓아다니는 일을 줄입니다.
  • 프로덕션 인시던트 감소: 가장 까다로운 장애 모드는 실제 사용자에게 피해를 주기 전에 설계에서 제거됩니다.
  • 수정 비용 절감: 스케치 단계에서 버그를 고치는 비용은, 이미 작성된 코드와 마이그레이션을 되갈아엎는 것과 비교할 수 없을 만큼 낮습니다.
  • 더 나은 아키텍처: 타임라인 검증을 통과한 설계는 대개 더 단순하고, 의도가 명확하며, 견고합니다.

종이가 테스트, 정적 분석, 런타임 관측 가능성(observability)을 대체하진 않습니다. 하지만 처음부터 건전한 정신 모델을 세우게 해 주어, 그 모든 도구를 훨씬 더 효과적으로 만들어 줍니다.


이번 주에 바로 시작하는 방법

팀에서 종이 프로토타입 디버거를 시도해 보려면, 이렇게 해 보세요.

  1. 비동기나 동시성 동작이 있는 기능을 하나 고른다.
  2. 30–45분짜리 화이트보드 또는 노트 세션을 잡는다.
  3. 액터들과 구체적인 시나리오를 그린다.
  4. 함께 여러 가지 interleaving을 따라가 본다.
  5. 발견한 설계 변경 사항을 정리하고, 이후 코드와 테스트에 반영한다.

허락이나 특별한 툴, 교육이 필요 없습니다. 마커 하나, 쓸 수 있는 표면 하나, 그리고 낙서할 의지만 있으면 됩니다.

다음 번 “도저히 재현이 안 되는” 레이스 컨디션이 프로덕션 인시던트 리포트가 아니라 종이 타임라인 위에서 먼저 나타난다면, 그때가 바로 종이 디버거가 제 역할을 하고 있다는 신호입니다.

종이 프로토타입 디버거: 손으로 그린 타임라인으로 레이스 컨디션을 배포 전에 잡아내기 | Rain Lag