← /
archive · 11 incidents

incidents

사고와 그것으로부터 만든 영구 룰. 같은 사고를 두 번 내는 것은 학습의 실패.
결과만 적은 portfolio 보다 무엇을 몰랐고 무엇을 배웠는지가 더 정직한 기록.

핵심 코드 · API key · 회사명 · IP 전부 마스킹. 작업 맥락만 노출

scroll ↓
결제01 incident

결제 후 리포트 안 뜨는 prod 사고

결제 후 리포트 안 뜨는 prod 사고
몰랐던 것

결제 SDK 를 표준대로 박아도, 결제사 *버전에 맞는 검증 방식*을 안 쓰면 '결제됐는데 권한 미부여' 가 난다는 것. SDK 연동 = 끝이 아니라는 것.

무슨 일이 있었나

사용자가 결제 완료 후 redirect 됐는데 권한이 안 풀림. DB 의 payment row 는 정상인데 접근 미반영. 근본 원인 = 결제사 v2 결제인데 검증 로직이 구버전(v1) 방식으로 연결돼 webhook 검증이 매번 실패 → 반영 누락. 검증 실패 누적 62건 (버전 불일치).

배운 것

결제는 단일 신뢰원(webhook 하나)에 의존하면 안 된다. 그리고 사고는 단일 축으로 진단하면 못 잡는다 — DB row + UX flow(사용자 시점) + webhook 검증·반영, 3축 동시에 봐야 진짜 root cause 가 나온다.

영구 룰

결제 사고 진단 표준 3축: (1) DB row 정상 여부, (2) UX flow 사용자 시점 재현, (3) webhook 도착·검증·반영 경로 일치. 단일 축으로 'DB 정상이니 OK' 결론 X. 동기 확인 + 사후 대조 다경로로 유실 흡수.

증거 → 원리 04 · 조용한 사고가 진짜 사고
DB02 incident

DB OOM 6번째 영구해결

DB OOM 6번째 영구해결
몰랐던 것

OOM 이 단일 원인이 아니라 여러 원인 누적이라는 것. 매번 다른 곳에서 메모리가 새고 있었다.

무슨 일이 있었나

DB 가 새벽에 죽는 사고가 6번째. 한 가지를 fix 하면 다음 새벽엔 다른 원인이 같은 증상을 냄 — long query / 누적 connection / 스왑 미설정 / 로그 무한 증식 / DB 튜닝 부재. '단일 원인' 착각이라 6번 반복됐다.

배운 것

반복되는 운영 사고는 한 패턴 fix 가 아니라 sweep — 가능한 원인을 동시에 정리. '메모리 부족이니 자원 더 사자' 같은 잘못된 신호에 휘둘리면 근본은 그대로 둔 채 시간만 날린다.

영구 룰

OOM 영구해결 다축 sweep: 메모리 튜닝 + 비운영 환경 정리 + 스왑 정책 + 로그 사이즈 cap 을 *한 번에* + 일정 시간 모니터. 단일 fix 후 'OK' 결론 X.

증거 → 원리 05 · 반복되면 원인은 하나가 아니다

SQL USE 절 hijack — staging 명시했는데 prod 로 redirect

몰랐던 것

마이그레이션 SQL 파일 첫 줄의 `USE <db>;` 절이 *셸 명령 인자로 명시한 DB* 보다 우선한다는 것. CLI 에서 db 이름 명시 = SSoT 가 아니라는 것.

무슨 일이 있었나

DB 마이그를 staging 에 박으려고 `mysql ... wai_board_staging < migration.sql` 셸 명령. 근데 SQL 파일 첫 줄에 `USE wai_board;` (prod) 가 박혀 있어 prod 로 redirect. 7개 마이그 파일이 같은 패턴. 다행히 멱등 안전 (CREATE IF NOT EXISTS 류) 이라 실 사고 없었지만 *DB 분리 신뢰* 가 깨졌다.

배운 것

셸 인자보다 SQL 파일 안 절(USE)이 우선이다. *DB 분리는 셸 명령 의지로 안 보장된다* — SQL 파일이 db 를 명시하면 그게 진실. 멱등 안 한 마이그였으면 prod 가 부분 적용됐을 수 있다 (조용한 사고 직전).

영구 룰

마이그레이션 SQL 파일 안 `USE <db>;` 절 영구 금지. db 이름 SSoT = mysql 명령 인자 (`-D <db>` 또는 stdin 직접). 마이그 PR 머지 전 grep `^USE` 자동 검증 추가.

증거 → 원리 04 · 조용한 사고가 진짜 사고
인프라02 incident

공용 호스팅 해외접속 차단 (DB IP 오진 정정)

공용 호스팅 해외접속 차단 (DB IP 오진 정정)
몰랐던 것

