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

발행: (2026년 1월 14일 오후 10:56 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

Cover image for I Stopped “Scaling Fast” and Started Designing Failure — Here’s What Changed

오랫동안 나는 개발자로서의 역할이 시스템을 정상적으로 작동하게 만드는 것이라고 생각했다.
이제는 내 진짜 역할이 시스템이 명확하게 실패하도록 만드는 것이라고 믿는다.

이 글은 내가 아키텍처, 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. 팀은 아키텍처의 일부이다

소유권이 흐릿하면 책임이 모두에게 공유되고, 실패가 “다른 사람의 레이어”가 되면 시스템도 그 모호함을 반영한다.

명확한 소유권 경계는 다음을 감소시킨다:

  • 무음 실패
  • 중복된 수정 작업
  • 사고 시 감정적 부담

기술 아키텍처와 사회적 아키텍처는 불가분의 관계이다.

나에게 바뀐 점

이 사고방식을 채택한 후:

  • 사고가 더 드물어졌고, 무엇보다도 짧아졌습니다.
  • 디버깅이 “무슨 일이 일어나고 있나요?”에서 “이 정확한 것이 실패했습니다.”로 전환되었습니다.
  • 새로운 개발자를 온보딩하는 속도가 빨라졌습니다.
  • 나 자신의 인지 부하가 크게 감소했습니다.

시스템이 더 단순해진 것은 아닙니다; 더 정직해졌습니다.

결론

복잡성은 피할 수 없습니다.
혼란은 선택 사항입니다.

실패를 설계한다고 해서 부정적인 것이 아니라; 신뢰성을 높이는 것입니다. 시스템이 실패한다면:

  • 명확하게
  • 신속하게
  • 그리고 이미 이해하고 있는 방식으로

올바르게 하고 있는 것입니다.

Back to Blog

관련 글

더 보기 »

시스템 설계 : 캘린더 앱

기능 요구사항 - 이벤트 생성, 이벤트 수정, 이벤트 취소 - 일간, 주간, 연간 캘린더 보기 - 반복 회의 설정 - 알림 전송 ...

시스템 설계 빠른 가이드

System Design은 규모의 언어이며, 모든 엔지니어는 이를 구사해야 합니다. 저는 복잡한 System Design 주제를 해독하는 데 도움이 되는 1‑페이지 Quick Guide를 만들었습니다.

Node.js 및 Express.js를 활용한 API 설계 모범 사례

소개: 대부분의 API가 규모를 확장하기 전에 실패하는 이유 🚨 오늘 작동하는 API를 만들 수 있지만… 내일은 여전히 ​​깨진 시스템을 구축하고 있을 수 있습니다. 많은 API가 실패하는 이유는…