버그 교훈 누적 누적 문서

마지막 업데이트 2026-07-29

개설: 2026-07-29 최종 갱신: 2026-07-29 항목: 4

이 문서의 규칙

이슈를 끝낼 때마다 항목 하나를 4줄로 추가한다. 상세는 개별 bugs/ 문서에 링크만 걸어 이 문서가 길어지지 않게 한다.
증상사용자가 보는 현상 한 줄
원인 패턴다른 코드에도 적용되는 일반화. 특정 파일 이야기로 쓰지 않는다
판별법로그·쿼리·지표 시그니처. 재현 없이 확진할 수 있는 근거
적용 규칙다음에 코드 쓸 때 지킬 것. 명령형으로

원인 패턴이 이미 있는 항목과 같으면 새 항목을 만들지 말고 기존 항목에 사례 링크만 추가한다 — 반복 횟수가 곧 우선순위 신호가 된다.

항목

① 쓰기 주체 ≠ 수명 소유자

증상진행자가 켠 '듣기'가 활동 전환 후 켜지지 않음 (PPI-1167)
원인 패턴같은 의미의 값이 두 곳에 있고, 자주 쓰는 쪽을 SSOT로 착각. 실제 정본은 그 값의 수명이 더 긴 쪽이었다. 사본은 리셋 경로(세션 종료·스텝 전환·차단 분기)를 갖고 있었다
판별법"판정하는 로그는 찍히는데 적용 로그가 없다" — 예약 실행(Defer mic: applied ...)은 있고 적용(Agent listening enabled)이 없으면 판정 입력이 빈손
적용 규칙값을 읽어서 분기하는 코드를 새로 넣을 때, 그 값을 false/null로 되돌리는 경로를 먼저 전부 찾는다. 리셋 경로가 있으면 정본은 다른 곳이다. 하위 훅이 상위 상태를 판정에 쓰면 인자로 받는다

상세: PPI-1167 재랜딩과 3연쇄 수정

② 읽기를 도입하면 공급자도 같이 만든다

증상기능은 동작하는데 특정 전환 뒤에만 조용히 no-op (PPI-1167)
원인 패턴"무조건 실행"을 "값을 보고 판단"으로 바꾸면서 그 값의 공급 경로를 만들지 않음. 값이 비는 순간이 있는데 채워주는 주체가 없어 구조적으로 미완성
판별법변경 전후 diff에서 - doSomething()+ if (x) doSomething() 형태를 찾고, x에 값을 쓰는 코드가 그 시점 이후에 존재하는지 역추적
적용 규칙조건을 추가하면 그 조건의 공급자·수명·리셋을 같은 PR에서 명시한다. 기존 무조건 동작을 조건부로 바꿀 때는 조건이 false가 되는 경우를 테스트나 로그로 남긴다

③ 대표 시나리오만 검증하면 사이드 이펙트를 놓친다

증상신규 기능은 정상인데 기존 동작이 깨져 revert (PPI-1167 / PPI-907 계열)
원인 패턴새 축(fallback)을 추가하면서 기존 축(configured)의 경로를 함께 바꿨는데, 검증은 새 축의 대표 시나리오만 수행. 새 축이 꺼져 있어야 정상인 상황이 사각지대
판별법기능 조건을 나열해 "기능이 비활성인 조합"을 명시적으로 만든다. 예: 진행자 부재 fallback이면 진행자가 있는 수업이 사각지대
적용 규칙기능 검증 케이스에 "기능 OFF 상태에서 기존 동작 유지"를 항상 포함한다. 새 축과 기존 축이 같은 적용 지점을 공유하면 두 축의 4조합(on/on, on/off, off/on, off/off)을 확인한다

④ 분산 환경에서 조회 응답과 이벤트의 도착 순서는 보장되지 않는다

증상진행자 presence가 실제와 반대로 고정되어 다음 변경까지 유지 (PPI-1167 재랜딩 중 발견)
원인 패턴snapshot(조회 응답)과 push(변경 이벤트)를 같은 상태에 적용하는데, 버전 비교를 한쪽에만 적용. 게다가 상태를 계산한 뒤 버전을 읽어 낡은 상태에 최신 버전이 붙어 stale이 최신으로 고정됐다. @socket.io/redis-adapter에서 cross-instance push는 pub/sub을 거쳐 로컬 ack보다 늦게 도착할 수 있다
판별법드롭 지점에 로그를 심어 source·수신 버전·적용 버전을 함께 남긴다. 정상 동작 중에는 이 로그가 뜨지 않아야 한다
적용 규칙버전 비교는 모든 소스에 동일하게 적용한다. 서버는 버전을 상태 계산 전에 캡처해 "상태 ≥ 버전" 불변식을 만든다(반대면 낡은 상태가 최신으로 위장). 버전 공간이 리셋될 수 있으면(Redis 재기동) 연결 단위로 baseline을 비운다

관련 문서