# 운영 장애 알림 시스템 — 메타정보 수집

**작성일**: 2026-08-07
**목적**: 주말·야간 등 관리자 화면을 보지 않는 시간대에 발생한 중요 장애를 인지할 수 있도록, 어떤 알림을 만들지 결정하기 위한 사전 조사.
**조사 범위**: `C:\Users\madsc\_projects` 하위 4개 저장소 (insuniverse-server / insuniverse-admin / insuniverse-site / insuniverse-temp = 셋을 묶은 submodule umbrella)

---

## 1. 시스템 현황

### 1.1 구성

| 구성요소 | 스택 | 배포 | 도메인 |
|---|---|---|---|
| API 서버 | NestJS 10 (monorepo: `apps/api` + `libs/*` 20종) | ECS Fargate 롤링, ECR 이미지 | `api.insuniverse.com` / `s-api.` |
| 스케줄러 | 동일 이미지, `RUN_MODE=scheduler` + `SCHEDULER_TYPE` 분기 | EventBridge → ECS RunTask | — |
| 관리자 | React + Vite (FSD) | S3 + CloudFront | — |
| 사용자 사이트 | React + Vite | S3 + CloudFront | `insuniverse.com` / `s.` |

- 환경: `dev` / `stg` / `prod` (브랜치명 = 환경명 = ECS 클러스터 `insuniverse-<env>`)
- 리전 `ap-northeast-2`, env 파일은 S3(`insuniverse-<env>-envs/api/<env>.env`)에서 주입
- 데이터: MySQL(drizzle ORM), Redis(ioredis — 분산 카운터/큐)
- 로깅: `nestjs-pino` → stdout → CloudWatch Logs (ECS 기본)

### 1.2 외부 의존 (장애 시 서비스 기능이 죽는 지점)

| 벤더 | 용도 | 실패 시 영향 |
|---|---|---|
| CODEF / COOCON / OCTOVER / HYPHEN / inhouse | 진료내역 스크래핑 (5개 공급자 풀, `TREAT_POOL`로 분산) | **핵심 기능 정지** — 분석 불가 |
| TOSS / SMARTRO | 결제·정기결제·빌링 | **매출 직결** — 구독 갱신 실패 |
| ALIGO | 알림톡 / SMS | 고객 통지 누락 |
| MOK (드림시큐리티) | 본인인증 | 가입·인증 불가 |
| VFAX | 팩스 발송 | 보험사 청구 누락 |
| FCM (firebase-admin) | 앱 푸시 | 통지 누락 |
| S3 / CloudFront | 파일·정적자산 | 업로드/다운로드/배포 실패 |
| SMTP (nodemailer) | 메일 | 통지 누락 |

### 1.3 스케줄러 목록 (EventBridge, 총 15종 배포 + 코드 16개)

`.github/workflows/api.yml:200-216` 의 `TYPES` 배열이 실제 배포 대상.

```
regular-subscription        regular-agency-subscription   disable-subscription
analysis-status             analysis-stall                settlement-agency
settlement-client           alimtalk-status               fax-status
delete-treat                delete-fc                     delete-analysis
sales-statistics            expired-money                 delete-invoice-files
```

> ⚠️ `apps/api/src/schedules/samsung-s2-report.schedule.ts` 는 코드에 있으나 `TYPES` 배열에 **없음** → task-def 미갱신. 의도된 것인지 확인 필요.

---

## 2. 이미 있는 것 (재사용 자산)

새로 만들기 전에 이미 검증된 패턴이 있다. **진료(treat) 도메인 한정으로 완성된 모니터링+알림이 이미 운영 중이다.**

### 2.1 진료 모니터링 알림 (참조 구현)

- `apps/api/src/modules/treat/monitor/treat-monitor.service.ts` — `overview()`가 지표 집계 후 `warnings[]` 생성 (`:527-549`)
- `apps/api/src/modules/treat/treat-alert.service.ts` — 웹훅 발송, **30분 중복억제**, **복구(✅) 알림 1회**, 벤더별 전용 채널 폴백
- `apps/api/src/schedules/analysis-status.schedule.ts:33` — **5분 주기**로 `checkAndSend()` 호출
- 설정 저장: `tn_treat_setting` 테이블 KV (`monitor_webhook_url`, `monitor_webhook_types`, `monitor_alert_state`)
- 관리자 UI: `insuniverse-admin/src/pages/Admin/Subscription/TreatMonitor/TreatMonitorPage.tsx`, `.../Site/TreatProvider/TreatProviderPage.tsx`