공용 호스팅이 특정 지역 외 IP 를 자동 차단할 수 있다는 것. CI 가 원격 DB 접속에 실패하면 'DB IP 가 바뀐 듯' 으로 오진하기 쉽다.

무슨 일이 있었나

CI 가 호스팅 DB 에 접속 못 함. 확인 안 된 첫 추측 'DB IP 바뀐 듯' 으로 재발급까지 달림 → 계속 실패. 진짜 원인은 호스팅의 해외 접속 차단 정책. 추측을 먼저 검증했으면 빨랐을 일.

배운 것

원격 인프라 사고 = 첫 추측이 맞을 확률은 낮다. 확인 안 된 추측으로 달리면 시간만 날린다 — provider 정책/방화벽/차단 룰을 사실로 먼저 확인.

영구 룰

CI/CD 인프라 사고 진단 표준: (1) 직접 접속 가능 여부 확인, (2) provider 차단 정책/방화벽 확인, (3) 마지막에 DB/앱 진단. 추측 X — 사실 우선.

증거 → 진단 원칙 · 확인 안 된 첫 추측으로 달리지 마

Bridge req body 이중 등록 hang — CSP report 504

몰랐던 것

Node http 서버에서 `req.on('data')` 가 *이미 처리된 body* 위에 새로 등록되면 event 가 발화 안 한다는 것. 라우터·미들웨어가 body 를 한 번 읽으면 그 stream 은 닫힌다.

무슨 일이 있었나

csp-report 라우트가 504 로 누적. 코드 보면 라우터가 `req.on('data', ...)` 로 body 새로 읽으려는데, 상위 미들웨어가 이미 `req._body` 로 흡수한 상태. data event 가 안 발화하니 요청이 30초 hang → gateway timeout 504. 운영자에겐 *알 수 없는 504* 로만 보였다.

배운 것

Node http body stream 은 *한 번만 읽을 수 있다*. 미들웨어가 흡수했으면 라우터는 `req._body` 또는 `req._rawBody` 를 써야 한다 — 새로 `req.on('data')` 등록 X. *조용한 hang* 이 사용자에겐 *조용한 사고*.

영구 룰

bridge 라우트는 `req._body` / `req._rawBody` 만 사용. `req.on('data')` 새 등록 영구 금지. 새 body-consuming 미들웨어 추가 시 라우터 호환성 검토 표준.

증거 → 원리 04 · 조용한 사고가 진짜 사고
보안02 incident

VPS 죽음 — 원격 접속 설정

VPS 죽음 — 원격 접속 설정
몰랐던 것

원격 접속 설정 한 줄 잘못 박으면 VPS 가 영구히 접근 불가가 된다는 것. out-of-band 접근 없으면 복구 불가.

무슨 일이 있었나

보안 강화 중 원격 접속 설정을 동시에 여러 줄 수정. 데몬 재시작 후 즉시 disconnect → 새 연결 거부. out-of-band 경로가 없어 영구 lock out.

배운 것

'연결 자체' 의 root layer 변경은 한 번 잘못 박으면 끝이다. 되돌릴 수 없는 변경엔 안전장치(병행 연결·확인 경로)를 *먼저* 깔고 손대야 한다.

영구 룰

원격 접속 설정 변경 시: (1) out-of-band 접근 먼저 확보, (2) 기존 연결 열어둔 채 변경, (3) 변경 후 즉시 새 연결 검증, (4) 일정 시간 두 연결 동시 유지.

증거 → 원칙 · 되돌릴 수 없는 변경엔 안전장치 먼저

멀티테넌트 RLS 누수 audit sweep

멀티테넌트 RLS 누수 audit sweep
몰랐던 것

RLS 가 모든 테이블에 일관 적용된 줄 알았던 것. 일부 join · 일부 view 가 tenant 격리 우회 가능. cross-tenant 자동 회귀 테스트 0 이면 누수 발견 불가.

무슨 일이 있었나

테스트 시점 super_admin 권한으로 통과 → tenant 일반 사용자 권한이면 다른 tenant 데이터 read 가능 케이스 35건 발견. RLS 정책은 박혀있는데 일부 query path 가 우회. 한 번에 sweep 으로 전 파일 audit.

배운 것

단일 룰 박는 것 (RLS) 만으로는 부족하다. 룰을 검증하는 *자동 회귀 테스트* 가 같이 박혀있어야 누수가 안 생긴다. 룰 + 검증 = pair.

영구 룰

멀티테넌트 라우트 신설 시 자동 강제 체크: (1) RLS 정책 정의, (2) cross-tenant 자동 회귀 테스트 추가, (3) super_admin 통과 + tenant 일반 사용자 격리 동시 검증, (4) PR 머지 전 회귀 PASS 확인.

증거 → 원칙 · 룰은 검증과 pair
디자인02 incident

signup 흰화면 사고

몰랐던 것

