왜 Edge Cases가 분산 시스템에서 중요한가

발행: (2025년 12월 29일 오후 10:16 GMT+9)
16 min read
원문: Dev.to

Source: Dev.to

저는 Designing Data‑Intensive Applications을 읽고 있는데, 이 책 덕분에 일상적인 엔지니어링에서 거의 질문하지 않던 가정들을 더 의식하게 되었습니다. 저에게 눈에 띈 점은 책이 얼마나 기술적인가가 아니라, 시스템에 대한 사고 방식을 서서히 바꾸어 놓는다는 점입니다. 보통은 잘 동작하기 때문에 배경에 묻혀버리는 가정들에 주목하게 해줍니다. 개발자로서 우리는 책에서 engineering folklore라고 부르는, 실무에서 잘 통한다는 이유만으로 전파되는 아이디어에 크게 의존합니다. 반드시 그 아이디어가 언제, 어디서 깨지는지 완전히 이해하고 있는 것은 아닙니다.

읽다 보니 책이 표면적인 동작을 넘어보도록 자주 권장한다는 것을 알게 되었습니다. 많은 시스템이 외관상 비슷해 보입니다: 비슷한 APIs를 제공하고, 익숙한 도구를 사용하며, 정상적인 상황에서는 잘 동작합니다. 하지만 이러한 피상적인 유사성은 오해를 불러일으킬 수 있습니다. 기대와 다른 무언가가 발생하면, 겉보기엔 비슷해 보이던 시스템도 매우 다르게 동작할 수 있습니다.

이러한 가정들은 개별적으로는 합리적입니다. 문제는 시스템이 성장하고 서로 상호작용하기 시작하면 가정들이 점점 더 취약해진다는 점입니다. 우리가 가정하는 것과 실제 일어나는 일 사이의 차이가 바로 많은 문제들의 시작점이 됩니다.

윤초 사고

이 책을 읽기 전까지 나는 윤초 사고에 대해 들어본 적이 없었습니다. 이를 접하고 나서 실제로 무슨 일이 일어났는지, 그리고 시스템들이 이후 어떻게 적응했는지 궁금해졌습니다.

윤초 사고는 2012년 6월 30일에 원자시계에 한 초를 추가해 지구 자전과 맞추면서 발생한 광범위한 기술적 장애를 가장 흔히 일컫는 말입니다. 이로 인해 61초가 들어간 1분이 생겨 23:59:60이라는 타임스탬프가 만들어졌습니다.

  • 이 조정은 이론적으로는 명확히 정의되어 있습니다.
  • 실제로는 많은 시스템이 시간이 멈추거나 반복되거나 뒤로 가는 상황을 처리하도록 설계되지 않았습니다.

가장 심각한 문제 중 하나는 Linux 커널의 고해상도 타이머 버그에서 비롯되었습니다. 윤초가 발생했을 때 일부 시스템은 라이브‑락(live‑lock) 상태에 빠졌습니다. 프로세스가 빡빡한 루프에 갇히면서 CPU 사용량이 100 %까지 치솟아 시스템이 진행되지 못했습니다.

시간에 대한 동일한 가정을 공유하는 인프라가 많았기 때문에 영향이 빠르게 퍼졌습니다. Reddit, LinkedIn, Mozilla, Yelp 등 주요 웹사이트가 부분적 또는 전체적인 중단을 겪었습니다. 예를 들어 Reddit은 한 시간 이상 서비스를 제공하지 못했습니다. 웹 서비스 외에도 Amadeus 항공 예약 시스템이 고장 나 호주에서 수백 편의 항공편이 지연되었습니다.

Java 기반 시스템 위에 구축된 애플리케이션—예를 들어 CassandraHadoop 같은 기술—은 특히 취약했습니다. 이들 시스템은 동일한 Linux 타이머에 의존했기 때문입니다. 운영체제 수준에서 시간이 예상치 못하게 동작하면 상위 소프트웨어에도 문제가 전파되었습니다.

내가 눈에 띈 점은 시스템의 서로 다른 부분이 다르게 반응했다는 것입니다:

  • 일부 구성 요소는 시간을 앞으로 이동시키려 했습니다.
  • 다른 구성 요소는 시간을 멈추거나 재시도하며 시간이 진행되기를 기다렸습니다.

