멱등성 엔진: 트랜잭션 무결성 보장하기

SchemaBridge Team · 2025-12-22 · Idempotency, Consistency, Transactions

파편화된 세계에서 트랜잭션 무결성을 보장합니다. 분산 안전성을 위한 수학적 패턴.

"이중 청구" 악몽: 분산 시스템이 재시도를 싫어하는 이유

불안정한 네트워크와 일시적인 클라우드 리소스로 가득한 우리의 세계에서, 장애는 단순히 자주 발생하는 것이 아니라 항상 존재하는 상태입니다. 모든 시니어 엔지니어는 "고아가 된 액션(Orphaned Action)" 악몽을 경험해 본 적이 있을 것입니다. 사소한 네트워크 결함으로 시작해 치명적인 데이터 손상이나 금전적 손실로 끝나는 시나리오입니다. 이것은 CTO들을 밤새 잠 못 이루게 하는 전형적인 분산 시스템 장애 시나리오입니다.

1. 요청: 오케스트레이터가 Stripe에 "청구(Charge)" 요청을 보내거나 FedEx에 "배송(Ship)" 요청을 보냅니다.

2. 성공: 타사 API가 요청을 성공적으로 처리하여 고객 카드를 청구하거나 배송 라벨을 출력합니다.

3. 파티션: 반환 경로에서 짧은 네트워크 결함이 발생합니다. API로부터의 응답 — 필수적인 확인 신호 — 이 워커 머신에 도달하지 못합니다.

4. 재시도: 타임아웃을 감지한 워커는 표준 관행에 따라 프로세스가 실패했다고 올바르게 가정합니다. 재시도 정책에 따라 요청을 다시 전송합니다.

5. 중복: API가 이 특정 의도(intent)에 대한 고유한 식별자를 부여받지 않았기 때문에, 요청을 다시 처리합니다. API는 이를 새로운 트랜잭션으로 간주합니다. 고객은 두 번 청구되거나, 동일한 주문에 대해 배송 라벨이 두 개 생성됩니다.

이것은 코드 로직의 실패가 아니라 아이덴티티(Identity)의 실패입니다. 시간과 공간을 넘나드는 특정 의도를 고유하게 식별할 방법이 없다면, 시스템은 연결을 재시도할 때마다 고객의 데이터와 자금을 걸고 도박을 하는 셈입니다.

분산 트랜잭션은 죽었다: 멱등성이여 영원하라

모놀리식 아키텍처에서는 일관성을 보장하기 위해 2단계 커밋(Two-Phase Commit, 2PC)이나 전역 분산 락에 의존합니다. 이러한 도구들은 여러 연산을 단일한 진실의 단위로 취급할 수 있게 해줍니다. 하지만 SaaS API, 서버리스 워커, 폴리글랏 마이크로서비스로 파편화된 세계에서는 전역 트랜잭션이 환상에 불과합니다. 확장이 되지 않고, 막대한 지연을 유발하며, 대부분의 타사 프로바이더는 이를 지원하지 않습니다(그리고 앞으로도 지원하지 않을 것입니다). 이는 조직 경계를 넘나들며 물리적으로 달성 불가능한 리소스 "락킹(Locking)"을 요구합니다.

분산 일관성을 위한 유일하고 실행 가능한 경로는 멱등성(Idempotency)입니다. 수학적으로, 어떤 연산이 최초 적용 이후 결과를 바꾸지 않고 여러 번 적용될 수 있다면 그 연산은 멱등성을 가집니다. 대수적으로 표현하면: f(x) = f(f(x)). 엔지니어링 관점에서는, 시스템이 몇 번이든 실패하고 재시도해도 최종 결과는 항상 올바르다는 것을 의미합니다.

SchemaBridge의 접근 방식: 플러그형 멱등성 전략

대부분의 팀은 UUID를 수동으로 생성하고 데이터베이스에 저장함으로써 멱등성 문제를 해결하려 합니다. 이는 "키 관리 함정(Key Management Trap)"입니다. 멱등성 키를 관리하는 데(생성하고, 저장하고, 확인하고, 결국 삭제하는 데) 실제 비즈니스 로직만큼이나 많은 코드를 작성하게 됩니다. 이는 1부에서 다룬 "글루 코드 위기"의 또 다른 형태입니다.

SchemaBridge에서는 아이덴티티의 부담을 인프라 계층으로 옮깁니다. 우리는 플러그형 멱등성 전략(Pluggable Idempotency Strategies)을 사용해 아이덴티티를 자동으로 관리함으로써, 개발자의 할 일 목록에서 수동 부기 작업을 제거합니다.