자동 코드 변환(codemod) 이 의존성(import) 라인까지 같이 지우면 페이지가 *에러도 없이* 백지로 죽을 수 있다는 것. 자동화의 silent 위험.

무슨 일이 있었나

i18n 일괄 변환 codemod 가 무관한 import 라인까지 함께 제거. 새로 가입하는 사용자만 보는 signup 페이지가 백지로 떴고, 운영자에겐 에러가 안 떠 30분간 인지 못함 (조용한 실패).

배운 것

일괄 자동변환이 import/의존성을 건드리면 silent fail. 게다가 사람이 안 보는 경로(신규 가입)에서 나면 사용자가 알릴 때까지 모른다 — 안 보는 경로일수록 시스템이 자동으로 잡게 강제해야 한다.

영구 룰

codemod 자동 import 제거 영구 금지. 일괄 변환은 'imports 안 건드린다' 원칙 + 변경 직후 자동 빌드 검증(눈으로 나중에 X).

증거 → 원리 04 · 조용한 사고가 진짜 사고

광고 ↔ 랜딩 톤 불일치 — 전환 0

광고 ↔ 랜딩 톤 불일치 — 전환 0
몰랐던 것

광고 크리에이티브와 랜딩 톤이 *분위기 수준에서* 어긋나면 전환이 *경고 없이* 0 으로 죽는다는 것. 기능·결제 다 정상이라 *코드 사고가 아닌 사고*.

무슨 일이 있었나

광고 캠페인 톤은 dramatic·hook 결인데 랜딩은 매거진·tranquil 결. 사용자가 광고 클릭 후 *다른 사이트에 들어온 듯한 brand mismatch* 경험. 결제·기능 다 정상인데 전환 0. *조용한 매출 사고*.

배운 것

광고와 랜딩은 *같은 universe* 로 묶여야 한다. 톤 mismatch = 사용자가 '잘못 들어왔다' 인식하고 즉시 이탈 — 분위기는 cosmetic 이 아니라 conversion 의 본체.

영구 룰

사이트와이드 톤 통일 (매거진 톤 등) + 광고/사이트 룰 분리 + 광고 launch 전 *랜딩 도착 시점 톤 매칭* 점검 표준. 톤 일관성은 코드 회귀처럼 매번 점검.

증거 → 원리 04 · 조용한 사고가 진짜 사고
워크플로02 incident

단일 파일 900라인 누적 사고

단일 파일 900라인 누적 사고
몰랐던 것

기능을 같은 파일에 계속 추가하면 *어느 순간* 디버깅·리뷰·수정이 동시에 망가진다는 것. '나중에 분리해야지' 는 사람 의지로 안 막힌다.

무슨 일이 있었나

워크스페이스 핵심 라우트 파일이 누적 900라인. 한 파일에 인증·게시·승인·결제·알림·첨부 다 박혀, 한 줄 수정에 무관한 기능 회귀가 자꾸 났다. 16시간 세션 밀어붙이며 *기능 속도*만 보고 *구조 부채*를 무시한 결과.

배운 것

코드 비대화는 *기능 추가 속도*가 아니라 *룰·자동 분리* 강제만 막을 수 있다. 사람 의지로 '나중에 분리' 절대 안 됨 — 누적 임계 넘으면 분리 비용이 fix 비용보다 커진다.

영구 룰

*파일 500라인 초과 금지* + *새 기능 = 새 파일* + *한 task = 한 파일/한 함수/한 라우트* 영구 룰. 편집 중 500라인 넘어가면 즉시 알림 + 리팩토링 우선. 누적 후 분리 X.

증거 → 원리 05 · 반복되면 원인은 하나가 아니다

스펙 미확정 후 실행 — sweep 4회 재작업

스펙 미확정 후 실행 — sweep 4회 재작업
몰랐던 것

3건+ 작업을 *대화 컨텍스트만 믿고* 진입하면 sweep 4회 재작업이 *기본값*이라는 것. 실행 속도 욕심이 *결과적으로 가장 느린* 패턴.

무슨 일이 있었나

여러 변경을 PRD·승인 없이 *맥락 추측*으로 진입. 1차 작업 → 사토시 검토 → 어긋남 발견 → 재작업 → 또 어긋남 → 4회 sweep 으로 끝남. 1회면 끝났을 작업이 4회로 늘어남.

배운 것

'추측 즉시 행동'은 *빠른* 게 아니라 *재작업 누적*. 큰 방향 재정의는 *task 재설계 먼저 → OK → 작업* 가 결과적으로 빠르다. 검증 안 한 가속은 음수 가속.

영구 룰

3건+ 작업·큰 방향 변경·신규 라우트·결제·인가·외부 API 진입 시 *1페이지 PRD + 사토시 OK 후* 실행 표준. 컨텍스트 추측 X, 인용 + 해석 1줄 확인 우선.

증거 → 원리 04 · 조용한 사고가 진짜 사고
다음 ↘method