이러한 일관성 부족은 잘못된 락 로직 때문이 아니라 시간 자체가 신뢰할 수 없는 공유 의존성이 되었다는 점에서 데드락이나 라이브‑락과 매우 흡사한 상황을 만들었습니다.

사후 효과와 완화 방안

  • Leap smearing – Google과 Meta는 추가된 1초를 여러 시간에 걸쳐 점진적으로 퍼뜨리는 방식을 채택했습니다. 시스템 입장에서는 시간이 점프하거나 멈추지 않고 짧은 구간 동안 약간 느리게 흐릅니다.
  • Linux 개선 – 커널의 시간 관리가 강화되어 윤초 발생 시 스레드가 예기치 않게 회전하거나 멈추지 않도록 했습니다.
  • 정책 변경 – 국제 시간 관리 기구는 2022년에 2035년까지 윤초를 완전히 폐지하기로 표결했습니다. 현재 새로운 윤초는 예정되어 있지 않습니다.

내게 남은 교훈은 문제의 핵심이 단순히 추가된 1초가 아니라 시간이 언제나 일관되고 시스템 전반에 걸쳐 보편적으로 동의된다는 가정이라는 점입니다.

결함, 실패, 그리고 대규모 현실

개념정의
Fault시스템의 일부가 기대된 동작에서 벗어납니다.
Failure전체 시스템이 사용자가 기대하는 서비스를 제공하지 못합니다.

그 차이는 겉보이는 것보다 더 중요합니다.

  • 결함은 정상입니다. 디스크가 고장납니다. 네트워크가 느려집니다. 시계가 오차를 보입니다. 인간은 실수를 합니다.
  • 대규모 시스템에서는 이것이 예외가 아니라 기대되는 현상입니다.

신뢰할 수 있는 시스템은 문제가 전혀 발생하지 않는 시스템이 아니라, 시스템 일부가 오작동하더라도 계속 작동하도록 설계된 시스템입니다.

구체적인 예시

하드 디스크는 평균 고장 시간 (MTTF)10–50 년 정도라고 보고됩니다. 이는 안심이 되지만 수천 개의 디스크를 운영하면 상황이 달라집니다. 그 규모에서는 디스크 고장이 드문 사건이 아니라 정기적으로 발생합니다. 단일 머신에서는 드물게 보이는 현상이 대규모 시스템에서는 정상적인 동작이 됩니다.

같은 개념이 fan‑out 패턴에서도 나타납니다. 하나의 이벤트가 여러 하위 작업을 트리거하는 패턴은 강력하지만, 작은 문제의 영향을 크게 확대시킵니다. 한 곳에서 발생한 지연이나 오류가 여러 서비스에 빠르게 퍼질 수 있습니다.

시스템 간에 임피던스 불일치가 존재하면, 원래 자연스럽게 맞춰지지 않았던 시스템들을 억지로 연결하게 됩니다. 이로 인해 복잡성이 더욱 빠르게 증가합니다. 시간이 지나면서 시스템은 이해하고, 운영하고, 안전하게 진화시키기 어려운 복잡성을 축적하게 됩니다.

성능은 속도만이 아니다

또 다른 미묘한 교훈은 latencyresponse time의 차이입니다. 이 용어들은 종종 서로 바꾸어 사용되지만, 동일하지 않습니다.

  • Response time – 사용자가 체감하는 시간입니다. 여기에는 처리 시간, 네트워크 지연, 그리고 대기열에서 기다리는 시간이 포함됩니다.
  • Latency – 요청이 처리되기 전에 대기하는 시간입니다.

사용자 입장에서는 이러한 서로 다른 지표가 중요합니다. 왜냐하면 인지된 성능과 전체 시스템 신뢰성에 영향을 미치기 때문입니다.

사람을 위한 시스템 설계

책은 또한 성능에 대한 일반적인 가정을 뒤흔들었습니다. 직관에 반해, 인‑메모리 데이터베이스가 디스크 읽기를 피한다는 이유만으로 항상 더 빠른 것은 아닙니다. 종종 더 빠른 이유는 메모리 내 데이터 구조를 디스크 저장에 적합한 형식으로 인코딩하는 오버헤드를 피하기 때문입니다. 이 세부 사항은 최적화에 대한 사고 방식과 성능 병목이 실제로 어디서 발생하는지를 바꾸어 줍니다.

