Paul Jialiang Wu agentic-portfolio English 中文 Español 日本語✉️ 무료 리스트
← 포트폴리오로 돌아가기

AI-네이티브 시리즈 · 신뢰성

버그를 세 번 고쳤다. 버그는 애초에 문제가 아니었다.

글: Paul Jialiang Wu · agentic-portfolio-lovat.vercel.app · 2026-08-02

1분 요약 — 이 글에서 얻어갈 것

AI 에이전트에게 똑같이 고장난 페이지를 세 번 보고했다. 세 번째는 대문자로만 썼다. 매번 대답은 사실이었다 — 고쳤다, 머지했다, 테스트도 녹색이다 — 그런데도 페이지는 죽어 있었다. 배포 사슬 안의 서로 다른 세 링크가 끊어져 있었고, 그 위의 모든 계층은 계속 성공했다고 보고하고 있었기 때문이다. 교훈: "완료"란 '마지막' 링크에 대한 주장일 뿐이다. 사용자가 실제로 보는 지점에서 검증하라, 그러지 않으면 당신은 자기 영수증만 수집하고 있는 셈이다.

어느 하루 오후를 부검해본다: 배선 버그, 정중히 대답 없이 사라진 배포 플랫폼, 그리고 단 한 번도 실행된 적 없는 기능. ~6분 분량.

A seven-box delivery chain — code, merge, build, deploy, serve, auth, pixels — with ✕ marks breaking three connectors: merge to build (break 2), serve to auth (break 1), and auth to pixels (break 3)
배포 사슬: 코드 → 머지 → 빌드 → 배포 → 서빙 → 인증 → 픽셀. 하루 오후 사이에 세 개의 링크가 끊어졌다. 끊어진 지점 위의 모든 링크는 계속 "완료"라고 말하고 있었다.

8월 2일, 나는 AI 에이전트에게 바로 이 포트폴리오에 있는 비공개 대시보드가 "찾을 수 없음(Not Found)"을 보여준다고 말했다. 에이전트는 버그를 고쳤다고 했다. 확인해보니: 찾을 수 없음. 다시 보고했다. 또 다른 버그를 고쳤다고 했다. 찾을 수 없음. 세 번째로 나는 이렇게 타이핑했다 WHY??? (번역: 왜???) — 물음표 세 개, 이해관계자의 인내심을 재는 만국 공통 단위였다.

여기서 불편한 부분이 나온다: 그 수정들은 하나같이 다 정확했다. 테스트는 녹색이었다. 머지도 실제로 이루어졌다. 그런데도 페이지는 그 모든 과정을 거치는 내내 죽어 있었다. 나는 버그 하나와 싸우고 있던 게 아니었기 때문이다. 나는 체인 — 그리고 체인은 한 번에 딱 한 링크씩만 끊어지며, 마지막 링크를 고쳐야만 비로소 다음 링크가 끊어져 있다는 게 드러난다.

멘탈 모델: 모든 주자가 자기 구간만 채점하는 계주 수정 사항은 반드시 이동해야 한다 — 코드 → 머지 → 빌드 → 배포 → 서빙 → 인증 → 사용자의 화면. 이 계주의 각 주자는 저마다 자기 구간을 훌륭하게 뛰었다고 장담할 것이고, 대개는 그 말이 맞다. 하지만 경주는 바통이 마지막 선을 넘어야만 끝나는 것이고, 중간에 있는 그 누구도 그 선을 볼 수가 없다. "완료"란 최종 인계에 대한 주장일 뿐이다. 그 이전의 모든 것은 이해당사자 본인의 자체 평가에 지나지 않는다.

1막: 둘 다 옳은데, 악수가 없다

대시보드의 API는 HTTP 헤더 하나를 읽어 인증한다. 대시보드 페이지는 그 API를 호출했다 — 그런데 헤더를 아예 보내지 않았다. API는 옳았다: 설계된 대로 익명 호출을 거부했다. 페이지도 옳았다: 설계된 대로 데이터를 요청했다. 그 사이에 있는 문장은 아무도 책임지지 않았다: 페이지는 API가 읽는 자격 증명을 제시해야 한다.