전략의 해부: 유연한 아이덴티티

SchemaBridge의 멱등성 전략은 아이덴티티가 어떻게 도출되는지를 정의할 수 있게 해줍니다. 일부 시스템은 무작위 UUID에 의존하지만, 우리의 기본 전략은 다음을 허용합니다:

1. 워크플로 인스턴스 ID: 특정 여정의 고유하고 영속적인 ID. 이는 키가 특정 사용자나 액션에 속하도록 보장합니다.

2. 버텍스 아이덴티티: 그래프 내 특정 단계(예: "ChargeCustomer").

3. 구성 가능한 로직: IdempotencyStrategy 인터페이스를 통해, 엄격하게 결정론적인 해싱이 필요한 경우 페이로드 내용에서 키를 도출하는 커스텀 로직을 주입할 수 있습니다.

이 전략이 엔진에 의해 처리되기 때문에, 네트워크 타임아웃, 머신 크래시, 또는 수동 재시작으로 인해 단계가 재시도되더라도 결과 키는 안정적으로 유지됩니다.

해시 충돌 이론: 수십억 건의 트랜잭션에도 안전한가?

보안을 중시하는 아키텍트들이 흔히 하는 질문은 이렇습니다: "만약 서로 다른 두 트랜잭션이 동일한 해시를 생성한다면 어떻게 될까요?" 이는 해시 충돌(Hash Collision)이라고 알려져 있으며, 수십억 개의 이벤트를 처리하는 대용량 시스템에서는 결코 사소한 우려가 아닙니다.

안전성의 수학

SchemaBridge는 워크플로 ID와 버텍스 ID를 결합한 고유성에 의존합니다. 워크플로 ID는 전역적으로 고유(UUIDv4)하고, 버텍스 ID는 워크플로 정의 내에서 고유하기 때문에, 이 조합은 특정 실행 인스턴스에 대해 고유함이 보장됩니다.

레거시 API 다루기: "읽기-검증-쓰기" 패턴

안타깝게도 많은 레거시 시스템과 틈새 SaaS 프로바이더들은 멱등성 키를 기본적으로 지원하지 않습니다. Idempotency-Key 헤더가 없습니다. 이러한 "비멱등적(Un-idempotent)" 엔드포인트를 위해, SchemaBridge는 특화된 영속적 패턴인 읽기-검증-쓰기(Read-Verify-Write)를 지원합니다.

단일 "액션" 버텍스 대신, 엔진이 조율하는 3단계 시퀀스를 사용합니다:

1. 검증자 버텍스 (읽기): 엔진이 먼저 다운스트림 시스템에 레코드가 이미 존재하는지, 또는 액션이 이미 수행되었는지를 조회합니다(예: GET /orders?external_id=123). 이 호출은 엔진의 결정론적 아이덴티티에 의해 구동됩니다.

2. 조건 분기: JSONata(2부 참조)를 사용해 엔진이 응답을 확인합니다. 주문이 존재하면 "건너뛰기(Skip)" 상태로 전환합니다. 존재하지 않으면 계속 진행합니다.

3. 액션 버텍스 (쓰기): 검증자가 부정적인 결과를 반환한 경우에만 엔진은 실제 쓰기 연산(POST /orders)으로 진행합니다.

이것이 영속적인 이유

이 시퀀스 자체가 영속적 워크플로(Durable Workflow)로 감싸져 있기 때문에, 엔진은 "확인(Check)"과 "실행(Act)" 사이의 전환이 안정적으로 처리됨을 보장합니다. 확인과 실행 사이에 시스템이 크래시하면, 엔진은 상태를 복구하고 진행 전에 재검증하도록 구성할 수 있어, 경쟁 조건이 발생할 가능성의 창을 거의 0에 가깝게 최소화합니다.

"키 관리" 함정: DIY 멱등성이 대규모에서 실패하는 이유

많은 엔지니어링 팀은 자신들의 주 데이터베이스에 "멱등성 테이블"을 구축하려 시도합니다. 이는 궁극적으로 속도와 신뢰성을 저해하는 세 가지 치명적인 문제를 야기합니다:

1. 쓰기 병목: 이제 모든 API 호출이 토큰을 기록하기 위한 데이터베이스 쓰기를 필요로 합니다. 부하가 높은 상황에서는 멱등성 테이블이 경합의 주요 지점이 됩니다. 단일 재시도의 안전성을 보장하기 위해 전체 애플리케이션을 느리게 만드는 행 단위 락을 만들게 됩니다.

