Project

Kafka Saga 패턴 도입해 보상 트랜잭션 구현하기 (ERP Kafka 도입 2편)

readyoun 2025. 1. 21. 14:48

 

지난 포스트에서는 Kafka 도입 배경과 그로 인한 효과를 살펴보았다. 

 

 

모놀리식 아키텍처 ERP 프로젝트에서 Kafka 도입하기 1편

정리한 내용이 생각보다 많아서 1편과 2편으로 나누어 작성한다.내용은 아래 목차 참고. 1편 목차 1. 도입: ERP 프로젝트의 시작과 모놀리식 아키텍처의 한계 2. REST Client로의 첫 번째 시도 • REST C

readyoun.tistory.com

 

Kafka를 통해 서비스 간 비동기 통신을 구현하고, 시스템의 확장성과 안정성을 크게 개선할 수 있었다. 하지만, Kafka만으로는 분리된 마이크로서비스 간의 데이터 일관성을 완벽히 보장하지 못하는 문제가 남아 있었다.

 

이번 글에서는 Kafka와 Saga패턴을 활용해서 데이터 정합성 문제를 어떻게 해결했는지를 살펴보겠다.


Kafka의 역할과 데이터 흐름의 전반적인 설계

 

Kafka는 ERP 시스템에서 서비스 간 데이터 전송의 중앙 허브 역할을 담당한다.

각 서비스는 데이터 변경, 생성, 삭제 등의 이벤트를 Kafka를 통해 주고받으며, 서로 독립적으로 동작한다.

