Export 에러 핸들링 / 실패 분류
Export action 이 예외를 raise
하면 SDK 는 raw 예외 체인을 failure classifier 에 통과시켜
ExportLogMessageCode 로 매핑합니다.
그 코드가 사용자에게 보이는 i18n 메시지와 UI 가 렌더하는 로그 레벨을
결정합니다. 이 페이지는 그 classifier 를 문서화하여 플러그인 작성자가
다음을 알 수 있게 합니다.
- SDK 가 이미 인식하는 v2
error.code값 목록, - 백엔드가 새 코드를 배포했을 때 카탈로그를 확장하는 방법,
- v2 envelope 가 없을 때 같은 메커니즘이 v1 regex 로 점잖게 fallback 하는 방식.
26H1Patch)이 페이지는 SYN-6919 의 SDK-5 부분을 문서화합니다 — 기존 v1 regex
classifier 위에 얹은 v2 error.code 카탈로그. SDK-1 의
Runtime v2 Client Wiring 과 같은 v2
export-readiness 번들의 일부입니다.
2계층 매칭 — v2 envelope catalog → v1 regex fallback
synapse_sdk.plugins.actions.export.failure_classifier.classify_export_error
는 체인된 예외 트리를 워크하면서 type 이름 + str(exc) 를 단일 텍스트
블롭으로 평탄화한 뒤 두 matcher 를 순서대로 실행합니다.
- v2 envelope catalog (SYN-6919 / SDK-5 신규) — 블롭에서
"error": {"code": "<value>"}형태를 찾습니다.<value>가 SDK 의 카탈로그 dict 에 등록돼 있으면 해당ExportLogMessageCode가 즉시 승리합니다. - 레거시 v1 regex 패턴 — 기존 컴파일 regex 리스트 (선언 순으로 매칭,
더 구체적인 패턴이 먼저).
KeyError, 네트워크 에러, 스토리지 타입 불일치, 권한 거부 등 기타 모든 케이스를 처리합니다.
v2 계층은 엄격히 additive 입니다. 미인식 v2 코드는 v1 regex 로 흘러
내려가고, v2 envelope 가 없는 예외는 카탈로그를 완전히 건너뜁니다.
classifier 는 SYN-6919 이전과 동일하게 — 알려진 v2 코드가 없는 입력에
대해 — 같은 ExportLogMessageCode (또는 None) 를 반환합니다. SemVer
영향은 patch.
v2 가 v1 보다 우선하는 이유
두 계층이 동시에 매칭 가능할 때 (예: HTTPError 가 v2 envelope 을 옮기고
있고 status code 에 대한 일반 v1 regex 에도 걸리는 경우), v2 카탈로그가
더 구체적인 신호입니다 — 백엔드가 정확히 어떤 실패 클래스인지 알려준
것이므로. v1 regex 로 흘려보내면 partial_failure (WARNING — 배치 절반은
성공) 가 일반 ERROR 로 다운클래스 될 위험이 있습니다. 매칭 순서가 이를
방지합니다.
ExportLogMessageCode 레퍼런스
v2 카탈로그 멤버 (SYN-6919 / SDK-5)
이 세 멤버는 SYN-6919 와 함께 추가되었고 v2 카탈로그 dict 에 연결되어 있습니다. 레벨은 UI 렌더링과 하류 도구의 retry 시맨틱을 모두 결정합니다.
| ExportLogMessageCode | v2 error.code | 레벨 | 재시도 정책 | 의미 |
|---|---|---|---|---|
EXPORT_FAILED_BAD_REQUEST | invalid_ids | ERROR | No-retry — caller 가 잘못된 ID 를 보냄 | Request body / filter 가 잘못됨 (일반적으로 알 수 없는 task / assignment / ground-truth ID). |
EXPORT_FAILED_BATCH_PARTIAL | partial_failure | WARNING | 실패한 항목만 재시도 | Multi-status 응답 (HTTP 207). 일부 성공, 일부 실패. 성공한 항목은 persist 됨. 짧은 back-off 후 실패 ID 만으로 bulk request 를 재전송. |
EXPORT_FAILED_RATE_LIMITED | rate_limited | ERROR | Back-off 후 재시도 | 백엔드 rate limiter (HTTP 429) 가 요청을 거부. 대기 후 재시도. |
partial_failure 가 ERROR 가 아닌 WARNING 인 이유부분 성공을 ERROR 로 다루면 UI 에 액션이 하드 실패로 보여, 대부분 의 항목이 export 되었다는 사실이 가려집니다. WARNING 레벨은 성공 측면을 계속 보여주고, 실패한 부분만 back-off + 재시도하는 시맨틱과도 맞습니다 (bulk 엔드포인트의 하류 도구들이 이미 구현 중인 패턴).
v1 regex 패턴 (SYN-6919 이전)
레거시 패턴 (예: EXPORT_FAILED_NETWORK, EXPORT_FAILED_PERMISSION,
EXPORT_FAILED_STORAGE_UNSUPPORTED, EXPORT_FAILED_INVALID_KEY …) 은
그대로 유지됩니다. v2 envelope 보다 먼저 존재했던 예외 (raw KeyError,
httpx 네트워크 실패, 스토리지 레이어 메시지 등) 를 잡고, v2 카탈로그가
매칭 결과를 내지 못한 경우에만 평가됩니다. 전체 순서 리스트는
synapse_sdk/plugins/actions/export/failure_classifier.py 를 참고하세요.
선언부에는 어떤 실패 클래스를 잡으려는지 코멘트가 달려 있습니다.
백엔드가 신규 코드 배포 시 카탈로그 확장
카탈로그는 모듈 레벨의 평범한 dict 입니다.
_V2_ERROR_CODE_TO_EXPORT_CODE: dict[str, ExportLogMessageCode] = {
'invalid_ids': ExportLogMessageCode.EXPORT_FAILED_BAD_REQUEST,
'partial_failure': ExportLogMessageCode.EXPORT_FAILED_BATCH_PARTIAL,
'rate_limited': ExportLogMessageCode.EXPORT_FAILED_RATE_LIMITED,
}
신규 백엔드 코드를 추가하는 작업은 3단계 — matcher 편집 불필요 입니다.
- enum 멤버 추가 —
synapse_sdk/plugins/actions/export/log_messages.py의ExportLogMessageCode에 적절한LogLevel로 멤버를 추가합니다. 같은 파일의register_log_messages({...})블록에en/koi18n 템플릿을 등록합니다. - v2
error.code매핑 —_V2_ERROR_CODE_TO_EXPORT_CODE에 새 enum 멤버로 한 줄 매핑. - 회귀 테스트 추가 —
tests/plugins/actions/export/test_failure_classifier.py에 새 코드를 가진 예외를 주입하고classify_export_error가 새 enum 을 리턴하는지 assert.
카탈로그를 top-level dict 로 외재화한 결과, 미래 contributor 는
classify_export_error 자체를 절대 건드릴 필요가 없습니다 — matcher
로직은 안정적으로 유지되고 인식 표면만 자랍니다.
외부 플러그인이 이 패턴을 재사용할 수 있는가?
가능합니다. matcher 는 의도적으로 generic 입니다 — HTTP status code 가 아닌 예외 텍스트를 검사 — 그래서 동일한 v2 envelope 형태로 raise 하는 custom action 은 동일하게 분류됩니다.
from synapse_sdk.exceptions import BackendError
raise BackendError(
'bulk fetch failed: '
'{"error": {"code": "partial_failure", "detail": "..."}}'
)
# → classify_export_error 는 EXPORT_FAILED_BATCH_PARTIAL 리턴
다른 카테고리 (upload, train, inference) 를 위한 별도 classifier 를 작성하는 플러그인 작성자는 같은 2계층 형태를 따르세요 — 알려진 백엔드 코드의 카탈로그를 먼저 평가, envelope 이전의 예외에 대한 regex fallback. Export classifier 가 레퍼런스 구현입니다. 구조를 복사하면 카테고리 간 i18n + 레벨 계약이 정렬됩니다.
워크 예시 — partial failure 처리
from synapse_sdk.exceptions import BackendError
from synapse_sdk.plugins.action import BaseAction
from synapse_sdk.plugins.actions.export.failure_classifier import (
classify_export_error,
)
from synapse_sdk.plugins.actions.export.log_messages import (
ExportLogMessageCode,
)
class MyExportAction(BaseAction):
def run(self):
try:
self.v2_client.assignments.bulk_fetch(ids=self.params.ids)
except BackendError as exc:
code = classify_export_error(exc)
if code is ExportLogMessageCode.EXPORT_FAILED_BATCH_PARTIAL:
# 배치 절반이 성공 — WARNING 으로 표면화하고
# 실패한 subset 만 재시도 큐에 enqueue.
self.log_message(code)
self._enqueue_retry(failed_ids=self._extract_failed(exc))
return
# 다른 분류 (또는 None) 는 ERROR 로 버블 업.
self.log_message(code or ExportLogMessageCode.EXPORT_FAILED)
raise
self.log_message(code) 는 enum 옆에 등록된 i18n 템플릿을 해상합니다 —
UI 는 raw 예외 대신 로컬라이즈된 설명을 표시합니다.
테스팅 노트
tests/plugins/actions/export/test_failure_classifier.py 가 커버하는 것:
- 세 개의 v2 카탈로그 히트 (코드당 1건),
- v2 우선 over v1 (
invalid_ids를 메시지에 가진KeyError는 일반 v1KeyError매핑이 아니라EXPORT_FAILED_BAD_REQUEST로 분류), - 미인식 v2 코드의 v1 regex 계층 fallthrough,
- v2 계층 prepend 후에 기존 v1 패턴 중 어느 것도 깨지지 않았음을 확인하는 10건의 parametrize 회귀 스위트.
외부 플러그인은 단위 테스트에서 classify_export_error 를 직접 활용할
수 있습니다 — SDK state 의존성이 없고 임의 예외 인스턴스를 받습니다.
See also
- Export Actions —
BaseExportAction레퍼런스 - Runtime v2 Client Wiring —
self.v2_clientwiring 방식 (SDK-1 / SYN-6919) - BackendV2Client — 이 페이지가 분류하는 envelope 을 emit 하는 v2 클라이언트
- 소스:
synapse_sdk/plugins/actions/export/failure_classifier.py - 소스:
synapse_sdk/plugins/actions/export/log_messages.py