구글의 SRE 책에는 몇 년 전 읽고서도 그냥 "좋은 말이네" 정도로 흘려 넘겼던 구절이 있다: "다층 시스템에서는 한 사람의 증상이 다른 사람의 원인이 될 수 있음을 유의하라" [1] (번역: Note that in a multilayered system, one person's symptom is another person's cause). 내 증상은 404였다. 원인은 각자 자기 테스트를 통과한 두 컴포넌트 사이의 이음매에 살고 있었다. 이음매에는 직접 작성하지 않는 한 테스트 스위트가 존재하지 않는다 — 이것이 바로 컨트랙트 테스트가 필요한 이유 전부다. Fowler는 이렇게 지적한다: "이 컨트랙트 테스트들 중 하나라도 실패한다면, 테스트 더블을 업데이트해야 한다는 뜻이고, 아마 코드도 함께 업데이트해야 할 것이다" [2] (번역: [a] failure in any of these contract tests implies you need to update your test doubles, and probably your code). 아무도 테스트하지 않은 것은 바로 그 컨트랙트였다.

2막: 조용히 자취를 감춘 플랫폼

그래서 우리는 배선을 고쳤고, 머지했고, 사용자(나)에게 완료됐다고 알렸다. Not Found.

수정 사항은 메인 브랜치에 올라가 있었다. 테스트는 초록불이었다. 아무도 몰랐던 사실: 호스팅 플랫폼의 무료 플랜은 — 자사 한도 페이지에 적힌 그대로 — "86400초마다 100회" [3] (번역: 100 times every 86400 seconds)까지만 허용했는데, 그날 오후 신나게 올려댄 자잘한 풀 리퀘스트들이 그 100회를 이미 다 써버린 뒤였다. 그 뒤로는 머지를 아무리 해도 아무 일도 일어나지 않았다. 레포에는 에러가 없었다. 빨간 X도 없었다. 배포 연동은 그저 조용히 응답을 멈췄을 뿐이다 — 바닥이 뚫린 우편함처럼, 편지는 하루 종일 받아 주는 척만 했다.

