실리콘밸리 인프라 조직이 신입에게만 몰래 보여주는 '사이드 이펙트(Side Effect) 제어' 내부 문서

실리콘밸리 인프라 조직이 신입에게만 몰래 보여주는 '사이드 이펙트(Side Effect) 제어' 내부 문서

James·2026년 1월 16일·4

분산 시스템에서 '정확히 한 번(Exactly-Once)' 전송의 허상과 사이드 이펙트 제어를 위한 Durable Outbox 패턴 및 Chr2 아키텍처의 설계 철학을 다룹니다.

"정확히 한 번(Exactly-Once)만 전송해주세요."

기획자나 주니어 개발자가 이런 요구사항을 들고 올 때마다 저는 쓴웃음을 짓습니다. 분산 시스템에서 '정확히 한 번'이라는 말은 유니콘과 같습니다. 모두가 존재한다고 믿고 싶어 하지만, 실제로 본 사람은 아무도 없죠.

네이버 검색 인프라 팀 시절부터 AWS EC2 코어 팀을 거쳐 지금 넷플릭스에 이르기까지, 수많은 장애 보고서(Post-mortem)를 썼습니다. 그중 절반은 '중복 실행' 아니면 '누락' 때문이었습니다. 결제 요청이 두 번 날아가서 고객이 격분하거나, 푸시 알림이 유실되어 마케팅 팀이 뒤집어지는 상황들 말입니다.

대부분의 엔지니어는 "Kafka 쓰면 해결되는 거 아니에요?"라고 반문합니다. 순진한 생각입니다. 메시지 브로커가 '한 번' 전달해 주는 것과, 당신의 코드가 외부 API를 '한 번' 호출하는 건 완전히 다른 차원의 문제입니다.

오늘은 이 '사이드 이펙트(Side Effect)'라는 괴물을 어떻게 가두리 양식장처럼 통제할 수 있는지, 최근 화제가 된 `Chr2(Chronon)` 아키텍처를 통해 이야기해 보려 합니다.

이건 어디 가서 "라이브러리 하나 찾았다"고 말할 게 아니라, "분산 시스템의 생존 원리"로 이해해야 합니다.

"Exactly-Once는 거짓말이다"

`Chr2`의 리드미(Readme) 첫 줄에 적힌 문장입니다. 저도 이 말에 전적으로 동의합니다. 네트워크는 언제든 끊어지고, 서버는 언제든 죽으며, 디스크는 언제든 배신합니다.

우리가 할 수 있는 건 '정확히 한 번' 실행된 것처럼 보이게 만드는 것뿐입니다. 이를 위해 `Chr2`는 Viewstamped Replication (VSR)Durable Outbox 패턴을 결합했습니다.

Raft나 Paxos 같은 합의 알고리즘에 익숙한 분들이라면 VSR이 낯설 수도 있습니다. 하지만 이 녀석은 리더 선출과 데이터 복제를 아주 영리하게 분리해 냅니다.

핵심은 간단합니다. "의도(Intent)와 실행(Execution)을 분리하라."

3AM의 호출을 막아주는 'Durable Outbox'

주니어들이 가장 많이 하는 실수가 비즈니스 로직 중간에 외부 API 호출(사이드 이펙트)을 심어버리는 것입니다.

  1. DB에 주문 상태 업데이트
  2. 결제 API 호출 (여기서 네트워크 타임아웃 발생)
  3. DB 롤백 시도 (이미 커밋돼서 실패)

이 시나리오가 터지면 새벽 3시에 제 호출기(PagerDuty)가 울립니다. 데이터는 꼬였고, 돈은 나갔거나 안 나갔고, 수습은 불가능합니다.

