← 모든 글

fail-open vs fail-closed — 우리의 결제 시스템 선택

결제 시스템의 fail-open vs fail-closed는 전체를 하나로 처리할 수 없다. 단계별 가역성(reversibility)이 설계 기준이며, 모니터링 없는 fail-open은 선택이 아니라 무관심이다.

왜 결제·정산 현장에서 이 선택이 유독 무거운가

일반적인 웹 서비스에서 fail-closed는 "페이지가 안 보인다"는 UX 문제로 끝난다. 결제에서는 다르다. fail-closed는 "결제가 안 됐다 → 고객이 이탈했다 → 매출이 증발했다"는 직접적인 비즈니스 손실로 이어진다. 반대로 fail-open은 "잘못된 거래가 통과됐다 → 사기 또는 과지급 → 회수 불가 손실"로 직결될 수 있다. 두 비용 모두 돈이고, 어느 쪽이 더 크냐는 시스템마다, 단계마다 다르다.

이 비대칭성이 "결제니까 당연히 fail-closed"라는 직관을 절반만 맞게 만드는 이유다. 결제 흐름은 단일 판단이 아니라 여러 서비스가 엮인 체인이다. 외부 PG사 연동, 잔액 조회, 한도 검증, 거래 기록이 순서대로 이어지고, 각 단계는 실패 가능성과 실패의 파급력이 다르다. 전체를 하나의 정책으로 처리하려는 순간, 설계가 틀어진다.

우리가 숫자로 확인한 fail-closed의 실제 비용

HEDVION의 초기 설계는 모든 단계를 fail-closed로 처리했다. 이론적으로 타당해 보였다. 문제는 잔액 조회 서비스에서 나타났다. 이 서비스는 쓰기 경합이 잦은 DB를 바라보고 있었고, 피크 타임에 p99 응답 시간이 400ms에서 2초 이상으로 튀는 일이 월 23회 발생했다. 우리 시스템의 타임아웃은 500ms였으니, 해당 시간대 거래 요청 중 약 58%가 "잔액 확인 불가"로 차단됐다. 실제 잔액은 충분한데도.

이 숫자를 처음 마주했을 때 팀에서 나온 반응은 "이게 사기 방지보다 더 큰 손실 아닌가"였다. 실제로 같은 기간 사기 거래 차단 건수와 비교하니, fail-closed로 인한 정상 거래 차단이 7배 많았다. 비율로만 보면 fail-closed가 오히려 더 많은 비용을 만들고 있었다. 이 지점에서 우리는 "전체 fail-closed"라는 설계 전제를 버렸다.

단계별로 다른 기준: "되돌릴 수 있는가"가 핵심이다

이 문제를 해결하면서 도달한 원칙은 단순하다: 해당 단계에서 잘못된 결정이 나왔을 때 되돌릴 수 있는가? 되돌릴 수 있는 단계는 fail-open의 여지가 생기고, 되돌리기 어렵거나 불가능한 단계는 fail-closed를 유지해야 한다.

잔액 조회는 되돌릴 수 있는 단계다. 캐시된 잔액(최대 30초 지연)으로 거래를 통과시키고, 이후 비동기 정산 단계에서 실제 잔액과 대조해 불일치가 있으면 자동으로 역전(reversal) 트리거를 건다. 실제 결제 사기에서 30초 안에 잔액이 극적으로 달라지는 케이스는 극히 드물고, 그런 케이스는 한도 검증과 사기 방지 레이어에서 따로 걸러진다. 반면 한도 검증과 사기 방지 로직은 fail-closed를 유지한다. 이 단계에서 오판이 발생하면 금전 손실이고 회수가 불가능한 경우가 많다.

구조를 바꾼 이후 피크 타임 거래 차단율은 5~8%에서 0.3% 이하로 떨어졌다. 비동기 정산에서 발생한 불일치는 전체 거래의 0.01% 미만이었고, 그 중 실제 회수 처리가 필요했던 건은 한 건도 없었다. 성과는 수치로 확인됐다.

모니터링이 없는 fail-open은 선택이 아니라 무관심이다