현재 발송되는 경고 6종 (`treat-provider.service.ts:25-32` `ALERT_TYPES`):

| type | 조건 | level |
|---|---|---|
| `consistency` | 결제 후 못 쓰는 회원 존재 / 예약수 불일치 > 30건 | critical / warn |
| `success_rate` | 최근 N분 성공률 < 70% (표본 ≥ 5) | critical |
| `provider` | 공급자별 성공률 bad (표본 ≥ 5) | warn |
| `queue` | 대기열 ≥ 30 & 증가중 / 이탈 ≥ 20 / 최장대기 ≥ 5분 | warn |
| `stuck` | 30분 이상 정체 ≥ 10건 / 10분 이상 ≥ 20건 | critical / warn |
| `scenario` | 인증방식별 성공률 bad | warn |

**→ 이 구조(집계 → warnings → dedupe → 웹훅 → 복구알림)를 전 시스템으로 확장하는 것이 가장 짧은 경로.**

### 2.2 배포 알림 (Google Chat)

- `.github/workflows/notification.yml` — 3개 저장소 모두 동일 재사용 workflow 보유
- secret `GOOGLE_CHAT_WEBHOOK`, 카드 포맷, 커밋 목록 + 성공/실패 + Actions 링크
- **Google Chat 웹훅 채널이 이미 팀에 존재하고 동작 중** → 신규 채널 협의 없이 재사용 가능

### 2.3 미사용 자산

- `libs/chat/src/chat.service.ts` — Google Chat `{text}` 발송 서비스. **어디에도 주입되지 않은 죽은 코드.**
  - 설정 키: `configuration.ts:141` 은 `GOOGLE_CHAT_WEBHOOK_URL` 을 읽는데, 로컬 `.env` 에는 `CHATBOT_WEBHOOK_URL` 로 되어 있음 → **키 이름 불일치. prod env(S3)에 실제 어느 키가 있는지 확인 필요.**
- `libs/mailer`, `libs/aligo`, `modules/fcm`, `modules/push` — 발송 채널로 전용 가능하나 **모두 고객용**. 운영 알림에 섞어 쓰면 고객 발송 장애가 알림 장애로 전이됨.

---

## 3. 사각지대 (지금은 인지 방법이 없는 것)

| # | 사각지대 | 근거 | 위험 |
|---|---|---|---|
| **B1** | **API 500 에러가 아무 데도 통보되지 않음** | `fliters/all-exception.filter.ts:34` — `logger.error()` 만 하고 응답. 외부 통보 없음 | 최상 |
| **B2** | **정기결제 실패가 info 로그로 삼켜짐** | `schedules/regular-subscription.schedule.ts:37-41` — 각 건 실패를 `logger.log(error)`(error 아님)로 catch. 스케줄러는 항상 `process.exit(0)` | **최상 (매출 직결)** |
| **B3** | **스케줄러 미실행/실패 자체를 모름** | EventBridge → ECS RunTask 는 기본 알림 없음. 태스크가 안 뜨거나(용량/이미지 오류) 중간에 죽어도 조용함 | 최상 |
| **B4** | **APM·에러추적 도구 전무** | 전체 저장소에 Sentry/Datadog/NewRelic 흔적 0건 (grep 확인) | 상 |
| **B5** | **진료 외 도메인은 모니터링 없음** | 결제·정산·팩스·알림톡·본인인증 도메인에 `warnings` 상당물 없음 | 상 |
| **B6** | **인프라 지표 알림 없음** | CloudWatch 알람 정의가 저장소에 없음 (IaC 미보유 → 콘솔 수동 설정 여부 확인 필요) | 상 |
| **B7** | **프론트 런타임 에러 미수집** | `insuniverse-site/front/src/shared/ui/ErrorBoundary.tsx` 는 화면 폴백만. 리포팅 없음 | 중 |
| **B8** | **헬스체크가 무의미** | `app.controller.ts:9` — `return 'OK'` 상수. DB·Redis·외부벤더 상태를 전혀 반영 안 함 | 중 |
| **B9** | **DB/Redis 커넥션 장애 무통보** | 연결 실패 시 요청 단위 500으로만 표출 → B1 과 동일하게 묻힘 | 상 |

---

## 4. 알림 후보 카탈로그