출처: Team O`MZ

 

ERP 시스템은 인사관리-회계, 물류-생산, 공통 서비스와 같은 주요 도메인으로 나뉜다.

각 도메인별 서비스는 Kafka를 통해 도메인간 데이터를 전달하고 처리한다.

 

여전히 해결되지 않은 데이터 정합성 문제

Kafka 도입 후에도 데이터 정합성 문제가 발생했다.

더보기

데이터 정합성이란?

시스템 내에서 데이터가 서로 일치하고 일관되게 유지되는 상태를 의미한다.

 

예를 들어, 한 시스템에서 직원의 급여 정보를 300만 원으로 수정했다면, 관련된 모든 시스템에서도 동일하게 300만 원으로 반영된다.

 

따라서 시스템 전반에서 데이터가 언제나 정확하고 일관되게 유지되도록 보장하는 것이다. 

직원 데이터를 수정했지만 일부 서비스에 반영되지 않아 일관성이 깨지는 사례가 있었다.

 

인사회계 서비스에서 직원 데이터를 수정했다면 물류, 생산, 공통 서비스에서도 직원 수정 사항이 동일하게 반영되어야 한다. Kafka를 통해 `employee-update` 이벤트가 발행되면, 이를 구독하는 서비스들이 데이터를 업데이트 하게 된다. 

하지만 분산 시스템에서는 항상 데이터가 모든 서비스에서 완벽히 동기화된다고 보장할 수 없다. 예를 들어, 물류 서비스는 데이터를 정상적으로 업데이트했지만, 공통 서비스에서는 처리 중 오류가 발생하거나 이벤트를 놓쳐 데이터를 업데이트하지 못할 수 있다.

이러한 문제는 Kafka가 최종적 일관성(Eventual Consistency)을 보장하기 때문이다. 즉, 데이터 동기화가 어느 시점에서는 완료되지만, 즉각적인 강한 일관성(Strong Consistency)은 보장하지 않는다. 

 

더보기

Kafka는 메시지를 프로듀서(Producer)에서 브로커(Broker)로 전송하고, 컨슈머(Consumer)가 해당 메시지를 읽어가는 구조다. 

 

기본적으로 메시지 전달 보장에 초점이 맞춰져 있으며, 각 메시지가 순서대로 전달되도록 설계되어 있다.  

최종적으로 모든 컨슈머가 동일한 메시지를 처리하도록 설계된 거다. 

 

그런데 이때, 컨슈머가 메시지를 놓치거나 실패한 경우에도 Kafka는 메시지를 재전송하여 최종적으로 데이터를 동기화할 수 있도록 한다. Kafka는 메시지 전달을 비동기적으로 처리하며, 서비스 간 상태 동기화가 즉시 이루어지는 강한 일관성을 보장하지 않는다.


즉, 한 컨슈머가 이벤트를 놓치거나 처리에 실패한 경우, 다른 서비스와 데이터가 일시적으로 불일치할 수 있다.

 

최종적 일관성(Eventual Consistency)
최종적 일관성은 시스템의 각 구성 요소(서비스, 데이터베이스 등)가 시간이 지남에 따라 동일한 상태로 수렴하는 일관성 모델이다. 


Kafka는 이벤트 기반 비동기 메시징 시스템으로, 컨슈머가 이벤트를 소비하는 데 지연이나 실패가 발생할 수 있다. 그러나 이러한 상황에도 Kafka는 메시지 유실을 방지하고 컨슈머가 메시지를 재처리할 수 있는 메커니즘(Offset 관리, 메시지 재전송 등)을 제공한다.

 

즉각적인 강한 일관성(Strong Consistency)
강한 일관성은 모든 노드(서비스)가 동시에 동일한 데이터를 가지고 있음을 보장하는 모델이다. 

 

Kafka는 메시지를 비동기적으로 전달하므로, 메시지가 컨슈머로 전달되고 처리되는 동안의 일시적인 상태 불일치가 발생할 수 있다. 따라서 Kafka는 기본적으로 강한 일관성을 제공하지 않으며, 최종적 일관성을 목표로 작동한다고 볼 수 있다. 

 

참고 자료

1. Apache Kafka Documentation

2. Microsoft Azure: Distributed System Design Patterns

3. Amazon AWS: Event-Driven Architecture



만약 공통 서비스에서 데이터가 반영되지 않았다면, 사용자 입장에서는 직원 급여가 잘못 표시되거나, 회계 처리에서 잘못된 데이터를 참조하게 되어 혼란과 장애를 초래할 수 있다.

 

이러한 문제를 해결하기 위해서는 Saga 패턴과 같은 보상 트랜잭션 설계를 통해 데이터를 복구하거나, 상태를 동기화하는 추가적인 설계가 필요하다.

 

[ 문제 상황: 직원 정보 수정 예 ] 

1. 직원 데이터 수정 이벤트 발행
인사회계 서비스에서 직원 데이터가 수정되면, Kafka를 통해 `employee-update` 이벤트를 발행한다.

2. 다른 서비스로 이벤트 전파
물류, 생산, 공통 서비스가 이 이벤트를 구독하여 각자의 데이터베이스에 동일한 수정 사항을 반영한다.

3. 문제 발생
물류 서비스는 정상적으로 업데이트되었지만, 공통 서비스에서 처리 중 오류가 발생해 데이터가 반영되지 않았다.
이로 인해 서비스 간 데이터 불일치 문제가 발생했다.

결과: 데이터 불일치로 인한 혼란
인사회계 서비스는 수정된 직원의 급여를 300만 원으로 반영했지만, 공통 서비스는 여전히 이전 데이터(예: 30만 원)를 보여준다.
• 사용자는 어떤 데이터를 신뢰해야 할지 혼란에 빠지고, 데이터 불일치로 인해 인사와 회계 처리가 엉망이 된다.

 

해결책: Saga 패턴

Saga 패턴이란?

분산 시스템에서 트랜잭션 상태를 중앙에서 관리하며, 데이터의 일관성안정성을 보장하는 설계 패턴으로, Saga Orchestrator를 사용한다. 

더보기

Saga 패턴은 분산 트랜잭션 관리의 설계 패턴을 지칭하며, Saga Orchestrator는 이 패턴을 구현하는 데 사용되는 구체적인 방식 또는 컴포넌트이다.

이 패턴은 다음의 두 가지 핵심 메커니즘을 제공한다.

 

1. 트랜잭션 상태 추적
각 서비스의 트랜잭션 상태를 추적하며, 성공/실패 여부를 기록한다.

 

2. 보상 트랜잭션
특정 서비스에서 트랜잭션 실패 시, 이전 상태로 롤백하는 메커니즘을 제공한다.

 

Saga Orchestrator란?

출처: Team O`MZ

Saga Orchestrator의 동작 원리

1. 트랜잭션 상태 중앙 관리

 

중앙 저장소 역할

Saga Orchestrator는 트랜잭션 상태를 중앙 저장소(DynamoDB 등)에 저장하며,

이를 통해 트랜잭션의 진행 상황을 실시간으로 추적한다.

 

상태 유형에는 성공(Success)실패(Fail)대기(Pending) 등이 있다.

(기본적으로는 처음에 Pending으로 저장한다. 성공/실패 여부는 작업 이벤트를 완료한 후에 반영한다.)

 

이 저장소는 장애 상황에서도 데이터를 복구하고, 보상 트랜잭션을 실행하기 위한 기준 정보를 제공한다. 

 

