트랜잭션 동작 원리

트랜잭션 동작 원리 (How transactions work)

이번에는 Pulsar 트랜잭션을 구성하는 컴포넌트들이 서로 어떻게 협력하는지 살펴볼게요. 트랜잭션 코디네이터, 트랜잭션 로그, 트랜잭션 버퍼 같은 핵심 개념부터 실제 데이터가 흐르는 과정까지 차근차근 따라가 보면, "왜 이런 구조인지"가 자연스럽게 이해될 거예요. 완전한 설계 상세는 PIP-31: Transactional Streaming을 참고해요.

출처: 문서

본문

이 섹션은 트랜잭션 컴포넌트와 그 컴포넌트들이 어떻게 함께 동작하는지 설명해요. 완전한 설계 상세는 PIP-31: Transactional Streaming을 참고하세요.

핵심 개념 (Key concept)

트랜잭션이 어떻게 동작하는지 이해하기 위한 사전 지식으로, 다음 핵심 개념을 알아두는 게 중요해요.

트랜잭션 코디네이터 (Transaction coordinator)

트랜잭션 코디네이터(TC, Transaction Coordinator)는 Pulsar 브로커 안에서 실행되는 모듈이에요.

  • 트랜잭션의 전체 수명주기를 관리하고, 트랜잭션이 잘못된 상태로 빠지지 않도록 방지해요.
  • 트랜잭션 타임아웃을 처리하고, 트랜잭션 타임아웃이 지나면 트랜잭션이 중단(abort)되도록 보장해요.

트랜잭션 로그 (Transaction log)

모든 트랜잭션 메타데이터는 트랜잭션 로그에 영속돼요. 트랜잭션 로그는 Pulsar 토픽으로 뒷받침(backed)돼요. 트랜잭션 코디네이터가 크래시해도 트랜잭션 로그에서 트랜잭션 메타데이터를 복구할 수 있어요.

트랜잭션 로그는 트랜잭션 안의 실제 메시지가 아니라 트랜잭션 상태를 저장해요(실제 메시지는 실제 토픽 파티션에 저장돼요).

트랜잭션 버퍼 (Transaction buffer)

트랜잭션 안에서 토픽 파티션으로 생산된 메시지는 그 토픽 파티션의 트랜잭션 버퍼(TB, Transaction Buffer)에 저장돼요. 트랜잭션 버퍼 안의 메시지는 트랜잭션이 커밋될 때까지 컨슈머에게 보이지 않아요. 트랜잭션이 중단되면 트랜잭션 버퍼 안의 메시지는 버려져요.

트랜잭션 버퍼는 진행 중(ongoing) 및 중단된(aborted) 트랜잭션을 모두 메모리에 저장해요. 모든 메시지는 실제 파티셔닝된 Pulsar 토픽으로 전송돼요. 트랜잭션이 커밋된 후에는 트랜잭션 버퍼의 메시지가 구체화(materialized)되어 컨슈머에게 보여요. 트랜잭션이 중단되면 트랜잭션 버퍼의 메시지는 버려져요.

트랜잭션 ID (Transaction ID)

트랜잭션 ID(TxnID)는 Pulsar에서 고유한 트랜잭션을 식별해요. 트랜잭션 ID는 128비트예요. 가장 높은 16비트는 트랜잭션 코디네이터의 ID를 위해 예약돼 있고, 나머지 비트는 각 트랜잭션 코디네이터에서 단조 증가하는 숫자로 사용돼요. TxnID로 트랜잭션 크래시 위치를 쉽게 찾을 수 있어요.

보류 중인 ack 상태 (Pending acknowledge state)

보류 중인 ack 상태(pending acknowledge state)는 트랜잭션이 완료되기 전에 트랜잭션 안의 메시지 확인(ack)을 유지해요. 메시지가 보류 중인 ack 상태에 있으면, 그 메시지가 보류 중인 ack 상태에서 제거될 때까지 다른 트랜잭션이 그 메시지를 ack할 수 없어요.

보류 중인 ack 상태는 보류 중인 ack 로그(cursor ledger)에 영속돼요. 새 브로커는 보류 중인 ack 로그에서 상태를 복구해 ack가 손실되지 않도록 보장해요.

데이터 흐름 (Data flow)

트랜잭션을 디버깅하거나 성능을 위해 튜닝하려면 다음 다이어그램과 설명을 살펴보세요. 트랜잭션의 데이터 흐름은 여러 단계로 나눌 수 있어요.

1. 트랜잭션 시작 (Begin a transaction)

Pulsar에 트랜잭션을 도입하기 전에는, 프로듀서를 만들고 메시지를 브로커로 보내 데이터 로그에 저장했어요. 이제 트랜잭션을 시작하는 단계를 함께 살펴볼게요.

단계 설명
1.1 첫 번째 단계는 Pulsar 클라이언트가 트랜잭션 코디네이터를 찾는 거예요.
1.2 트랜잭션 코디네이터가 트랜잭션에 트랜잭션 ID를 할당해요. 트랜잭션 로그에는 트랜잭션 ID와 상태(OPEN)로 기록되어, 트랜잭션 코디네이터가 크래시해도 트랜잭션 상태가 영속되도록 보장해요.
1.3 트랜잭션 로그가 트랜잭션 ID 영속 결과를 트랜잭션 코디네이터로 보내요.
1.4 트랜잭션 상태 항목이 기록된 후, 트랜잭션 코디네이터가 트랜잭션 ID를 Pulsar 클라이언트로 되돌려줘요.

2. 트랜잭션으로 메시지 게시 (Publish messages with a transaction)

