설계 패키지의 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>
96 lines
5.2 KiB
Markdown
96 lines
5.2 KiB
Markdown
---
|
|
title: Runbook — STUDIO_UNAVAILABLE (Tech Log Studio 일시 장애)
|
|
category: TRANSIENT_DEPENDENCY
|
|
error_codes: [STUDIO_UNAVAILABLE]
|
|
severity: P1
|
|
owner: oncall
|
|
last_updated: 2026-08-18
|
|
status: 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 시나리오 추가 검토
|
|
|
|
## 7. Related
|
|
|
|
- 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]]
|