분산 환경에서는 트랜잭션 상태가 여러 서비스에 걸쳐 분산되므로, 중앙에서 이를 일관성 있게 관리하지 않으면 데이터 정합성 문제가 발생할 수 있기 때문에 이러한 중앙 관리가 중요하다. 

 

2. Kafka를 통한 이벤트 발행 및 처리

 

Kafka Broker의 역할

Kafka는 서비스 간 통신을 위한 중앙 메시징 허브 역할을 한다. 모든 서비스는 Kafka의 토픽을 통해 데이터를 주고받는다. 

더보기

동작 예시

 

이벤트 발행(Producer):

예를 들어, 회계 및 인사 서비스가 직원 데이터를 수정하면 employee-update라는 이벤트가 Kafka의 해당 토픽에 발행된다.

 

이벤트 소비(Consumer):

이 이벤트는 다른 서비스(예: 물류, 공통 서비스)로 전달되어 각 서비스가 필요한 로직을 수행한다.

 

Kafka의 장점:

1. 비동기성: 서비스 간 결합도를 낮추고, 서로 독립적으로 작동할 수 있게 한다.

2. 확장성: 새로운 서비스가 추가되어도 Kafka 토픽을 구독하기만 하면 통합이 가능하다.

3. 트랜잭션 실패 시 보상 트랜잭션 실행

 

오류 감지 및 처리

만약 트랜잭션 중 하나의 서비스에서 오류가 발생하거나 응답이 지연되면, Saga Orchestrator는 이를 감지한다. 

실패한 트랜잭션은 보상 트랜잭션 메시지를 통해 롤백되며, 이전 상태로 복구된다. 

 

보상 트랜잭션의 실행 과정

1. Kafka를 통해 롤백 이벤트를 발행한다.

2. 각 서비스가 해당 이벤트를 구독하고, 롤백 작업을 수행한다. 

 

효과

데이터 정합성을 유지하며, 장애 상황에서도 안정성을 보장한다.

 

3줄 요약

1. Saga Orchestrator는 Kafka를 중심으로 분산 트랜잭션을 관리하며, 트랜잭션 상태를 중앙 저장소(DynamoDB 등)에 기록하여 상태를 실시간으로 추적한다. 

2. Kafka Broker는 서비스 간 데이터를 비동기적으로 교환하며, 이벤트 발행(Producer)과 소비(Consumer)를 통해 서비스 간 통신을 처리한다.

3. 트랜잭션 도중 실패가 발생하면 Orchestrator는 보상 트랜잭션 메시지를 통해 작업을 롤백하고 데이터 정합성을 유지한다. 

 

참고

Apache Kafka 공식 문서: Kafka Documentation

Saga Pattern 설명: Microsoft Azure Architecture

 

Kafka와 Saga 패턴을 활용한 사원정보 수정 로직

