본문으로 건너뛰기

Export 에러 핸들링 / 실패 분류

Export action 이 예외를 raise 하면 SDK 는 raw 예외 체인을 failure classifier 에 통과시켜 ExportLogMessageCode 로 매핑합니다. 그 코드가 사용자에게 보이는 i18n 메시지와 UI 가 렌더하는 로그 레벨을 결정합니다. 이 페이지는 그 classifier 를 문서화하여 플러그인 작성자가 다음을 알 수 있게 합니다.

  • SDK 가 이미 인식하는 v2 error.code 값 목록,
  • 백엔드가 새 코드를 배포했을 때 카탈로그를 확장하는 방법,
  • v2 envelope 가 없을 때 같은 메커니즘이 v1 regex 로 점잖게 fallback 하는 방식.
Ticket: SYN-6919 (parent SYN-6893, label 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 를 순서대로 실행합니다.

  1. v2 envelope catalog (SYN-6919 / SDK-5 신규) — 블롭에서 "error": {"code": "<value>"} 형태를 찾습니다. <value> 가 SDK 의 카탈로그 dict 에 등록돼 있으면 해당 ExportLogMessageCode 가 즉시 승리합니다.
  2. 레거시 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 시맨틱을 모두 결정합니다.

ExportLogMessageCodev2 error.code레벨재시도 정책의미
EXPORT_FAILED_BAD_REQUESTinvalid_idsERRORNo-retry — caller 가 잘못된 ID 를 보냄Request body / filter 가 잘못됨 (일반적으로 알 수 없는 task / assignment / ground-truth ID).
EXPORT_FAILED_BATCH_PARTIALpartial_failureWARNING실패한 항목만 재시도Multi-status 응답 (HTTP 207). 일부 성공, 일부 실패. 성공한 항목은 persist 됨. 짧은 back-off 후 실패 ID 만으로 bulk request 를 재전송.
EXPORT_FAILED_RATE_LIMITEDrate_limitedERRORBack-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 편집 불필요 입니다.

  1. enum 멤버 추가synapse_sdk/plugins/actions/export/log_messages.pyExportLogMessageCode 에 적절한 LogLevel 로 멤버를 추가합니다. 같은 파일의 register_log_messages({...}) 블록에 en / ko i18n 템플릿을 등록합니다.
  2. v2 error.code 매핑_V2_ERROR_CODE_TO_EXPORT_CODE 에 새 enum 멤버로 한 줄 매핑.
  3. 회귀 테스트 추가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 는 일반 v1 KeyError 매핑이 아니라 EXPORT_FAILED_BAD_REQUEST 로 분류),
  • 미인식 v2 코드의 v1 regex 계층 fallthrough,
  • v2 계층 prepend 후에 기존 v1 패턴 중 어느 것도 깨지지 않았음을 확인하는 10건의 parametrize 회귀 스위트.

외부 플러그인은 단위 테스트에서 classify_export_error 를 직접 활용할 수 있습니다 — SDK state 의존성이 없고 임의 예외 인스턴스를 받습니다.

See also

  • Export ActionsBaseExportAction 레퍼런스
  • Runtime v2 Client Wiringself.v2_client wiring 방식 (SDK-1 / SYN-6919)
  • BackendV2Client — 이 페이지가 분류하는 envelope 을 emit 하는 v2 클라이언트
  • 소스: synapse_sdk/plugins/actions/export/failure_classifier.py
  • 소스: synapse_sdk/plugins/actions/export/log_messages.py