https://www.ibm.com/developerworks/cloud/library/cl-manage-cloud-transactions_1/?social_post=872241990&fst=Discover

클라우드에서 트랜잭션 관리, 1부

트랜잭션의 기초 및 분산 트랜잭션

2단계 커밋(2 Phase Commit) 분산 트랜잭션의 작동 방식 이해

    Andre Fachat
2017년 4월 17일 작성

 

시리즈 소개:

이 글은 2편으로 구성된 ‘클라우드에서 트랜잭션 관리’ 시리즈의 1부입니다.

1부: 트랜잭션의 기초 및 분산 트랜잭션

2부: 분산 트랜잭션 없이 사용하기
인터넷 쇼핑몰 고객은 주문 상품이 신속하게 처리되어 빨리 배송되기를 원하며 은행 고객은 이체 과정에서 알 수 없는 이유로 내 돈이 사라지는 일이 있어서는 안 된다고 생각합니다. 엔터프라이즈 애플리케이션에서 기존의 트랜잭션은 일관성, 다른 트랜잭션과의 분리 등 여러 품질 특성을 보장하고 분산 트랜잭션(distributed transaction)은 여러 리소스를 포괄하는 범위에서, 이를테면 매장 데이터베이스 및 ERP 시스템의 전 범위에서 그와 같이 보장합니다. 일반적으로 트랜잭션 품질은 미들웨어 인프라에서 담당하므로 프로그래머의 부담을 크게 덜어줄 수 있습니다.

그러나 클라우드에서는 미들웨어에서 그러한 품질을 보장하지 않는 경우가 많습니다. 일반적으로 분산 트랜잭션을 사용하지 못할 뿐 아니라 인프라가 훨씬 더 가변적일 수 있습니다. 낮은 착오율(error rate)도 애플리케이션과 함께 빠르게 증가하면서 지원 비용의 부담이 커지게 됩니다. 따라서 확장형 클라우드 애플리케이션에서 일어날 수 있는 트랜잭션 관리 오류를 사전에 예방하거나 제대로 처리하는 것이 중요합니다.

2편으로 구성된 이 시리즈의 1부에서는 트랜잭션 처리에 대한 배경 정보 및 트랜잭션에서 보장하는 품질을 다루고 클라우드 환경에서 무엇이 달라지는지 살펴봅니다. 2부에서는 비트랜잭션 서비스를 호출하는 데 기존 트랜잭션 처리를 대체할 방법을 소개합니다.