우선순위 = (주말에 발생 시 손실) × (현재 인지 불가능성).

### P0 — 돈이 새거나 서비스가 멈추는 것

| ID | 알림 | 신호원 | 임계(초안) | 심각도 |
|---|---|---|---|---|
| A1 | **정기결제 실패 급증** | `tn_subscribe_failed` (`subscribe-failed.service.ts:68 create(subId, reason)`) 신규 건수 | 스케줄 실행(09시/18시) 후 실패 ≥ 5건 또는 실패율 ≥ 20% | critical |
| A2 | **결제 PG 응답 이상** | TOSS/SMARTRO 호출 실패율 | 10분 내 실패율 ≥ 30% (표본 ≥ 5) | critical |
| A3 | **스케줄러 미실행 / 비정상 종료** | ECS task exit code ≠ 0, 또는 "마지막 성공 시각" 하트비트 | 예정 시각 + 유예 30분 내 성공 기록 없음 | critical |
| A4 | **API 5xx 급증** | `AllExceptionFilter` 카운터 | 5분간 5xx ≥ 20건 또는 5xx 비율 ≥ 5% | critical |
| A5 | **API 서비스 다운** | ECS `runningCount < desiredCount` 또는 `/health` 실패 | 2회 연속 실패 | critical |
| A6 | **DB / Redis 연결 실패** | 커넥션 예외 | 1건이라도 발생 | critical |

### P1 — 기능은 살아있으나 결과가 잘못되는 것

| ID | 알림 | 신호원 | 임계(초안) | 심각도 |
|---|---|---|---|---|
| B1 | 진료 수집 전면 장애 | 기존 `success_rate` 경고 | (기존 유지) 성공률 < 70% | critical |
| B2 | 공급자 단일 장애 | 기존 `provider` 경고 | (기존 유지) | warn |
| B3 | 알림톡/SMS 발송 실패 | `alimtalk-status` 스케줄러 결과 | 실패 ≥ 10건/회 | warn |
| B4 | 팩스 발송 실패 | `fax-status` 스케줄러 결과 | 실패 ≥ 5건/회 | warn |
| B5 | 본인인증(MOK) 실패율 | 인증 시도 대비 실패 | 30분 실패율 ≥ 40% (표본 ≥ 10) | warn |
| B6 | 정산 스케줄러 결과 이상 | `settlement-agency/client` 처리 0건 또는 예외 | 처리 예정 있는데 0건 | warn |
| B7 | 파일 업로드(S3) 실패 | S3 SDK 에러 | 10분 ≥ 5건 | warn |

### P2 — 추세·용량 (주말 호출 사유는 아니지만 알아야 함)

| ID | 알림 | 신호원 | 임계(초안) |
|---|---|---|---|
| C1 | 대기열 적체 | 기존 `queue` 경고 | (기존 유지) |
| C2 | 처리 정체 | 기존 `stuck` 경고 | (기존 유지) |
| C3 | ECS CPU/메모리 | CloudWatch | 5분 평균 ≥ 85% |
| C4 | RDS 커넥션/CPU | CloudWatch | 커넥션 ≥ 80%, CPU ≥ 80% |
| C5 | 벤더 비용 급증 | `TREAT_POOL` 단가 × 호출수 | 일 예산 대비 초과 |
| C6 | 배포 실패 | 기존 `notification.yml` | (이미 있음) |
| C7 | 프론트 JS 에러 급증 | ErrorBoundary → 리포팅 엔드포인트 | 신규 도입 필요 |

### 알림에 넣지 말 것 (노이즈)

- 개별 사용자 1건 실패 (스크래핑 특성상 상시 발생 — 비율로만 판단)
- `dev` 환경 전부
- `delete-*` 계열 정리 스케줄러 성공 로그
- 이미 자동 복구되는 정합성 보정(`reconcileReservedQuantity`) 의 정상 동작

---

## 5. 발송 채널 옵션

| 옵션 | 장점 | 단점 | 비고 |
|---|---|---|---|
| **Google Chat 웹훅** | 이미 배포알림·진료알림에서 검증됨. 코드/설정 재사용, 추가비용 0 | 주말 폰 푸시가 확실치 않음 (사용자 알림설정 의존) | **1순위** |
| 알림톡/SMS (ALIGO) | 주말 인지율 최상 | 고객 발송 인프라와 공유 → 장애 전이. 건당 비용 | critical 만 이중화 |
| 이메일 (SMTP) | 이미 있음 | 주말 인지율 낮음 | 일일 요약용 |
| CloudWatch Alarm → SNS | 인프라 지표(CPU/커넥션/태스크실패) 는 이게 정석. 앱 코드 불필요 | 저장소에 IaC 없음 → 콘솔 수동 관리 | 인프라 계열 전용 |