fail-open 구간을 도입하면서 팀 내에서 가장 오래 논의한 부분은 모니터링 설계였다. 기술적으로 fail-open 자체는 어렵지 않다. 어려운 건 그 구간에서 무슨 일이 일어나는지 정확하게 보는 시스템을 만드는 것이다.

우리가 만든 구조는 세 가지다. 첫째, 비동기 정산 파이프라인에서 잔액 불일치가 발생하면 즉시 Slack 알림 + 자동 티켓 생성. 둘째, fail-open으로 통과된 거래에 별도 태그를 붙여 이후 추적 가능하게 유지. 셋째, 일별·주별 불일치 비율 대시보드로 추세 모니터링. 이 구조가 작동하면서 예상치 못한 부산물이 생겼다. 알림이 실제로 울린 경우 중 두 건이 로직 버그의 징후였다 — 하나는 정산 계산식에서 소수점 반올림 처리 오류, 다른 하나는 특정 통화 코드에서 환율 적용이 누락되는 문제였다. 모든 걸 fail-closed로 처리했다면 이 버그들은 훨씬 늦게 발견됐을 것이다. fail-open + 촘촘한 모니터링이 오히려 시스템 품질을 높이는 역할을 했다.

팀 역량도 설계 조건에 포함된다

fail-open vs fail-closed는 순수 기술 결정이 아니다. 팀이 감당할 수 있는 운영 역량에 대한 결정이기도 하다. fail-open을 선택하면 비동기 정산, 불일치 감지, 역전 처리 로직을 유지·운영해야 한다. 온콜 당직이 있어야 하고, 알림이 울릴 때 즉각 대응할 수 있는 체계가 있어야 한다. 작은 팀에서는 이 운영 부담이 생각보다 크다.

HEDVION처럼 작은 팀이 이 구조를 유지할 수 있었던 건, 역전 처리를 최대한 자동화했기 때문이다. 사람이 개입해야 하는 케이스는 전체 불일치의 10% 미만으로 설계했고, 나머지는 파이프라인이 자동으로 정리한다. 만약 이 자동화 없이 모든 불일치를 수동으로 처리해야 했다면, fail-open은 오히려 더 큰 운영 부담이 됐을 것이다. 설계 선택과 운영 역량은 서로를 전제로 한다.

지금 바로 적용할 수 있는 판단 기준

fail-open vs fail-closed를 단계별로 결정할 때 실제로 물어봐야 할 질문 목록이다.

  1. 이 단계에서 잘못된 결정이 나왔을 때, 얼마 안에 되돌릴 수 있는가? 되돌리기 불가능하거나 비용이 클수록 fail-closed가 맞다. 가역성이 먼저다.
  2. 타임아웃·오류율 데이터가 있는가? 직관이 아니라 숫자로 판단해야 한다. 어느 단계가 실제로 자주 느려지는지, fail-closed로 차단된 정상 거래가 얼마나 되는지 측정부터 하라.
  3. 비동기 정산 파이프라인을 만들 수 있는가? fail-open을 선택한다면 이게 없으면 시작하지 마라. 파이프라인 없는 fail-open은 구멍 뚫린 방어막이다.
  4. 자동 역전(reversal) 처리가 가능한가? 수동 대응 비율이 높으면 팀 운영 비용이 선형으로 증가한다. 자동화 비율 목표를 먼저 정하고 설계하라.
  5. 불일치 알림이 울렸을 때 즉각 대응하는 체계가 있는가? 모니터링 없는 fail-open은 시스템이 아니라 방치다. 알림 → 확인 → 처리의 SLA를 팀 내에서 명시적으로 정해둬야 한다.

이 다섯 가지 질문에 단계별로 답하면, fail-open과 fail-closed를 혼합하는 설계가 자연스럽게 나온다. "전체를 하나의 정책으로" 처리하려는 유혹을 버리는 것이 시작이다.

— by slecs

* 위 링크는 인프런 affiliate 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

📚 추천 강의
한 입 크기로 잘라먹는 바이브코딩 (with Claude Code)
Claude Code로 바이브코딩, 개발자라면 꼭 들어야 할 필수 강의
강의 보러가기 →

* 위 추천 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.