트랜잭션의 기본 특성 중 하나가 원자성(atomicity)입니다. 실제 분산 트랜잭션에서 이 원자성이 제대로 구현되지 않는다면 그로 인해 일관성(consistency까지 잃을 수도 있습니다.

 

트랜잭션의 일반 속성

IT 환경에서 트랜잭션은 대개 완전한 하나의 단위로 성공하거나 실패하는, 정보에 대한 개별 작업을 의미합니다. 하나의 트랜잭션이 부분적으로 완료되는 경우는 없으며 이것은 중요한 속성입니다. 예를 들어 재무 작업에서는 자금의 총액이 바뀌지 않게끔 트랜잭션을 사용합니다. 어떤 계정에서 자금을 인출하면 동시에 다른 계정으로 그 자금을 예치해야 합니다.

더 공식적으로 설명하자면, 트랜잭션은 흔히 다음 4가지 “ACID” 속성과 연결됩니다.

  • 원자성(Atomicity)— 트랜잭션은 “성공 아니면 실패”입니다. 성공했다면 트랜잭션에 포함된 모든 작업이 수행된 것입니다. 실패한 경우 그 작업 중 어느 것도 수행되지 않아야 합니다.
  • 일관성(Consistency)— 트랜잭션은 시스템을 어떤 일관된 상태에서 다른 일관된 상태로 전환해야 합니다. 예를 들어 데이터베이스에 기록된 데이터는 (커밋 시점에) 데이터베이스에 정의된 모든 제약 조건을 따라야 합니다.
  • 격리성(Isolation)— 복수의 병렬 트랜잭션은 서로 격리되어 마치 순차적으로 실행되는 것처럼 작동합니다.
  • 지속성(Durability)— 어떤 트랜잭션이 커밋되면 그 결과는 정전 또는 기타 오류가 일어나더라도 유지됩니다.

이것이 트랜잭션에 대한 이론적인 원칙이지만, 실제 상황에서는 성능을 위한 효과 또는 최적화 때문에 변칙 적용될 수도 있습니다.

예를 들어 원자성, 일관성, 지속성은 트랜잭션 처리 시스템의 중요한 목표이지만 격리성은 엄격한 격리가 성능에 미칠 수 있는 영향 때문에 더 느슨하게 적용될 경우가 많습니다. 시스템에서 허용 가능한 격리 오류를 결정하고자 다양한 트랜잭션 격리 레벨이 정의될 수 있으며 이러한 격리 레벨은 단일 데이터베이스에서 발생하므로, 여기서는 자세히 다루지 않겠습니다.

일반적으로 트랜잭션은 트랜잭션 클라이언트리소스 관리자를 호출하면서 시작합니다. 이는 대개 데이터베이스 또는 메시지 큐입니다. 리소스 관리자는 데이터베이스의 테이블 또는 행과 같은 형태로 리소스를 관리합니다. 클라이언트가 트랜잭션을 열고 가능하다면 복수의 호출을 통해 시스템에서 하나 이상의 리소스에 대한 여러 작업을 수행한 다음 트랜잭션 커밋을 시도합니다(그림 1 참조). 트랜잭션에 단일 리소스 관리자가 있다면 원자성 및 일관성의 기준에서 트랜잭션이 올바른지 스스로 판단하고 커밋 시점에 지속성을 보장할 수 있습니다.

그림 1. 클라이언트, 리소스 관리자, 리소스로 이루어진 일반적인 트랜잭션

전체 크기 이미지 보기

 

자동 커밋

위에서는 start() 및 commit() 작업의 명시적 트랜잭션 경계를 사용합니다. 따라서 하나의 리소스에 대한 여러 작업을 단일 원자성 변경으로 묶을 수 있습니다. 트랜잭션 과정에서 해당 리소스가 잠기므로 다른 변경은 불가합니다. 그런 다음 다른 조치의 결과와, 가능하다면 다른 리소스의 결과에 따라 커밋 또는 롤백 결정이 내려질 수도 있습니다.

트랜잭션 클라이언트가 어떤 리소스에 대해 단일 작업을 수행해야 하는 경우 리소스 관리자를 여러 번 호출할 필요가 없습니다. 단일 리소스에 대한 단일 변경은 자동 커밋(autocommitted)이 가능합니다. 즉, 그림 2에서 보여주는 것처럼 즉시 적용됩니다.

그림 2. 명시적 커밋에서는 다른 조치의 결과가 나올 때까지 기다릴 수 있으나 리소스를 잠급니다

전체 크기 이미지 보기

단일 리소스에 대해 자동 커밋할지 아니면 명시적 커밋이나 롤백을 사용할지 여부는 아래에서 설명하는 복수 리소스에 대한 변경에 적용되는 오류 처리 패턴에도 영향을 미칩니다.

장기 트랜잭션과 단기 트랜잭션

위에서 설명한 트랜잭션 속성은 일반적으로 트랜잭션 기간에 유효합니다. 예컨대 신용카드 구매와 같은 비즈니스 트랜잭션에서도 이 속성이 유지되어야 합니다. 그러나 어떤 트랜잭션은 긴 시간이 소요될 수 있습니다. 예를 들어 호텔에 체크인할 때 호텔은 투숙객의 계정 잔고(즉, 카드 이용 한도)가 충분한지 온라인으로 문의합니다. 그러나 그 계정에 대한 청구는 며칠 후 체크아웃 시점에 이루어집니다. 이를 장기(long-running) 트랜잭션이라고 하는데, 이러한 비즈니스 트랜잭션은 대개 트랜잭션 및 리소스 관리자에서 (기술) 리소스를 차단하는 것을 예방하고자 여러 트랜잭션으로 나뉩니다.

일반적으로 리소스 관리자는 트랜잭션 간에 리소스를 잠글 수 없습니다. 장기 비즈니스 트랜잭션에서 리소스를 잠그기 위해서는 대개 기능적 데이터 모델이 이 추가적인 리소스 상태를 저장할 수 있도록 수정되어야 합니다.

예를 들어 어떤 고객이 호텔에 체크인할 때 단기 트랜잭션이 고객의 계정에서 일부 금액을 예약합니다. 이 예약 상태는 데이터베이스에서 별도의 행에 저장할 수 있으며, 예약 시 예약금 총액이 고객의 신용 한도를 초과하지 않음을 확인합니다. 체크아웃할 때 또 다른 단기 트랜잭션이 그 예약금을 실제 자금 이체로 대체합니다. 각 트랜잭션은 (위에 언급된 격리 레벨의 한도 내에서) ACID 속성을 이행할 수 있으나, 비즈니스 트랜잭션은 그렇지 않습니다. 예약금은 다른 비즈니스 트랜잭션에서 예약할 수 없으므로 다른 트랜잭션으로부터 격리되지 않습니다.

장기 비즈니스 트랜잭션에서 원자성 및 일관성을 보장하기 위해 특별한 기술이 적용될 수 있습니다. 예를 들어 신용카드 트랜잭션에 대한 예약금은 사실상 일종의 비관적 잠금(pessimistic locking)입니다. 클라우드 설정에서 이러한 기술을 사용하는 것에 대해서는 이 글에서 다시 살펴보겠습니다.

(이 주제에 대한 자세한 내용은 SYS-CON Media 웹 사이트의 “Web-Services Transactions” 참조하십시오.)

로컬 트랜잭션과 분산 트랜잭션

로컬 트랜잭션은 단일 리소스 관리자만 사용합니다. 그러나 어떤 트랜잭션에 둘 이상의 리소스 관리자가 필요하다면 어떻게 될까요? 이를테면 2개의 데이터베이스에 있는 데이터를 관리하거나 데이터베이스의 어떤 상태에 따라 메시지 큐의 메시지를 전송해야 한다면? 그러한 경우 첫 번째 리소스 관리자를 커밋한 다음 두 번째 리소스 관리자를 커밋하는 방식은 적합하지 않습니다. 두 번째 리소스 관리자가 어떠한 제한 조건의 위반으로 롤백할 경우, 첫 번째 리소스 관리자가 이미 커밋된 트랜잭션에 포함되었을때 이를 어떻게 롤백할까요?

지금까지 유용하게 사용되었던 일반적인 해결 방법은 트랜잭션 관리자(transaction manager)를 사용하는 것입니다. 이는 하나의 트랜잭션에 관련된 리소스 관리자를 조정하며 트랜잭션 관리자는 대개 2단계 커밋(2 Phase Commit)인 분산 트랜잭션 프로토콜을 사용하여 트랜잭션을 커밋합니다.

2단계 커밋에서는 트랜잭션 관리자가 먼저 관련 리소스 관리자에게 트랜잭션이 커밋되면 성공을 보장하도록 요청합니다. 이는 투표 단계(voting phase)입니다. 모든 리소스 관리자가 동의하면 커밋 단계(commit phase)에서 트랜잭션이 모든 리소스 관리자에게 커밋됩니다. 어느 한 리소스 관리자가 커밋을 거절하면 모든 리소스 관리자가 롤백되고 일관성이 보장됩니다. 그림 3은 2단계 커밋 트랜잭션을 보여줍니다.

그림 3. 2단계 커밋 프로토콜의 분산 트랜잭션

전체 크기 이미지 보기

이 모델은 호스트 기반 트랜잭션 처리, JEE(Enterprise Java) 애플리케이션 서버, Microsoft Transaction Server, 기타 시스템에서 성공적으로 사용되었습니다(“A Detailed Comparison of Enterprise JavaBeans (EJB) & The Microsoft Transaction Server (MTS) Models” 참조).

분산 트랜잭션은 애플리케이션 서버뿐 아니라 웹 서비스에도 사용할 수 있습니다. 웹 서비스를 위한 WS-Transaction 표준이 있지만, 아직까지는 실제로 사용된 예를 보지 못했습니다.

실제 환경에서의 분산 트랜잭션

트랜잭션의 기본 특성 중 하나가 원자성입니다. 실제 분산 트랜잭션에서 이 원자성이 제대로 구현되지 않을 때도 있으며, 그로 인해 일관성까지 잃을 수도 있습니다. 메시지 큐와 데이터베이스 간의 2단계 커밋 트랜잭션을 생각해보십시오. 어떤 상태가 데이터베이스에 기록되고 관련 메시지가 큐에 보내집니다. 원자성과 일관성을 고려하면 큐에서 메시지를 받는 즉시 데이터베이스 상태는 그와 일관된 상태가 되어야 합니다. 이를 강력한 일관성(strong consistency) 모델이라 하며, 이는 기존의 트랜잭션에서 보장됩니다.

그러나 트랜잭션 관리자가 두 리소스 관리자에 보내는 커밋은 차례로 보내지므로 첫 번째 리소스에 대한 변경이 두 번째 리소스에 대한 변경보다 먼저 확인될 수도 있습니다. 이를테면 두 번째 리소스인 데이터베이스는 실제로 커밋을 수행하는 데 더 많은 시간이 걸리는데, 첫 번째 리소스 관리자에게 커밋된 메시지는 이미 수신된 상태입니다. 따라서 데이터베이스 상태가 아직 확인되지 않아 처리가 실패하게 됩니다.

이러한 현상이 자주는 아니더라도 가끔 발생하며, 저자도 실제로 경험한 바 있습니다(“Two-phase commit race condition on WebLogic” 및 “Messaging/Database race conditions” 참조). 따라서 기존의 분산 트랜잭션에서도 불일치 윈도우(window of inconsistency)가 발생하고 트랜잭션은 최종 단계에 이르러야 일관성을 갖게 됩니다. 그림 4를 참조하십시오.

그림 4. 2단계 커밋에서도 발생하는 불일치 윈도우

전체 크기 이미지 보기

또한 2단계 커밋 프로토콜에서는 두 단계 모두 완전히 수행되어야 데이터베이스 레코드와 같은 차단된 리소스가 해제됩니다. 트랜잭션 관리자는 (이를테면 트랜잭션 관리자가 중단되거나 재시작하는 상황에서도) 트랜잭션의 완전성을 보장하고자 직접 자신의 트랜잭션 로그를 작성하는데, 트랜잭션 관리자가 중단되더라도 이 로그는 보존됩니다. 그런 다음 트랜잭션 관리자가 시작할 때 이 로그를 읽고 보류 중인 트랜잭션을 완료합니다. 따라서 대개 파일 시스템에 기록되는 영구적인 트랜잭션 로그는 트랜잭션 관리자의 2단계 커밋 트랜잭션을 지원해야 합니다.

2단계 커밋 트랜잭션은 커밋 작업만을 위해서도 복수의 원격 호출이 필요합니다. 분산 트랜잭션의 오버헤드가 감지되면 일반 JEE 설정에서도 이러한 호출을 피하는 경우가 많지만 리소스 관리자 간 일관성을 보장하는 데 필요하다면 사용할 수도 있습니다.

트랜잭션 비동기(메시징) 프로토콜

분산 트랜잭션을 효과적으로 활용하는 사례 중 하나가 메시지 지향 미들웨어와 데이터베이스 트랜잭션을 결합하는 경우입니다. 데이터베이스 트랜잭션이 성공할 때만 큐에서 메시지를 가져오게 하는 것은 매우 좋은 기능이며 애플리케이션에서 오류 처리 작업의 부담을 덜어줍니다. 데이터베이스 트랜잭션이 성공적으로 커밋될 수 있을 때만 메시지가 전송되게 하는 것도 데이터베이스와 메시징 큐 간의 일관성 유지에 도움이 됩니다.

그러한 설정에서는 분산 트랜잭션이 “로컬” 데이터베이스 및 메시징 서버에 한정되며 다른 리소스 관리자를 포함하지 않습니다. 그리고 메시징 시스템은 발신 애플리케이션 서버와 수신 애플리케이션 서버 사이에서 트랜잭션 속성을 보장하며 이러한 방식은 최종적으로 일관성을 실현합니다. 반드시 발신 리소스의 변경이 커밋된 이후에 수신 리소스가 변경되기 때문입니다.

그림 5. 메시징 인프라로 트랜잭션의 최종 일관성을 보장할 수 있습니다

전체 크기 이미지 보기

메시지 지향 미들웨어를 사용하면 트랜잭션 품질의 이벤트 기반 아키텍처를 구축할 수 있으며 오류 처리의 상당 부분을 트랜잭션 관리자에게 위임할 수 있습니다.

결론

1부에서는 트랜잭션의 기초 및 분산 트랜잭션을 살펴봤습니다. 트랜잭션은 그 속성 때문에 단일 리소스뿐 아니라 특히 여러 리소스를 포괄하는 범위에서 오류 처리가 용이해질 수 있습니다. 여기서는 2단계 커밋 분산 트랜잭션의 작동 방식을 소개하면서 약간의 시간 간격은 있지만 “최종적으로는 일관된다”는 점을 설명했습니다. 또한 분산 트랜잭션에서는 트랜잭션 관리자의 영구적인 트랜잭션 로그가 꼭 필요하다는 사실도 밝혔습니다.

그러나, CloudFoundry와 같이 영구적인 파일 시스템이 없는 클라우드 환경이 많습니다. 따라서, JEE 애플리케이션 서버 런타임이 자체 트랜잭션 관리자의 트랜잭션 로그를 작성할 수 없으며 이는 결국 2단계 커밋 트랜잭션이 불가능하다라는 것을 의미합니다. 또한, JavaScript 서버와 같은 새로운 런타임 환경 중 일부는 심지어 런타임에서 사용 가능한 트랜잭션 관리자가 전혀 없습니다. (“Microservices Best Practices for Java” 참조)

2부 에서는 트랜잭션 보장의 일부를 제공하는, 분산 트랜잭션의 대안을 살펴볼 것입니다. 이러한 대안을 통해 클라우드 애플리케이션에서도 트랜잭션 품질을 보장하고 인터넷 쇼핑몰의 주문이 제대로 처리되게 할 수 있습니다.

 

토론 참가

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.