2. 정리 복잡성: 쓰레기 문제: 멱등성 키가 영원히 필요한 것은 아닙니다. 오래된 키를 정리하기 위한 백그라운드 프로세스나 TTL(Time-To-Live)이 필요합니다. 정리가 너무 공격적이면 느린 재시도 작업에서 이중 청구의 위험이 있습니다. 너무 느리면 데이터베이스가 계속 커지다 결국 크래시합니다. 이 균형을 관리하는 것은 상당한 운영 부담입니다.

3. 분산 상태 불일치: 데이터베이스 쓰기는 성공했지만 API 호출은 실패했다면 어떻게 될까요? 또는 워커가 API 호출 이후, 그러나 데이터베이스가 "완료됨"으로 업데이트되기 전에 크래시했다면? 결국 수동 개입으로만 해결할 수 있는 분산 상태 불일치가 발생합니다.

SchemaBridge는 실행 엔진과 긴밀하게 통합된 내부의 최적화된 키 스토어를 사용해 이러한 문제를 제거합니다. 키는 워크플로의 원자적 상태 커밋의 일부로 영속화되며, 워크플로가 자연스러운 종료 상태에 도달하면 자동으로 관리되고 최종적으로 폐기됩니다. 이는 "아이덴티티를 위한 가비지 컬렉션"입니다.

클라이언트 측 vs. 서버 측 토큰 생성

토큰은 어디서 생성되어야 할까요?

의도의 출발점(워크플로)에서 토큰을 생성함으로써, 데이터가 중간 게이트웨이나 프록시를 몇 번이나 거치든 관계없이 종단 간 무결성을 보장할 수 있습니다.

비교표: 일관성 모델

| 기능 | 데이터베이스 제약 조건 | DIY 멱등성 테이블 | SchemaBridge 엔진 |

| :--- | :--- | :--- | :--- |

| 범위 | 내부 DB만 | 자체 서비스만 | 모든 타사 SaaS API |

| 영속성 | 영구적 | 수동 TTL | 라이프사이클 인식 |

| 오버헤드 | 높음 (락) | 높음 (보조 IO) | 낮음 (원자적 상태 커밋) |

| 가시성 | 불투명 (DB 로그) | 부족함 (커스텀 로그) | 시각적 (추적 가능한 그래프) |

| 신뢰성 | 높음 | 낮음 (버그 발생 취약) | 높음 (인프라 수준) |

전문가 팁: 멱등성 체크리스트

1. 타임스탬프를 절대 사용하지 마세요: 키는 시간이 아니라 데이터를 기반으로 해야 합니다.

2. 키의 범위를 지정하세요: "배송"용 키가 동일한 입력값을 가지더라도 "청구"용 키와 충돌하지 않도록 하세요.

3. 409 충돌을 처리하세요: API가 409(충돌)를 반환하면, 입력값이 일치할 경우 시스템은 이를 이상적으로 성공으로 처리해야 합니다.

4. 영속적 이력을 사용하세요: 트랜잭션이 완전히 종료되고 감사되었다는 확신이 100% 들기 전까지는 키를 폐기하지 마세요.

5. 토큰 생성을 자동화하세요: 개발자가 멱등성 키를 추가하는 것을 기억해야 한다면, 결국 잊어버리게 됩니다. 이를 엔진으로 옮기세요.

결론: 아이덴티티는 진실의 근간이다

분산 시스템에서는 네트워크를 신뢰할 수 없고, 시계를 신뢰할 수 없으며, 응답을 신뢰할 수 없습니다. 진정으로 신뢰할 수 있는 것은 오직 아이덴티티뿐입니다.

멱등성 엔진은 SchemaBridge의 "영속적 진실(Durable Truth)" 약속의 근간입니다. 이러한 키들의 생성과 관리를 자동화함으로써, 우리는 수동 부기 작업의 부담 없이 복잡하고 신뢰할 수 있는 트랜잭션을 구축할 수 있게 해줍니다. 우리는 "이중 청구 악몽"을 아키텍처의 해결된 문제로 바꿉니다.

5부에서는 "병합(Merge)" 버텍스를 살펴보고, 경쟁 조건 없이 병렬 브랜치 전반에 걸쳐 상태를 동기화하는 방법을 다룹니다. '롱테일(Long Tail)' 문제와 수백만 개의 병렬 이벤트를 단일하고 일관된 상태로 조율하는 방법을 탐구합니다.

살펴보기