에러를 발생시키지 않는 버그: 프로덕션 환경에서의 몽키 패치 (Monkey-patch) 순서 문제
요약
AgentCost 구축 과정에서 발생한 몽키 패칭(Monkey-patching) 순서 문제를 다룹니다. 코드 로드 순서에 의존하는 패칭 방식이 예외 없이 조용히 잘못된 동작을 유발하는 위험성을 경고합니다.
핵심 포인트
- 몽키 패칭 시 여러 요소가 동일 함수를 패치할 때 순서 의존성 발생
- 로드 순서에 따른 래핑 변경은 예외 없이 조용히 실패하여 디버깅이 어려움
- 패치 시점에 로깅을 추가하여 래핑 순서를 추적하는 보험이 필요함
- 로드 순서에 의존하는 설계는 코드 간 보이지 않는 의존성을 생성함
이 문제는 오픈 소스 LLM 비용 추적기인 AgentCost를 구축하는 과정에서 발생했습니다. 토큰 사용량을 일관되게 캡처하기 위해 여러 SDK를 패칭(patching)하는 작업은 바로 이런 종류의 버그가 숨어들기 딱 좋은 지점입니다.
설정 (The setup)
3시간. 한 줄의 수정. 그동안 단 하나의 예외(exception)도 발생하지 않았습니다.
이 글은 도입하기는 쉽지만 찾아내기는 잔혹할 정도로 어려운 버그 유형에 대한 기록입니다:
호출되는 순서가 아니라, 코드가 로드되는 순서에 의존하는 동작.
버그 (The bug)
런타임에 메서드나 함수를 재정의하는 몽키 패칭 (Monkey-patching)은 Python 및 JS 라이브러리에서 흔히 사용됩니다. 소스 코드를 건드리지 않고 로깅, 추적 또는 재시도(retry) 동작을 추가하기 위해 함수를 래핑(wrap)하는 방식입니다:
original_call = SomeClient.call
def patched_call(self, *args, **kwargs):
...
개별적으로는 괜찮습니다. 문제는 둘 이상의 요소가 동일한 함수를 패치할 때 발생하며, 각 요소가 자신이 유일하게 패치하고 있다고 가정할 때 시작됩니다.
왜 조용히 실패하는가 (Why it fails silently)
가장 나중에 로드되는 패치가 이전의 모든 패치를 래핑하게 됩니다. 임포트(import) 순서를 바꾸거나, 다른 라이브러리가 먼저 초기화되거나, 지연 임포트(lazy import)가 다른 시점에 해결되면 래핑 순서가 변경됩니다.
예외도 없고, 경고도 없습니다. 함수는 여전히 어떤 값을 반환합니다. 다만 당신이 기대하는 값이 아닐 뿐이며, 스택 트레이스(stack trace)에는 그 이유를 알려줄 정보가 아무것도 없습니다.
이것이 바로 이 종류의 버그를 비용이 많이 들게 만드는 원인입니다. 소란스러운 버그는 찾기 쉽지만(cheap), 조용한 버그는 모든 것이 정상적으로 작동하는 것처럼 보이기 때문에 수 시간을 허비하게 만듭니다.
해결책 (The fix)
패치 시점에 한 줄만 추가하면 되는 저렴한 보험입니다:
import logging
def patch(name, order):
...
만약 출력이 예상치 못하게 변경된다면, 무엇이 무엇을 어떤 순서로 래핑했는지 정확히 보여주는 로그 기록을 가질 수 있습니다.
일반화할 수 있는 교훈 (The generalizable lesson)
명시적인 호출 그래프 (call graph)가 아니라 **로드 순서 (load order)**에 따라 동작이 결정되는 상황이 발생한다면, 여러분은 코드베이스의 서로 관련 없는 부분 사이에 보이지 않는 의존성 (dependency)을 도입한 것입니다. 이는 코드 리뷰 단계에서 반드시 잡아내야 할 문제입니다. 장애가 발생한 후에 발견하기보다는, "이 패치 (patch)가 이 함수를 다른 무엇이 패치했는지에 대해 무언가를 가정하고 있는가?"라고 질문해야 합니다.
Python 이외의 환경에서도 이와 같은 순서 버그 (ordering bugs)를 경험한 분들이 있는지 궁금합니다. 런타임 패칭 (runtime patching)이나 의존성 주입 (dependency injection)이 흔히 사용되는 곳이라면 어디에서든 (Ruby, JS 빌드 도구 등) 발생할 수 있을 것 같습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기