내 에이전트는 계속 "머지 완료 ✓"라고 말했다 — 점심 이후로 문 닫은 주방을 두고 웨이터가 "금방 나옵니다"라고 말하는 것과 똑같이. 그 말은 사실이었다. 그리고 그 말은 동시에 쓸모가 없었다. 왜냐하면 "머지됨"이란 자기가 자기 영수증에 직접 쓴 손글씨에 불과하기 때문이다 — 고객이 실제로 궁금한 질문은 "배포돼서 서빙되고 있는가"이고, 그 질문은 단 한 번도 던져지지 않았다. SRE 책은 이를 블랙박스/화이트박스 구분이라 부른다: 블랙박스 모니터링은 "증상 지향적이며, 예측된 문제가 아니라 실제로 벌어지고 있는 문제를 나타낸다: '시스템이 지금 당장 제대로 작동하지 않는다'" [1] (번역: is symptom-oriented and represents active—not predicted—problems: 'The system isn't working correctly, right now'). 우리가 돌린 점검은 전부 화이트박스였다. 그 자리에서 유일하게 근무 중인 블랙박스 모니터는 사용자였고, 사용자는 물음표로 나에게 청구서를 날리고 있었다.

3막: 단 한 번도 실행된 적 없는 기능

쿼터가 풀렸고, 배포를 강제로 돌렸고, 페이지가 마침내 로드됐다 — 그런데 비어 있었다. 대시보드는 CLI가 사이트로 밀어넣는 리포트 카드를 렌더링한다. 그 푸시는, 이제서야 밝혀졌지만, 지금껏 단 한 번도 성공한 적이 없었다: CLI는 자격 증명을 한 헤더 이름으로 보냈는데, 사이트는 다른 이름을 읽고 있었다. 이 기능은 테스트를 거치긴 했다 — 자격 증명이 필요 없는 절반의 경로에 대해서만. 인증이 필요한 절반은 프로덕션 실행 횟수가 0이었다. "드물게 실행됨"이 아니라. 0이었다.

프로덕션에서 단 한 번도 실행된 적 없는 코드 경로는 기능이 아니다. 그것은 유닛 테스트를 갖춘 소문일 뿐이다.

Left box: where the tests looked — unit tests, merges, green builds, three checkmarks. Right dark box: the user's screen, 'Not Found' times three. Below, a dashed panel titled 'THE GAP' lists the three numbered breaks: the missing credential handshake, the spent deploy quota, and the wrong header name
테스트가 바라본 곳과 사용자가 바라본 곳. 세 가지 고장은 모두 그 틈새에 살고 있었다 — 그리고 각각이 다음 것을 숨기고 있었다.

왜 세 번씩이나? 스위스 치즈로 설명하기

James Reason의 스위스 치즈 모델은 첨단 기술 시스템의 사고를 여러 방어층에 뚫린 구멍들을 통과하는 궤적으로 설명한다 — "some are engineered (alarms, physical barriers, automatic shutdowns, etc), others rely on people" [4] (번역: 일부는 공학적으로 설계된 것이고(경보, 물리적 장벽, 자동 정지 장치 등), 나머지는 사람에 의존한다). 보통 이 모델은 구멍들이 일렬로 늘어서서 재앙이 뚫고 지나갈 때 인용된다. 내 그날 오후는 그 반대였고, 현업 엔지니어라면 매일 겪는 버전이었다: 켜켜이 너무 깊게 겹쳐 있어서 구멍 하나를 고칠 때마다 그다음 층의 구멍이 드러났다, 사용자 불만 한 건씩. 심층 방어는 양방향으로 작동한다. 실패 역시 겹겹이 쌓인다는 뜻이다. 각 층은 내 수정을 정중히 흡수하고는 새로운 "찾을 수 없음"을 내놓았을 뿐이다.

이 흐름은 누군가 마지막 링크에서 검증할 때에야 끝난다. 마침내 실제 브라우저로, 실제 소유자 세션으로, 실제 배포된 페이지에 접속했을 때 — 흐름 전체에 불이 들어왔다. 배선 수정은 배포되지 않았고, 배포는 애초에 일어나지도 않고 있었으며, 데이터 파이프는 한 번도 물을 나른 적이 없었다.

패턴 / 안티패턴

이 메커니즘에 이름을 붙이자면: 엉뚱한 지점에서의 확인. 각 층은 자신이 한 일만 확인할 뿐인데, 사람(그리고 에이전트)의 뇌는 그 확인들의 합창을 하나의 큰 "완료"로 듣는다.

제1원칙, 한 문장으로: 변화는 오직 사용자가 그것을 경험하는 지점에서만 존재한다. 그 위의 모든 단계는 이해당사자의 증언일 뿐이다.

증거 자료

검증에 관한 글이라면 그 자체로 검증 가능해야 하니 밝혀둔다: 사용자 보고 3건; 서로 다른 세 지점에서 끊긴 연결 고리(페이지→API 인증 정보, 머지→빌드 쿼터, CLI→API 헤더 이름); 하루 100회라는 배포 플랫폼의 한도(86,400초 기준)를 완전히 소진한 사건 1건; 그 파이프를 타고 흘러간 리포트 카드는 열아홉 건이고, 이번 사건 자체의 리포트 카드까지 치면 스무 건이다 — 그런데 그전까지 이 파이프는 단 한 건도 전달한 적이 없었다. 그리고 내 책임 추적 엔진이 이번 턴에 매긴 점수는 1.0점 만점에 0.57점이었다. 저장된 카드(이 레포의 rc0020)에는 내가 docs/reportcards/collection.json 스스로 매긴 점수가 기록돼 있는데 — 자칭 1.0점이 네 개나 되지만 — 엔진의 채점기는 카드를 다시 읽어들일 때 각 주장의 근거가 얼마나 탄탄한지에 따라 점수에 상한을 건다. 해당 파일에 make checkanyagent report roadmap를 돌리면 0.57이 출력된다. 내 주장과 내 실제 점수가 공개적으로, 그것도 영구히 어긋나 있는 셈이다 — 그리고 나는 그 숫자를 한 글자도 빼지 않고, 계속 기다리게 만든 그 사람에게 그대로 전달해야 했다. 나는 이 조용한 숫자를 억지로라도 소리 내어 말하게 만드는 시스템을 만들 것을 권한다. 이 부검이 존재하는 이유는 오직 그것뿐이다.

참고 문헌

  1. Beyer, B., Jones, C., Petoff, J., & Murphy, N. R. (2016). Site Reliability Engineering, 6장: 분산 시스템 모니터링. O'Reilly / Google. sre.google/sre-book/monitoring-distributed-systems
  2. Fowler, M. (2011). Contract Test. martinfowler.com/bliki/ContractTest.html
  3. Vercel. Limits — "Deployments per day (Hobby): You are able to deploy 100 times every 86400 seconds (1 day)." (번역: "일일 배포 횟수(Hobby 등급): 86400초(1일)당 100회까지 배포할 수 있습니다.") vercel.com/docs/limits
  4. Reason, J. (2000). Human error: models and management. BMJ, 320(7237), 768–770. pmc.ncbi.nlm.nih.gov/articles/PMC1117770

관련 글

사건 당일에 근거 자료를 바탕으로 썼다: PR들, 배포 로그, 배포 스크립트 출력, 그리고 물음표 세 개짜리 사용자 메시지 하나. — Paul Jialiang Wu · agentic-portfolio-lovat.vercel.app