Files
DongHyeonkaandClaude Opus 5 91e6d99654 feat: Tech Log Studio 백엔드 기반 — 계약 배선, 오류 코드, 경계 규칙, 스키마, 엔드포인트 2종
설계 패키지의 studio-v1.yaml(v3.0.0, 응답 봉투)을 이 저장소에 배선하고
슬라이스 1의 기반을 세운다. 19개 operation 중 getStudioSession과
listStudioCatalog를 구현했다.

계약과 생성
- src/config/openapi/studio-v1.yaml 을 vendor하고 MANIFEST에 출처 커밋을 기록
- openapi-generator로 DTO(model)만 생성한다. generateApis 대신
  globalProperties.set(['models': '']) — 그 두 속성은 플러그인 7.18.0에 없다
- useOneOfInterfaces=false. 그 대가로 discriminator union 5종의 Jackson 배선이
  깨진다(spec §3.1). 그 5종을 쓰는 7개 operation은 Plan 02에서 전략을 정한 뒤 구현한다
- 생성 코드는 별도 generatedOpenapi sourceSet에 둔다. -Werror가 생성물의 deprecated
  API 사용을 빌드 실패로 승격하기 때문이다. jar와 test 클래스패스에 별도로 얹는다

오류 계약
- StudioError 23종(계약 ApiError.code와 1:1) + StudioException(ApiErrorCarrier)
- StudioExceptionHandler는 techlog 패키지로 범위를 좁힌다. 다른 기능의 오류 응답을
  바꾸지 않기 위해서다
- 클라이언트 문구는 레지스트리의 client_safe_message에서 가져오고 예외 메시지는
  로그 전용이다(ApiErrorCarrier javadoc의 요구)
- 바인딩 예외를 봉투로 옮긴다. 그러지 않으면 bare RFC 7807이 새어 나가 ADR-006을 위반한다

게이트
- TechLogBoundaryArchTest 7종 — spec §4.3의 bounded context 경계. Gradle leaf를
  늘릴 수 없어 이 규칙이 경계의 유일한 방어선이다
- StudioErrorRegistryTest — enum ↔ 레지스트리 ↔ 계약 3축 대조, vendor 사본 해시 검증
- StudioContractDriftTest — springdoc 표면이 계약을 벗어나면 실패. @ComponentScan이라
  새 컨트롤러가 자동으로 걸린다
- StudioSessionCsrfHeaderProfileContractTest — 배포 가능한 세 프로파일이 계약의
  csrf-header-name const로 해소되는지 고정. 이 저장소는 실제 composition root를
  테스트에서 부팅할 수 없어 파일 단언으로 그 층을 덮는다

스키마
- V7__techlog_core.sql, 28 테이블. 설계 DDL에서 studio_idempotency(기존
  idempotency_record 재사용)와 범위 밖 6종을 제외했다
- 원본의 tech_log 스키마 대신 public을 쓴다. 원본의 SET search_path는 Flyway
  세션에만 적용되고 런타임 커넥션 풀은 상속하지 않는다

알려진 제약
- getStudioSession은 세션 인프라(redis-session)가 없어 503 STUDIO_UNAVAILABLE을
  반환한다. 계약이 이 operation에 허용하는 유일한 실패 코드다. 가짜 CSRF 토큰으로
  200을 만들지 않았다
- 따라서 슬라이스 1의 "프론트 로그인 실동작" 목표는 아직 달성되지 않았다

이 커밋은 AGENTS.md의 human-only 커밋 정책에 대한 저장소 소유자의 명시적 지시로
작성됐다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 15:14:52 +09:00

5.2 KiB

title, category, error_codes, severity, owner, last_updated, status
title category error_codes severity owner last_updated status
Runbook — STUDIO_UNAVAILABLE (Tech Log Studio 일시 장애) TRANSIENT_DEPENDENCY
STUDIO_UNAVAILABLE
P1 oncall 2026-08-18 active