`Chr2`는 이 문제를 Durable Outbox로 해결합니다.

  1. 의도 저장: 애플리케이션은 "결제 API를 호출하겠다"는 '의도'만을 생성합니다. 실제 호출은 하지 않습니다.
  2. 합의 및 저장: 이 의도는 VSR 합의 과정을 거쳐 로그에 안전하게 기록(Durability)됩니다.
  3. 실행: 오직 리더(Primary) 노드만이 펜싱 토큰(Fencing Token)을 들고 이 의도를 실행합니다.
  4. 확인(Ack): 실행이 끝나면 "실행했음"을 다시 로그에 기록합니다.

만약 실행 도중 리더가 죽으면? 새로운 리더가 선출되어 로그를 확인합니다. "어? 실행 의도는 있는데 완료(Ack) 기록이 없네?" 하고 다시 실행합니다.

여기서 중요한 건, 외부 시스템이 멱등성(Idempotency)을 지원해야 한다는 점입니다. 결국 '최소 한 번 실행(At-Least-Once)' + '멱등성' = '정확히 한 번(Exactly-Once)'이라는 공식이 성립하는 것이죠.

시스템을 죽이지 않는 디테일: Control Plane 분리

제가 이 프로젝트에서 감탄한 부분은 따로 있습니다. 바로 제어 평면(Control Plane)과 데이터 평면(Data Plane)의 분리입니다.

보통의 구현체들은 디스크 I/O가 밀리면 하트비트(Heartbeat) 전송도 늦어집니다. 그러면 멀쩡한 노드를 죽은 것으로 간주하고 리더 선거를 다시 시작합니다. 선거가 일어나는 동안 서비스는 멈춥니다(Stop-the-world). 트래픽이 몰려서 디스크가 바쁜 건데, 시스템은 스스로 자폭해 버리는 꼴이죠.

`Chr2`는 하트비트와 리더 선거 같은 제어 신호가 디스크 I/O에 차단되지 않도록 설계했습니다. `io_uring`과 `O_DSYNC`를 사용하여 동기적인 내구성을 보장하면서도, 비동기적인 처리를 가능하게 했습니다.

이런 디테일이 바로 "고가용성 99.999%"와 "매일 밤 장애 대응 회의"를 가르는 차이입니다.

코드가 아니라 설계를 훔쳐라

`Chr2`는 Rust로 작성되었고, 아직 실험적인 프로젝트일 수 있습니다. 당장 여러분 회사의 프로덕션에 이걸 도입하라는 말이 아닙니다.

하지만 이 설계 철학은 훔쳐야 합니다.

여러분이 작성하는 코드를 돌아보십시오. DB 트랜잭션 안에 HTTP 요청을 섞어놓진 않았습니까? 서버가 크래시 났을 때, 재시작된 서버가 중단된 작업을 어디서부터 다시 해야 할지 알고 있습니까? 외부 API가 5초 동안 응답하지 않으면, 전체 시스템의 스레드 풀이 고갈되어 헬스 체크조차 실패하진 않습니까?

엔지니어의 실력은 화려한 언어 스펙이나 최신 프레임워크 사용 능력이 아닙니다. 최악의 상황에서도 데이터 정합성을 지켜내는 집요함입니다.

"시스템은 반드시 실패한다"는 전제하에 설계하십시오. 범인 찾기(Blaming)보다 중요한 건, 누가 버튼을 잘못 눌러도 시스템이 알아서 수습할 수 있는 구조를 만드는 것입니다.

그래야 우리가 퇴근하고 다리를 뻗고 잘 수 있습니다. 우리의 인생 가용성을 위해서라도, 이 '사이드 이펙트'라는 놈은 확실히 격리해 둡시다.

James
James실리콘밸리 15년차 Staff SRE

연봉 3억과 캘리포니아의 햇살, 그리고 공황장애. 화려한 빅테크 간판 뒤에 가려진 '생존의 청구서'를 정산해드립니다. 기술적 탁월함만큼 중요한 건 엔지니어로서의 지속 가능성임을 병상에서 깨달았습니다.

James님의 다른 글

댓글 0

첫 번째 댓글을 남겨보세요!