나는 “Scaling Fast”를 멈추고 “Failure” 설계를 시작했다 — 여기서 바뀐 점
Source: Dev.to

오랫동안 나는 개발자로서의 역할이 시스템을 정상적으로 작동하게 만드는 것이라고 생각했다.
이제는 내 진짜 역할이 시스템이 명확하게 실패하도록 만드는 것이라고 믿는다.
이 글은 내가 아키텍처, API, 심지어 팀까지 설계하는 방식을 조용히 바꾼 변화를 설명한다—특히 겉보기엔 괜찮아 보이지만 어느 순간 문제가 발생하는 복잡한 제품을 만들 때. 초보자를 위한 글이 아니다; 이는 여러분이 배포하고, 문제가 생기고, 새벽 3시에 고쳐가며 “다시는 안 된다”라고 스스로 다짐한 뒤에야 신경 쓰게 되는 결정들에 관한 이야기다.
The Problem: Complexity Hides Behind “Working Code”
Most systems don’t fail because of bad code.
They fail because:
- 가정이 암묵적이다
- 제약이 문서화되지 않았다
- 실패 모드가 보이지 않는다
- 성공 경로는 최적화되고, 실패 경로는 무시된다
In early versions of one of my products, everything “worked”:
- API가 응답했다
- UI가 부드러웠다
- 메트릭이 정상(그린)이었다
Until a single edge case cascaded into:
- 부분적인 데이터 손상
- 재시도가 부하를 증폭시켰다
- 로그가 진실이 아닌 이야기를 전했다
The system didn’t fail loudly; it failed politely. That was the real problem.
전환: 실패를 먼저 설계하기
대신에 묻기:
“이걸 어떻게 확장 가능하게 만들 수 있을까?”
나는 이렇게 묻기 시작했다:
“이게 어떻게 깨질까 — 그리고 바로 어떻게 알 수 있을까?”
이것은 나에게 몇 가지 절대 타협할 수 없는 설계 원칙을 채택하게 만들었다.
1. 제약은 제한이 아니라 기능이다
모든 복합 시스템에는 제약이 있다. 문제는 그 제약이 존재하지 않는 척하는 것이다.
코딩하기 전에 지금은 명시적으로 적어 두는 명시적 제약 예시:
- 최대 요청 크기 (하드 실패, 베스트‑에포트가 아님)
- 허용 가능한 데이터 오래됨 정도
- 의존성당 타임아웃 예산
- 재시도 제한 (지수 백오프 적용 또는 전혀 적용하지 않음)
- 소유권 경계 (이 서비스는 저 서비스의 버그를 고치지 않는다)
제약이 명시되지 않으면 전설이 된다. 전설은 장애 시 살아남지 못한다.
2. 실패 모드는 반드시 이름을 붙여야 한다
어떤 것이 어떻게 실패하는지 이름을 붙일 수 없으면, 그것에 대해 논리적으로 사고할 수 없다.
나는 이제 실패 모드를 다음과 같이 문서화한다:
- 상위 서비스 사용 불가 → 캐시된 저하된 응답 반환
- 부분 쓰기 성공 → 보상 이벤트 발생
- 클라이언트 오용 → 실행 가능한 오류와 함께 크게 거부
- 알 수 없는 상태 → 처리를 중단하고 인간에게 알림
이는 비관주의가 아니라 엔지니어링 정직성이다.
3. 가시성은 로깅이 아니다
- 로그는 서사이다.
- 메트릭은 집계이다.
- 트레이스는 타임라인이다.
이 중 어느 하나만으로는 진실을 알 수 없다. 중요한 경로에 대해 나는 묻는다:
- 어떤 신호가 이것이 깨졌다는 것을 알려주는가?
- 깨짐과 감지 사이에 얼마나 걸리는가?
- 추측 없이 누가 영향을 받았는지 알 수 있는가?
답이 “로그를 확인하자”라면, 시스템이 나에게 거짓말을 하고 있는 것이다.
4. API는 관대하지 않아야 한다 (시스템을 보호하기 위해)
“받는 것을 관대하게 하라”는 말은 멋지게 들리지만, 이자가 붙은 기술 부채가 될 때까지는 그렇다.
이제 나는 다음과 같은 API를 설계한다:
- 공격적으로 검증한다
- 모호한 입력을 거부한다
- 무엇을 고쳐야 하는지 설명하는 오류를 반환한다, 단순히 무엇이 실패했는지만 알려주지 않는다
친절한 API는 사용자를 보호한다. 엄격한 API는 시스템을 보호한다. 훌륭한 API는 두 가지를 모두 제공한다.
5. 팀은 아키텍처의 일부이다
소유권이 흐릿하면 책임이 모두에게 공유되고, 실패가 “다른 사람의 레이어”가 되면 시스템도 그 모호함을 반영한다.
명확한 소유권 경계는 다음을 감소시킨다:
- 무음 실패
- 중복된 수정 작업
- 사고 시 감정적 부담
기술 아키텍처와 사회적 아키텍처는 불가분의 관계이다.
나에게 바뀐 점
이 사고방식을 채택한 후:
- 사고가 더 드물어졌고, 무엇보다도 짧아졌습니다.
- 디버깅이 “무슨 일이 일어나고 있나요?”에서 “이 정확한 것이 실패했습니다.”로 전환되었습니다.
- 새로운 개발자를 온보딩하는 속도가 빨라졌습니다.
- 나 자신의 인지 부하가 크게 감소했습니다.
시스템이 더 단순해진 것은 아닙니다; 더 정직해졌습니다.
결론
복잡성은 피할 수 없습니다.
혼란은 선택 사항입니다.
실패를 설계한다고 해서 부정적인 것이 아니라; 신뢰성을 높이는 것입니다. 시스템이 실패한다면:
- 명확하게
- 신속하게
- 그리고 이미 이해하고 있는 방식으로
올바르게 하고 있는 것입니다.