신뢰성, 확장성, 유지보수성

챕터가 진행될수록 책은 신뢰성, 확장성, 유지보수성에 계속해서 돌아옵니다. 내가 특히 감사하게 여기는 점은 이 목표들이 순수하게 기술적인 목표로 제시되지 않고 인간적인 목표라는 점입니다.

  • Operable – 팀이 시스템을 계속 운영할 수 있다.
  • Understandable – 새로운 엔지니어가 설계를 빠르게 파악할 수 있다.
  • Evolvable – 시스템이 요구사항 변화에 맞춰 적응할 수 있다.

불필요한 복잡성을 축적한 시스템은 기계만 느려지게 하는 것이 아니라 사람도 느려지게 합니다. 학습을 어렵게 만들고, 위험을 증가시키며, 실수 비용을 높입니다. 여기서 단순함은 기능이 적다는 의미가 아니라 숨겨진 가정이 적고 불필요한 복잡성이 적다는 뜻입니다.

핵심 요점

에지 케이스를 고려하는 것은 프로그래밍에서 새로운 개념이 아닙니다. 대부분의 개발자는 초기에 널 값, 잘못된 입력, 명백한 경계 조건을 처리하는 방법을 배웁니다. 그 부분은 당연히 기대되는 일입니다.

시스템이 커질수록 덜 명확해지는 것이 다른 종류의 에지 케이스입니다. 이것들은 논리적 실수가 아니라, 흔히 당연하게 여겨지는 가정들입니다.

  • 시간은 부드럽게 앞으로 흐른다고 가정하지만, 실제로는 드리프트하거나 멈추거나 반복될 수 있습니다.
  • 디스크는 드물게 고장난다고 가정하지만, 고장은 통계적으로 기대되는 일입니다.
  • 네트워크는 켜져 있거나 꺼져 있다고 취급하지만, 실제로는 대부분 부분적인 장애이며 지연, 패킷 손실, 전반적인 성능 저하 형태로 나타납니다.
  • 메시지는 한 번만 도착하고 순서대로 온다고 가정하지만, 중복 및 순서 뒤바뀜이 흔히 발생합니다.

소규모에서는 이러한 가정이 충분히 잘 작동합니다. 대규모가 되면 이 가정들이 서로 영향을 주기 시작합니다.

시계가 조금씩 드리프트하고, 디스크가 가끔 고장 나며, 메시지가 늦게 도착하는 것은 각각 따로 보면 큰 문제가 아닐 수 있습니다. 하지만 이러한 작은 편차가 다수의 머신과 서비스에 걸쳐 발생하면, 에지 케이스가 아니라 일반적인 동작이 됩니다—즉, 결함이 실패로 전환되는 지점이죠.

해야 할 일

  • 모든 가능한 시나리오를 처리하려고 하지 말라—그것은 비현실적입니다.
  • 시스템이 의존하는 가정에 대해 의도적으로 접근하라.
  • 그 가정이 깨졌을 때 어떻게 될지 계획하라.

행복한 경로(정상 흐름)를 설계하는 것은 필요합니다. 편차를 염두에 두고 설계하는 것이 시스템을 장기간 안정적으로 유지하게 합니다.


이 내용이 공감된다면, 실제 시스템에서 깨졌을 때 가장 놀랐던 가정이 무엇인지 알려 주세요.

Back to Blog

관련 글

더 보기 »

Knotlog: PHP용 와이드 로깅

왜 로깅이 형편없는가와 이를 고치는 방법 loggingsucks.com(https://loggingsucks.com/)이 훌륭하게 설명하듯, 전통적인 로깅은 현대 환경에 대해 근본적으로 깨져 있다.

EP 7: ‘Join’ 세금 vs. ‘Storage’ 세금

SQL vs NoSQL 트레이드‑오프 시스템 설계에서 SQL과 NoSQL을 논할 때, 우리는 구문을 넘어 핵심 트레이드‑오프로 이동합니다. 실제 시스템에서는 데이터를 선택합니다.

시스템 설계 빠른 가이드

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