Runbook: STUDIO_UNAVAILABLE (runbook://studio/unavailable)

1. Trigger

이 runbook은 Tech Log Studio API(studio-v1.yaml)가 error.code=STUDIO_UNAVAILABLE (HTTP 503, category TRANSIENT_DEPENDENCY, retryable=true)을 응답할 때 발동됩니다.

  • alert name: studio_error_rate_critical 또는 studio_dependency_unavailable
  • alert payload 필수 field: operation(문서/미리보기/게시 중 어느 슬라이스인지), error.code, error.category, dependency_name(가능하면), runbook_link
  • 임계:
    • P1: STUDIO_UNAVAILABLE 비율이 1분간 Studio 전체 트래픽의 10% 초과
    • P2: 단발성 spike이나 5분 내 자연 회복

StudioExceptionHandler(adapter/inbound/web)가 StudioException을 봉투로 변환하는 지점이므로, 이 코드는 항상 Studio facade/use case(application-core)가 자신의 하위 의존성(영속성, 캐시, 오브젝트 스토리지, 렌더링/미리보기 파이프라인 등) 실패를 클라이언트에 안전한 단일 코드로 접어(classify) 던진 결과입니다 — 원인 그 자체가 아니라 Studio가 판단한 결과라는 점을 유의합니다.

2. First Response (5분 이내)

Step 1 — 확인

  1. 최근 배포 이력 확인 (app-bootstrap 롤아웃, config 변경) — 배포 직후 spike면 롤백 우선 검토
  2. 로그에서 StudioException이 감싸고 있던 실제 원인을 확인: dev.caskeleton.application.techlog 패키지의 use case/facade 로그에서 STUDIO_UNAVAILABLE로 분류되기 직전의 원인 예외 (PersistenceFailureException, DependencyFailureException 등)를 추적
  3. runtime-health Dependency Matrix에서 Studio가 의존하는 구성요소(DB, 캐시, object storage)의 required/optional 분류와 현재 상태 확인
  4. 특정 slice(문서 편집/미리보기/게시)에 국한된 장애인지, Studio 전역 장애인지 구분

Step 2 — 임시 격리

  • 원인이 특정 하위 의존성이면 해당 의존성의 runbook으로 전환 (예: runbook://db/unavailable, runbook://cache/unavailable) — STUDIO_UNAVAILABLE은 진입점일 뿐, 근본 원인 대응은 하위 의존성 runbook이 담당
  • 클라이언트(Frontend)는 이미 retryable=true를 신뢰해 지수 백오프 재시도를 수행하므로, 단기 spike는 자연 회복을 우선 관찰 (조기 개입으로 인한 추가 부하 유발 방지)

3. Diagnosis

  • log query: {service="app"} | error.code="STUDIO_UNAVAILABLE" | stats count by operation
  • 원인 추적: 같은 요청의 correlation id로 use case 로그를 따라가 어떤 하위 호출이 StudioException.withDetails(StudioError.STUDIO_UNAVAILABLE, ...)로 재분류됐는지 확인
  • metric panel:
    • http_server_requests_seconds_count{uri=~"/api/v1/studio/.*", outcome="SERVER_ERROR"}
    • 하위 의존성 metric (hikaricp_connections_active, resilience4j_circuitbreaker_state, object storage client 오류율)
  • 가능한 원인:
    • DB/캐시/오브젝트 스토리지 등 필수 의존성 장애가 Studio 계층까지 전파
    • Studio 자체 리소스 고갈 (스레드풀, 커넥션풀)
    • 미리보기/렌더링 파이프라인의 타임아웃 누적
    • 배포 직후 신규 코드 경로의 미검증 예외가 fallback으로 STUDIO_UNAVAILABLE에 접힘

4. Mitigation

  • 단기: 근본 원인이 확인된 하위 의존성이면 해당 dependency runbook의 mitigation을 적용
  • Studio 자체 리소스 고갈이면 인스턴스 스케일아웃 또는 커넥션/스레드풀 상향 검토
  • 특정 slice(예: 미리보기)만 영향받는다면 해당 slice만 일시적으로 기능 차단하고 나머지(문서 편집/게시)는 정상 유지 검토 — 전면 장애보다 부분 degrade 우선
  • 장기: 반복되는 하위 의존성 장애가 STUDIO_UNAVAILABLE로 잦게 나타나면, 해당 의존성의 circuit breaker/timeout 임계를 재조정하고 fallback 경로 보강

5. Escalation

  • P1 5분 내 회복 신호 없으면 해당 하위 의존성 오너 팀에 page
  • 여러 slice(문서/미리보기/게시)에서 동시에 발생하면 incident commander 호출 (공유 인프라 계층 문제 의심)

6. Recovery / Verification

  • 회복 확인 metric: STUDIO_UNAVAILABLE 비율이 5분간 1% 미만으로 유지
  • 하위 의존성 metric(circuit breaker CLOSED, connection pool 정상)도 함께 확인
  • post-incident:
    • 어떤 하위 의존성이 STUDIO_UNAVAILABLE로 접혔는지 기록하고 근본 원인 runbook에 링크
    • Studio 클라이언트(Frontend STUDIO_ERROR_CODES) 쪽 재시도/백오프 동작이 기대대로 작동했는지 확인
    • 특정 slice 반복 장애면 chaos test 시나리오 추가 검토
  • error-codes.yaml row: STUDIO_UNAVAILABLE (feature-techlog-studio-backend, dev.caskeleton.application.techlog.error.StudioError.STUDIO_UNAVAILABLE)
  • 매핑 지점: dev.caskeleton.adapter.inbound.web.techlog.StudioExceptionHandler
  • 관련 runbook: runbook://db/unavailable, runbook://cache/unavailable
  • 관련 branch: feature-techlog-studio-backend