오류 처리
훅은 자신이 관찰하는 요청 내부에서 실행되므로, 그 실패를 어떻게 처리하는지가 중요합니다. 이 페이지는 정확한 정책을 다룹니다.
두 가지 정책
hooks_raise는 훅이 예외를 던질 때 무슨 일이 일어나는지 제어합니다.
hooks_raise |
동작 |
|---|---|
True(기본값) |
훅 예외가 호출 밖으로 전파됩니다. |
False |
훅을 건너뛰고 RuntimeWarning을 내며 호출이 계속됩니다. |
인스턴스별로 설정되며 호출별로 재정의할 수 있습니다(predict_batch, system_one,
Router.route, Router.predict의 hooks_raise=). 호출별 None은 “인스턴스 값을 사용”을
뜻합니다.
# strict: a broken audit hook fails the request
laya.load("convaiinnovations/laya", on_predict_end=audit, hooks_raise=True)
# lenient: telemetry must never take down a served request
laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_raise=False)
dispatch는 Exception을 잡습니다. Exception이 아닌 것(BaseException
참고)은 hooks_raise=False에서도 결코 삼켜지지 않습니다.
무언가 실패하면 무엇이 실행되는가
예측 수명 주기는 try / except / finally로 감싸이므로, 실패 시 정리 훅이 실행됩니다.
try:
on_predict_start
inference
except BaseException as exc:
ctx.error = exc
on_error (best effort; cannot mask exc)
raise
finally:
elapsed_ms, usage
on_predict_end (best effort; cannot mask exc on the failure path)
이벤트와 런타임별 실패 행렬:
| 이벤트 | 런타임 | 예외를 던지면 |
|---|---|---|
on_predict_start |
Agent / Router | hooks_raise=True: on_error와 on_predict_end가 여전히 실행된 뒤 예외가 전파됩니다. False: 경고하고 계속합니다(예외 전에 만든 변경은 남음). |
| 추론 | Agent / Router | ctx.error 설정, on_error 실행, on_predict_end 실행, 예외 전파. |
on_error |
Agent / Router | 원래 예외를 결코 가리지 않으며 __context__로 연결됩니다. |
on_predict_end(성공 경로) |
Agent / Router | hooks_raise=True: 전파됩니다(결과는 계산되었지만 호출은 실패). False: 경고. |
on_predict_end(실패 경로) |
Agent / Router | 원래 예외를 결코 가리지 않으며 __context__로 연결됩니다. |
on_route |
Router | 그대로 전파됩니다. 아직 예측 컨텍스트가 없습니다. |
on_load |
Router | 그대로 전파됩니다. 체크포인트는 만들어진 채 상주합니다. |
on_evict |
Router | 그대로 전파됩니다. 체크포인트는 이미 해제되었습니다. |
알아 둘 만한 결과:
- 실패한
on_load는 모델을 캐시에 남기므로, 다음load는on_load를 다시 실행하지 않고 그 모델을 반환합니다. - 성공 경로에서 실패한
on_predict_end는 추론이 성공했음에도 호출자가 결과 대신 예외를 받는다는 뜻입니다. 순수한 부수 효과인 end 훅에는hooks_raise=False를 사용하십시오.
예외 연결
다른 예외가 이미 전파되는 중에 훅이 실패하면, 원래 예외가 다시 발생하고 훅의 예외가
__context__로 첨부됩니다. 근본 원인은 결코 사라지지 않습니다.
class BadTelemetry:
def on_error(self, ctx):
raise RuntimeError("telemetry down")
try:
agent.system_one(state, questions, hooks=[BadTelemetry()])
except RuntimeError as exc:
assert exc.__context__ is not None # the telemetry failure
실패 경로에서 실패한 on_predict_end에도 같은 규칙이 적용됩니다.
BaseException
dispatch는 BaseException이 아니라 Exception을 잡으므로, KeyboardInterrupt와
SystemExit는 항상 전파됩니다. 이들은 여전히 예측 수명 주기의 except BaseException
분기를 트리거하므로, 프로세스가 풀리기 전에 on_error와 on_predict_end가 실행됩니다.
인터럽트 지연 시간이 중요하다면 그 훅을 빠르고 비블로킹으로 유지하십시오.
구성 오류
잘못된 구성은 추론 전에 TypeError로 즉시 실패합니다.
| 경우 | 발생 시점 | 예 |
|---|---|---|
| 인스턴스 대신 클래스 | 생성 | hooks=[MyHook] |
| 수명 주기 메서드 없음 | 생성 | hooks=[object()] |
| 호출 불가능 이벤트 | 생성 | on_predict_start = 5 |
| 호출 불가능 편의 훅 | 생성 | on_predict_start=123 |
hooks=의 일반 호출 가능 객체 |
생성 | hooks=[lambda ctx: None] |
호출별 훅은 호출이 이루어질 때 검증되므로, 잘못된 호출별 훅은 생성 시점이 아니라
predict/system_one에서 TypeError를 발생시킵니다.
경고
hooks_raise=False이면, 실패하는 각 훅이 훅 타입과 이벤트를 밝히는 RuntimeWarning을
하나씩 냅니다.
laya: hook Metrics.on_predict_end failed: connection reset
경고는 훅 정의당 한 번이 아니라 실패당 한 번 발생하므로, 부하 상태의 불안정한 훅은 시끄러울 수 있습니다. 그것이 중요하면 훅 내부에서 집계하거나 속도를 제한하십시오.
타임아웃
hooks_timeout은 각 훅 호출을 초 단위로 제한합니다. 제한 이후에도 실행 중인 훅은 훅 실패로
취급됩니다. hooks_raise=True이면 TimeoutError, False이면 RuntimeWarning입니다.
None(기본값)은 제한 없음을 뜻합니다.
laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_timeout=2.0)
인스턴스별로 설정하거나 predict_batch, system_one, Router.route, Router.predict,
ONNXAgent.system_one에서 호출별로 재정의할 수 있습니다. 값은 양수여야 합니다. 0이나
음수는 길이 0인 join에서 경합하는 대신 설정되는 지점에서 ValueError를 발생시킵니다.
타임아웃이 걸린 훅은 호출자의 contextvars 컨텍스트 사본을 지닌 워커 스레드에서 실행되므로,
호출자가 설정한 요청 id나 트레이싱 스팬이 훅에 보입니다.
한 가지 솔직한 주의점: Python은 스레드를 중단할 수 없으므로, 타임아웃된 훅은 백그라운드에서
계속 실행됩니다. 타임아웃은 요청이 얼마나 기다리는지를 제한할 뿐, 훅이 얼마나 사는지를
제한하지 않습니다. 서빙 중인 요청을 응답성 있게 유지하는 데 쓰고, 작업을 회수하는 데 쓰지
마십시오. 멈출 수 있는 훅에는 기반 호출에도 자체 타임아웃(소켓 또는 HTTP 타임아웃)을
주십시오. 스레드를 회수할 수 없으므로, 매 호출마다 멈추는 훅은 호출당 스레드 하나씩
늘어납니다. 멈출 수 있는 훅에는 hooks_timeout에 의존해 멈추게 하지 말고 자체 한도를
주십시오.
비동기 훅의 경우 코루틴은 이벤트 루프에서 실행됩니다. 호출 측의 타임아웃은 여전히 제한 시간 후 반환하고, 코루틴은 루프에서 계속 실행됩니다.
타임아웃은 또한 hooks_concurrent=False 잠금을 해제합니다. 디스패치는 제한 시간까지만 훅을
기다린 뒤 넘어가고, 타임아웃된 훅은 잠금 밖에서 계속 실행됩니다. 따라서 잠금은 제때 끝나는
훅을 직렬화하는 것이지, 시작된 모든 훅을 직렬화하는 것이 아닙니다. 초과한 훅이 더 이상
뒤에 있는 훅을 막지 않습니다.
정책 선택
| 훅 종류 | 권장 | 이유 |
|---|---|---|
| 정책 / 가드레일 / 마스킹 | hooks_raise=True |
조용히 실패하는 정책은 보안 구멍입니다. |
| 감사 / 로깅 | 테스트에서는 hooks_raise=True, 운영에서는 흔히 False |
감사 기록을 잃는 것은 시끄러워야 하지만 반드시 치명적일 필요는 없습니다. |
| 메트릭 / 트레이싱 | hooks_raise=False |
관측성은 요청을 실패시켜서는 안 됩니다. |
| 캐시 읽기/쓰기 | hooks_raise=True |
깨진 캐시는 조용히 미스하는 대신 드러나야 합니다. |
섞을 수도 있습니다. 가드레일은 인스턴스 기본값으로 설치하고 텔레메트리 훅에는 자체
try/except를 주거나, 호출별로 다른 hooks_raise를 사용하십시오.