출처: Team O`MZ


1. Kafka에 이벤트 발행
회계 & 인사 서비스 → Kafka Server (employee-update 토픽)

2. Saga Consumer 활성화
Kafka 이벤트 → Saga Listener Lambda

3. DynamoDB에 트랜잭션 데이터 저장
Saga Start → DynamoDB 기록

4. 트랜잭션 전파
Saga Start 이벤트 → 공통 서비스, 물류/생산 서비스

5. 트랜잭션 상태 업데이트
Kafka 작업 완료 이벤트 → Saga Listener Lambda → DynamoDB 상태 변경

6. 오류 시 롤백
오류 발생 → 보상 트랜잭션 이벤트 발행 → 각 서비스 롤백

 

Saga Listener와 Lambda 자세히 알아보기 

더보기

Saga Listener와 Lambda

 

Kafka는 이벤트를 처리하는 기능을 제공하지만, 이벤트를 감지하고 처리하는 로직은 별도로 구현해야 한다. 이때 Lambda 함수를 트리거하여 이벤트를 처리하는 역할을 하는 것이 Saga Listener다. 

 

Saga Consumer는 AWS Lambda의 Saga Listener를 활성화해 Saga Start 이벤트를 발행한다. 

 

Saga Listener는 Kafka에서 발행되는 이벤트를 감지하고 처리하기 위해 사용되는 컴포넌트인데, 

Kafka와 Lambda 사이의 연결을 관리하기 위해 필요한 '다리' 역할이다. 

Kafka의 이벤트를 처리하려면 이 리스너를 활용해서 이벤트 관리를 훨씬 간단하고 효율적으로 할 수 있다. 

이외에도 필터링 및 전처리나 재시도 메커니즘을 구현할 수 있다. 

 

이 리스너는 Lambda 함수로 구현하는데, 서버리스 컴퓨팅을 제공해서 이벤트 기반 아키텍처에 최적화되어 있다. 

 

즉, Consumer가 이벤트를 구독하고 있다가, 관련 이벤트가 발생하면 Listener Lambda를 트리거하는 것이다. 

 

이렇게 Saga Listener Lambda가 활성화되면, 

1. DynamoDB에 트랜잭션 상태 정보를 기록한다.

2. 트랜잭션 진행 상황을 모니터링하다가, 특정 서비스에서 오류가 발생하면 보상 트랜잭션 이벤트를 Kafka에 발행한다. 

 

Lambda는 확장성이 뛰어나고, 관리 overhead가 적어 Saga Listener 같은 이벤트 처리 로직을 효과적으로 구현할 수 있다.

 

결과적으로 Saga Listener Lambda는 Kafka 이벤트를 감지하고, 트랜잭션 상태를 관리하며, 필요 시 보상 트랜잭션을 실행하는 핵심 역할을 한다. 

 

*참고로 DynamoDB는 서버리스 데이터베이스 서비스로, AWS Lambda와 자연스럽게 통합된다. 다중 항목 작업에서 ACID 트랜잭션을 지원해 데이터 정합성을 보장한다는 이점이 있다. 특히, ERP와 같은 대규모 트랜잭션 처리 시스템에서 성능 요구를 충족시키에 적합한 고성능 및 저지연 이점이 있다(초당 수백만 건 읽기/쓰기 요청 처리 및 10ms 이하의 짧은 지연 시간 제공). 

 

Kafka와 Saga 패턴을 기반으로 한 사원정보 수정 로직은 분산 환경에서 데이터의 정합성을 유지하고, 트랜잭션 상태를 효과적으로 관리하기 위한 설계이다. 이를 통해 각 서비스는 보상 트랜잭션 이벤트를 받으면, 자신의 데이터베이스에서 이전 상태로 데이터를 복구하는 롤백 작업을 수행할 수 있다.

 

이 로직의 흐름을 단계별로 설명하자면,

 

1. 이벤트 발행 (Producer)
회계 & 인사 서비스는 사원정보 변경 요청을 처리한 후, 변경 내용을 Kafka의 특정 토픽(예: employee-update)에 이벤트로 발행(Publish) 한다. 이 이벤트는 Kafka Broker에 저장되어 구독자(Consumer)가 사용할 수 있게 된다.

 

2. 이벤트 소비 및 트리거 활성화 (Consumer 및 Lambda)
Kafka에 발행된 이벤트는 Saga Consumer가 이를 구독하여 감지한다. Saga Consumer는 AWS Lambda의 Saga Listener를 활성화하여 Saga Start 이벤트를 발행한다.

 

이 과정에서 Lambda는 변경된 트랜잭션 데이터를 DynamoDB에 저장하여 상태를 추적한다. 저장된 데이터에는 트랜잭션 ID, 상태(예: Pending), 서비스 그룹 정보 등이 포함된다.

 

3. 트랜잭션 상태 전파
Saga Start 이벤트는 공통 서비스 및 물류/생산 서비스와 같은 다른 서비스로 전파된다. 각 서비스는 이 이벤트를 소비(Consume)하여 변경된 데이터를 처리한다. 처리 완료 후, 각 서비스는 작업 완료 이벤트를 다시 Kafka에 발행한다.

 

4. 트랜잭션 상태 업데이트
Kafka에 발행된 작업 완료 이벤트를 Saga Listener Lambda가 다시 감지한다. Lambda는 트랜잭션 상태를 업데이트하며, 모든 서비스가 정상적으로 데이터를 처리했는지 확인한다. 상태가 정상(Completed)일 경우, 성공적으로 트랜잭션이 종료된다.

 

5. 오류 발생 시 보상 트랜잭션
특정 서비스(예: 공통 서비스)에서 오류가 발생하거나 응답이 지연된 경우, Saga Listener Lambda는 보상 트랜잭션 이벤트를 발행한다. 이 이벤트를 기반으로 이전에 수행된 작업이 롤백되며, 데이터 정합성이 유지된다.

 

3줄 요약

1. 인사 서비스가 Kafka에 사원 정보 수정 이벤트를 발행하면 Saga Listener Lambda가 이를 감지하여 DynamoDB에 상태를 기록하고, 변경 내용을 다른 서비스로 전파한다.

2. 각 서비스는 이를 처리하고 완료 이벤트를 발행하며, 모든 작업이 성공적으로 완료되면 트랜잭션 상태를 완료로 업데이트한다.

3. 장애 발생 시 보상 트랜잭션 이벤트를 통해 이전 상태로 롤백하여 데이터의 정합성을 보장한다.

 


 

해당 아키텍처의 주요 장점

1. 데이터 일관성 유지

Saga 패턴을 통해 분산 환경에서 데이터 정합성을 보장한다.

 

1) 트랜잭션 상태 추적: 중앙 저장소(DynamoDB)를 활용하여 각 서비스의 트랜잭션 상태(성공, 실패, 대기)를 기록하고, 이를 실시간으로 추적할 수 있다.

2) 보상 트랜잭션 실행: 트랜잭션 실패 시 보상 트랜잭션을 통해 이전 상태로 복구하여 데이터의 일관성을 유지한다.

 

예시: 직원 데이터 수정 중 물류 서비스가 실패했을 때, Saga Orchestrator가 모든 관련 작업을 롤백하여 데이터 불일치를 방지한다. 

 

2. 효율적인 트랜잭션 관리

각 서비스가 독립적으로 작업을 수행하면서도 중앙에서 트랜잭션 상태를 통합 관리할 수 있다. 

 

1) 서비스 독립성: Kafka를 사용한 비동기 통신으로 서비스 간 결합도를 낮추고, 각 서비스가 개별적으로 데이터를 처리할 수 있게 한다. 

2) 중앙 관리: Saga Listener가 모든 트랜잭션 상태를 DynamoDB에 기록하여, 복잡한 분산 트랜잭션도 쉽게 관리 가능한다.

3) 확장성: 새로운 서비스가 추가될 경우, Kafka 토픽 구독만 설정하면 손쉽게 시스템에 통합될 수 있다. 

 

3. 장애 대응

장애 발생 시 빠르게 복구할 수 있는 체계적인 메커니즘을 제공한다. 

 

1) 장애 감지: Kafka 이벤트와 DynamoDB 상태를 모니터링하여 트랜잭션 실패를 즉시 감지한다.

2) 롤백 처리: Saga Orchestrator가 보상 트랜잭션 이벤트를 발행하여, 장애로 인해 발생한 데이터 불일치를 자동으로 복구한다. 

3) 안정성 강화: Kafka의 리더-팔로워 복제 구조로 메시지 손실을 방지하고, 장애 상황에서도 메시지 처리가 안전하게 이어질 수 있다.

 

예시: 직원 데이터 수정 중 공통 서비스에서 응답이 지연되더라도, 다른 서비스에는 영향을 주지 않고 복구 과정을 진행한다. 

 

4. 고성능 데이터 처리

Kafka를 기반으로 데이터를 파티션 단위로 병렬 처리하며, 대량의 트랜잭션도 효율적으로 처리할 수 있다. 

 

1) 파티셔닝: 데이터를 파티션 단위로 나누어 병렬 처리를 가능하게 하며, 트래픽 증가에도 병목현상을 방지한다. 

2) Offset 관리: 데이터 손실 없이, 장애 복구 시에도 정확히 이어서 작업할 수 있다. 

 

5. 확장 가능성

아키텍처는 서비스의 확장성과 유연성을 극대화하도록 설계되었다. 

 

1) 새로운 기능 추가: Kafka 토픽을 활용하면 새로운 도메인 서비스를 기존 시스템에 간단히 통합할 수 있다. 

2) 비용 절감: 독립적인 서비스 아키텍처를 통해 특정 서비스에만 자원을 할당하거나 확장할 수 있다.

 


 

이처럼 Kafka와 Saga 패턴을 활용한 분산 트랜잭션 관리 아키텍처는 ERP 시스템의 데이터 일관성과 안정성을 크게 향상시켰다. 서비스 간 결합도를 낮추고 독립성을 보장하면서도, 중앙에서 트랜잭션 상태를 체계적으로 관리할 수 있게 되었다. 

 

특히 장애 상황에서도 신속한 복구가 가능하도록 설계되어, 사용자에게 신뢰할 수 있는 데이터를 제공할 수 있게 되었다. 이는 단순한 기술 적용을 넘어, 전체 시스템의 안정성과 확장성을 높이는 핵심적인 역할을 했다. 

 

다음에는 Redis를 알아보고, 프로젝트에 적용한 Redis 전략을 소개하겠다.