이 단계에서 Pulsar 클라이언트는 트랜잭션 루프에 들어가, 트랜잭션을 구성하는 모든 메시지에 대해 컨슘-처리-프로듀스 작업을 반복해요. 이는 긴 단계로, 여러 프로듀스 및 ack 요청으로 구성될 수 있어요.

단계 설명
2.1.1 Pulsar 클라이언트가 새 토픽 파티션으로 메시지를 생산하기 전에, 트랜잭션 코디네이터에 파티션을 트랜잭션에 추가하라는 요청을 보내요.
2.1.2 트랜잭션 코디네이터가 트랜잭션의 파티션 변경을 영속화하기 위해 트랜잭션 로그에 기록해요. 이렇게 하면 트랜잭션 코디네이터가 트랜잭션이 처리하는 모든 파티션을 알 수 있어요. 트랜잭션 코디네이터는 end-partition 단계에서 각 파티션의 변경을 커밋하거나 중단할 수 있어요.
2.1.3 트랜잭션 로그가 새 파티션 기록 결과(메시지 생산용)를 트랜잭션 코디네이터로 보내요.
2.1.4 트랜잭션 코디네이터가 새로 생산한 파티션을 트랜잭션에 추가한 결과를 보내요.
2.2.1 Pulsar 클라이언트가 파티션에 메시지 생산을 시작해요. 이 부분의 흐름은 일반 생산 흐름과 같지만, 트랜잭션이 생산하는 메시지 배치에는 트랜잭션 ID가 포함된다는 점만 달라요.
2.2.2 브로커가 메시지를 파티션에 써요.

3. 트랜잭션으로 메시지 ack (Acknowledge messages with a transaction)

이 단계에서 Pulsar 클라이언트는 트랜잭션 코디네이터에 요청을 보내고, 새 서브스크립션이 트랜잭션의 일부로 ack돼요.

단계 설명
3.1.1 Pulsar 클라이언트가 트랜잭션 코디네이터에 ack된 서브스크립션을 추가하라는 요청을 보내요.
3.1.2 트랜잭션 코디네이터가 서브스크립션 추가를 로그로 기록해요. 이렇게 하면 트랜잭션이 처리하는 모든 서브스크립션을 알 수 있고, 종료 단계에서 각 서브스크립션의 변경을 커밋하거나 중단할 수 있어요.
3.1.3 트랜잭션 로그가 새 파티션 기록 결과(메시지 ack용)를 트랜잭션 코디네이터로 보내요.
3.1.4 트랜잭션 코디네이터가 새로 ack된 파티션을 트랜잭션에 추가한 결과를 보내요.
3.2 Pulsar 클라이언트가 서브스크립션에서 메시지를 ack해요. 이 부분의 흐름은 일반 ack 흐름과 같지만, ack 요청이 트랜잭션 ID를 운반한다는 점만 달라요.
3.3 ack 요청을 받은 브로커는 그 ack가 트랜잭션에 속하는지 아닌지 확인해요.

4. 트랜잭션 종료 (End a transaction)

트랜잭션의 끝에서 Pulsar 클라이언트는 트랜잭션을 커밋할지 중단할지 결정해요. 메시지 ack에서 충돌이 감지되면 트랜잭션이 중단될 수 있어요.

4.1 종료 요청 (End transaction request)

Pulsar 클라이언트가 트랜잭션을 마치면 종료 요청(End transaction request)을 보내요.

단계 설명
4.1.1 Pulsar 클라이언트가 트랜잭션 코디네이터에 종료 요청(커밋할지 중단할지를 나타내는 필드 포함)을 보내요.
4.1.2 트랜잭션 코디네이터가 트랜잭션 로그에 COMMITTING 또는 ABORTING 메시지를 써요.
4.1.3 트랜잭션 로그가 커밋/중단 상태 기록 결과를 보내요.

4.2 트랜잭션 마무리 (Finalize a transaction)

트랜잭션 코디네이터가 이 트랜잭션에 포함된 모든 파티션에 메시지를 커밋하거나 중단하는 과정을 시작해요.

단계 설명
4.2.1 트랜잭션 코디네이터가 서브스크립션의 트랜잭션을 커밋하고, 동시에 파티션의 트랜잭션도 커밋해요.
4.2.2 프로듀스 브로커가 생산된 커밋 마커(produced committed markers)를 실제 파티션에 써요. 동시에 ack 브로커가 ack된 커밋 마커(acked committed marks)를 서브스크립션 보류 ack 파티션에 써요.
4.2.3 데이터 로그가 생산된 커밋 마커 기록 결과를 브로커로 보내요. 동시에 보류 ack 데이터 로그가 ack된 커밋 마커 기록 결과를 브로커로 보내요. 커서가 다음 위치로 이동해요.

4.3 트랜잭션을 COMMITTED 또는 ABORTED로 표시 (Mark a transaction as COMMITTED or ABORTED)

트랜잭션 코디네이터가 트랜잭션을 완료하기 위해 최종 트랜잭션 상태를 트랜잭션 로그에 써요.

단계 설명
4.3.1 이 트랜잭션에 포함된 모든 파티션에 대한 생산 메시지와 ack가 모두 성공적으로 커밋되거나 중단된 후, 트랜잭션 코디네이터가 최종 COMMITTED 또는 ABORTED 트랜잭션 상태 메시지를 트랜잭션 로그에 써서 트랜잭션이 완료됐음을 나타내요. 트랜잭션 로그에서 이 트랜잭션과 관련된 모든 메시지는 안전하게 제거할 수 있어요.
4.3.2 트랜잭션 로그가 커밋된 트랜잭션의 결과를 트랜잭션 코디네이터로 보내요.
4.3.3 트랜잭션 코디네이터가 커밋된 트랜잭션의 결과를 Pulsar 클라이언트로 보내요.

더 알아보기 (Learn more)