**권장 조합**: 기본 Google Chat 단일 채널 + `critical` 만 SMS/알림톡 이중 발송. 인프라 지표는 CloudWatch Alarm → SNS → 동일 Google Chat.

---

## 6. 설계 시 지켜야 할 제약 (기존 구현에서 확인된 것)

1. **중복억제 필수** — 5분 주기 폴링이므로 동일 경고 재발송 방지 없으면 스팸. 기존 값 30분(`treat-alert.service.ts:6 DEDUPE_MS`).
2. **복구 알림 필수** — 경고가 사라지면 ✅ 1회. 없으면 "아직 죽어있나?" 확인하러 결국 관리자에 접속하게 됨.
3. **발송 실패는 swallow** — 알림 실패가 본 로직/스케줄러를 죽이면 안 됨 (`treat-alert.service.ts:88-92` 패턴).
4. **표본 하한** — 새벽 저트래픽 구간에서 1/1 실패 = 성공률 0% 로 오탐. 기존 코드 전부 `표본 ≥ 5` 가드 존재.
5. **채널/종류 ON-OFF 는 DB 설정** — 재배포 없이 관리자 화면에서 조정 (`tn_treat_setting` KV 패턴).
6. **환경 분리** — `prod` 만 발송. `stg`/`dev` 는 별도 채널이거나 무발송.

---

## 7. 결정 필요 사항 (사용자 확인 요청)

| # | 질문 | 선택지 |
|---|---|---|
| Q1 | 주말 인지 채널로 Google Chat 만으로 충분한가? | Chat 단독 / Chat + SMS(critical) / 별도 온콜 도구 |
| Q2 | prod 의 실제 웹훅 env 키가 `GOOGLE_CHAT_WEBHOOK_URL` 인가 `CHATBOT_WEBHOOK_URL` 인가? | S3 env 파일 확인 필요 |
| Q3 | CloudWatch 알람이 콘솔에 이미 설정돼 있는가? | 있음(현황 공유) / 없음 |
| Q4 | 알림 수신 대상은 누구인가? 단일 채널 vs 도메인별 분리(결제팀/개발) | — |
| Q5 | P0 범위만 먼저 만들지, P1 까지 한 번에 갈지 | P0 우선(권장) / 전체 |
| Q6 | `samsung-s2-report` 스케줄러 미배포는 의도된 것인가? | — |

---

## 8. 조사 근거 파일

```
insuniverse-server/.github/workflows/api.yml                              배포·스케줄러 15종
insuniverse-server/.github/workflows/notification.yml                     Google Chat 알림 (기존)
insuniverse-server/deploy/deploy-api.sh, deploy/README.md                 ECS 배포 경로·health 확인법
insuniverse-server/apps/api/src/app.controller.ts                         health = 상수 'OK'
insuniverse-server/apps/api/src/config/configuration.ts                   외부벤더·웹훅 설정 전체
insuniverse-server/apps/api/src/fliters/all-exception.filter.ts           500 처리(통보 없음)
insuniverse-server/apps/api/src/modules/treat/monitor/treat-monitor.service.ts   warnings 생성 로직
insuniverse-server/apps/api/src/modules/treat/treat-alert.service.ts      dedupe·복구·웹훅 발송
insuniverse-server/apps/api/src/modules/treat/provider/treat-provider.service.ts ALERT_TYPES·설정 KV
insuniverse-server/apps/api/src/schedules/analysis-status.schedule.ts     5분 주기 알림 트리거
insuniverse-server/apps/api/src/schedules/regular-subscription.schedule.ts 결제 실패 삼킴
insuniverse-server/apps/api/src/modules/subscribe/subscribe-failed/       결제 실패 기록 테이블
insuniverse-server/libs/chat/src/chat.service.ts                          미사용 Google Chat 발송기
insuniverse-admin/src/pages/Admin/Subscription/TreatMonitor/              모니터링 관리자 화면
insuniverse-site/front/src/shared/ui/ErrorBoundary.tsx                    프론트 폴백(리포팅 없음)
```
