https://www.ibm.com/developerworks/cloud/library/cl-manage-cloud-transactions_2/index.html

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

분산 트랜잭션 없이 사용하기

클라우드 애플리케이션에서 트랜잭션 품질을 보장하는 방법

     Andre Fachat   2017년 5월 2일 작성

시리즈 소개:

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

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

2부: 분산 트랜잭션 없이 사용하기

 

이 시리즈의 1부에서는 트랜잭션 처리 및 분산 트랜잭션에 대해 살펴봤습니다. 트랜잭션 속성을 활용하면 애플리케이션에 필요한 오류 처리 작업의 부담을 덜 수 있으나 클라우드 기반 런타임에서는 여러 리소스를 포괄하면서 트랜잭션을 관리하는 분산 트랜잭션을 사용하지 못할 경우가 있습니다. 2부에서는 클라우드 환경에 초점을 맞추고 분산 트랜잭션이 없더라도 여러 리소스 관리자를 활용하면서 트랜잭션 품질을 보장할 수 있는 방법을 알아보겠습니다.

클라우드 환경은 기존 환경과 비교하면 분산 트랜잭션에 매우 불리한 환경입니다. 트랜잭션 관리자 못지않게 리소스(데이터베이스와 같은 리소스 관리자)도 가변성을 띌 수 있으며 이는 다시 말해서 각기 다른 부하 상황에 따라 언제라도 시작하거나 중단될 수 있다는 것입니다. 리소스 관리자를 재시작하면 지금까지 활성화되었던 모든 트랜잭션이 중단되야 하는데 분산 트랜잭션의 투표 단계에 참여하고 있던 트랜잭션 관리자는 어떻게 될까요? 영구적인 트랜잭션 관리자 로그가 없으면, 트랜잭션은 불확실한 상태에서 사라지고 이 트랜잭션을 위해 잠겼던 리소스는 리소스 관리자에 의해 해제될 기회를 영영 잃게 됩니다.

또한 클라우드 환경에서는 일부 SOA(service-oriented architecture) 환경과 마찬가지로 리소스 관리자 호출에 쓰이는 프로토콜이 REST over HTTP 호출과 같은 트랜잭션을 지원하지 않는 경우가 많습니다. 그런 프로토콜이 사용될 경우 호출은 1부에서 설명한 자동 커밋된 작업과 같아집니다.

클라우드에서는 더 용이하고 신속하게 새 버전을 배치하거나 애플리케이션을 확장할 수 있으나, 그로 인해 애플리케이션 프로그래머는 더 큰 난관에 봉착하기도 합니다. 여기서 트랜잭션 관리에 대해 알게 된 내용을 바탕으로 클라우드에서 더 효과적으로 작동하는 애플리케이션을 만들어보십시오.

가변적인 작업에 대한 보상을 위해 영구적인 공유 트랜잭션 로그를 별도의 스토리지 인프라에 두면서 클라우드에서 애플리케이션 서버를 설정하는 방법도 있을 수 있지만, 널리 쓰이는 통신 프로토콜 중 다수는 이러한 트랜잭션 사용법을 지원하지 않습니다.

즉, 클라우드 설정에서는 분산 트랜잭션 없이 사용할 수 있어야 하며 다음 섹션부터는 비 트랜잭션 설정에서 여러 리소스를 조정하는 다양한 기법을 소개합니다.

 

클라우드에서 분산 트랜잭션을 다루는 방법

트랜잭션 처리의 기본 전제는 복수의 리소스가 상호 일관되어야 한다는 것입니다. 어떤 리소스의 변경 사항은 다른 리소스의 변경 사항과 일맥상통해야 합니다. 분산 트랜잭션이 없는 경우 다음과 같은 오류 처리 패턴을 복수의 리소스에 적용할 수 있습니다.

  • 리소스 중 하나에서만 변경이 수행됩니다. 기능적 관점에서 이러한 상황이 용인된다면 트랜잭션을 자동 커밋하고 다른 리소스의 오류 상황은 로깅한 다음 무시할 수 있습니다.
  • 두 번째 리소스 변경 시 오류가 발생한다면 첫 번째 리소스의 변경을 롤백합니다. 그러기 위해서는 오류 발생 시 첫 번째 리소스의 트랜잭션이 열려 있어야 롤백이 가능합니다. 또는 양쪽 리소스의 트랜잭션이 열려 있는 상태에서 두 트랜잭션 중 하나를 처음 커밋할 때 오류가 발생하면, 롤백된 첫 번째 리소스의 변경이 외부에 보이지 않으므로 롤백하더라도 불일치 윈도우(window of inconsistency)가 나타나지 않습니다.
  • 한 리소스가 변경되고 그 이후 어느 시점에 두 번째 리소스가 변경되어 첫 번째 리소스와 일관된 상태가 되도록 합니다. 두 번째 리소스를 변경하는 동안 복구 가능한 오류가 발생하면 이러한 상황이 될 수 있거나 배치 처리처럼 두 번째 리소스의 변경을 지연시키는 아키텍처 패턴일 수도 있습니다. 이러한 패턴은 (더 큰) 불일치 윈도우를 발생시키지만, 최종적으로는 일관성에 이르게 됩니다. 따라서 이 패턴을 최종 일관성(eventual consistency)이라고 합니다. 여기서는 두 번째 변경이 커밋되므로 롤 포워드 불일치(roll-forward inconsistency)라고 부르겠습니다.
  • 두 번째 리소스를 변경하는 동안 오류가 발생하면 첫 번째 리소스의 변경이 보상됩니다. 이 패턴에서는 첫 번째 리소스가 별도의 트랜잭션에서 변경을 롤백할 수 있도록 보상 트랜잭션을 제공해야 합니다. 여기서도 최종 일관성이 발생하지만, 오류 패턴은 위의 롤 포워드 불일치와 다릅니다. 여기서는 첫 번째 변경이 롤백되므로 롤백 불일치(roll-back inconsistency)라고 부르겠습니다.

다음 섹션에서는 시스템이 즉각적으로 일관성을 띠지는 않지만, 트랜잭션이 커밋되면 곧 일관성을 갖게 되는 최종 일관성 속성을 적용하기 위한 기법을 소개합니다.

 

완화 기법

오류가 드물게 일어나거나 아예 일어나지 않도록 오류 조건을 줄일 수 있는 방법은 무엇일까요?

무시 – 나중에 수정

물론 가장 쉬운 방법은 어떤 오류 상황이되던지 무시했다가 나중에 배치 작업으로 수정하는 것입니다만 보기만큼 쉽지 않을 수도 있습니다. 예를 들어 어떤 데이터베이스에 구현된 장바구니와 주문 처리 서비스로 구성된 쇼핑몰 시스템을 생각해보십시오. 주문 서비스가 사용 불가능 상태일 때 이를 무시하려면 다음과 같은 조치가 필요합니다.

  • 아직 주문이 발송되지 않은 장바구니의 상태를 보존합니다.
  • 상황을 해결하기 위한 배치가 실행될 때까지 필요한 시간을 고려하지만 야간에만 실행될 경우, 백엔드에서 주문 처리가 이루어질 때까지 기다리느라 하루를 허비하고 고객의 불만을 살 수도 있습니다.

그림 1은 불일치를 해결하는 최종 일관성 배치의 예를 보여줍니다.

그림 1. 불일치를 해결하는 배치 작업

전체 크기 이미지 보기

이 주제의 변형 중 하나가 데이터베이스에 “작업”을 기록하고 백그라운드 프로세스에서 성공할 때까지 계속 주문 배송을 시도하는 것입니다. 이러한 경우 사실상 자체 트랜잭션 로그 및 트랜잭션 관리자를 만들게 됩니다.

시간 기준 비관적 잠금

비관적 잠금(pessimistic locking)은 트랜잭션에서 변경하려는 리소스를 실제로 사용 가능한 상태로 유지할 수 있는 기술입니다. 비관적 잠금은 다른 트랜잭션에서 (데이터베이스 행과 같은) 리소스를 수정하는 것을 차단하며, 때때로 단기 트랜잭션에서도 이 기술이 필요합니다.

1부에서 설명한 것과 같은 장기 비즈니스 트랜잭션에서는 초기에 어떤 단기 트랜잭션이 리소스의 상태를 변경하여 다른 트랜잭션에서 이 리소스를 다시 잠글 수 없게 만듭니다. 1부에서 살펴본 신용카드의 예라면, 장기 실행 비즈니스 트랜잭션에서 이 기술을 대규모로 잘 활용할 수 있습니다.

이 기술은 클라우드 설정에서도 사용 가능하지만, 한 가지 부담이 있습니다. 더 이상 필요하지 않을 때 반드시 개체에 대한 변경을 커밋하거나 트랜잭션을 롤백하여 잠금을 해제해야 합니다. 다시 말해서, 근본적으로는 트랜잭션 클라이언트 또는 트랜잭션 관리자에서 트랜잭션 로그를 작성하고 처리함으로써 다른 오류가 발생하더라도 반드시 잠금이 해제되게 해야 합니다.

잠금 해제를 보장할 수 없는 경우 최후의 수단으로 시간 기준 비관적 잠금(time-based pessimistic locking)을 사용하면 됩니다. 이는 주어진 시간 간격이 지나면 자동으로 해제되는 잠금으로 이러한 잠금은 리소스의 간단한 타임스탬프로 구현할 수 있습니다. 즉, 배치 작업이 필요하지 않습니다.

잠금은 트랜잭션 클라이언트와 리소스 관리자 사이에 일종의 공유 상태를 형성하여 잠금을 식별하여 트랜잭션과 연결해야 합니다. 따라서 커밋 또는 롤백 시점에 잠금을 확인하려면 시스템 간에 잠금 식별자 또는 트랜잭션 식별자 중 하나를 전달해야 합니다.

보상

긍정적 잠금(optimistic locking)은 비관적 잠금과 달리 오류 방지 기술이 아니며, 불일치를 탐지하는 것이 목적입니다. 긍정적 잠금은 트랜잭션을 위해 리소스를 예약하지만, 더 긍정적인 방식은 불일치가 없다고 가정하고 첫 단계에서 곧바로 트랜잭션을 위한 로컬 변경을 수행하는 것입니다. 하지만 불일치가 발생하면 변경을 롤백할 수 있어야 합니다. 이것이 긍정적으로 수행된 작업의 보상(compensation)입니다.

이론상으로는 괜찮은 접근 방식입니다. 작업이 롤백될 때 보상 서비스 작업만 호출하면 됩니다. 그러나 JEE 애플리케이션에서는 일부 데이터베이스 제약 조건을 커밋 시점에만 확인 가능하기 때문에 리소스 관리자가 변경 수행 시점이 아니라 commit()을 시도하는 시점에 롤백하도록 결정할 수 있습니다. 따라서 직접 Java 리소스 어댑터를 작성해야 합니다. 그래야 다른 리소스에 대한 트랜잭션이 롤백되었는지 아니면 커밋되었는지 확실히 알게 됩니다. 하지만 이 역시 앞서 설명한 이유로 클라우드 설정에서 피하려 했던 분산 트랜잭션이 됩니다. 게다가 어떤 이유로든 보상이 실패할 경우 어떻게 될지 직접 판단해야 합니다.

더 나은 방법은 어떤 형태로든 트랜잭션 로그에 실패를 기록하는 것입니다. 이를테면 별도의 (자동 커밋 트랜잭션) 작업에서 특정 테이블의 행에 기록합니다. 그런 다음 나중에 배치 작업으로 보상을 처리하면 됩니다. 그러나 다음 섹션에서 보여주는 것처럼, 두 번째 변경이 호출된 서비스에서 완전히 처리되지만 호출자가 응답을 받지 못할 경우 문제가 생깁니다. 그 다음 첫 번째 리소스가 보상되면 또 다른 불일치가 생기는 것입니다. 이러한 상황을 피하기 위해서는 첫 번째 변경을 보상하기 전에 두 번째 리소스의 상태를 확인해야 합니다.

이처럼 일어날 수 있는 온갖 오류 모드를 점검하는 것은 지루하지만 필요한 일입니다. 마지막으로 한 가지 상황만 더 살펴보겠습니다. 작업이 성공적으로 수행되지만 호출자가 회신을 받지 못해 오류가 발생했다고 가정하는 경우입니다.

반복 가능성(idempotence)

장바구니와 주문 서비스의 예제를 다시 사용합니다. 주문 서비스가 호출되었는데 회신이 네트워크에서 사라졌습니다. 호출자는 주문 서비스가 성공했는지 여부를 알지 못해 다시 시도합니다. 물론 주문이 두 번 처리되어서는 안 됩니다. 그렇다면 어떤 방법이 있을까요?

그림 2. 서비스 작업은 “이중 제출” 문제를 관리해야 합니다

전체 크기 이미지 보기

그림 2가 그러한 상황을 보여줍니다. 이 문제는 웹 애플리케이션의 “이중 제출(double submit)” 문제와 비슷합니다. 즉, 실수로 버튼을 두 번 클릭하는 바람에 동일한 요청이 서버에 두 번 보내지는 것입니다. 서비스 응답 리소스 관리자가 이 반복된 호출을 탐지한다면? 위 예제에서는 주문 서비스가 두 번째 호출을 반복 호출로 식별하고 첫 번째 호출에 대한 결과의 복사본만 반환해야 합니다. 실제 주문은 한 번만 처리되며, 주문 서비스는 반복 가능합니다.

서비스의 반복 가능성은 분산 트랜잭션이 없는 설정에서 오류 처리의 복잡성을 줄이는 데 큰 역할을 하며 이는 서비스가 멱등성(idempotent)을 갖는다는 뜻입니다. 물론 동일한 주문에 한해야 하지만 서비스 호출을 여러 번 반복할 수 있으며, 항상 그 결과는 서비스가 한 번만 호출되었을 때와 동일합니다. 별도의 주문은 내용이 동일하더라도 신규 주문으로 처리해야 합니다.

이 해결책은 서비스 호출의 모든 특성을 비교하는 방법으로 구현할 수 있습니다. 예를 들어 온라인 뱅킹 애플리케이션에서 거래를 두 번 입력하는 실수를 저지르면 거래 은행은 금액, 주체, 수령자가 첫 번째 트랜잭션과 동일한 두 번째 트랜잭션을 거부합니다.

더 확실한 방법으로, 요청과 함께 보내진 트랜잭션 식별자를 통해 이중 제출을 탐지할 수 있습니다. 이 경우에는 호출된 서비스 작업이 호출자가 보낸 트랜잭션 ID를 등록했다가 동일한 ID가 다시 사용되면 원래의 결과만 반환합니다. 그러나 클러스터의 서비스 인스턴스 사이에 일종의 공유 상태(트랜잭션 ID)가 형성될 수 있는데, 그러면 반복된 호출이 다른 클러스터 구성원을 대상으로 하더라도 한 번만 처리되어 또 다른 일관성 문제가 생기게 됩니다.

수동 트랜잭션 및 오류 처리

분산 트랜잭션이 없는 상태에서는 시스템이 각각의 작업을 검토하고 일관성을 보장하는 방향으로 그 처리 방법을 결정해야 합니다. 복수의 리소스 관리자를 (아마도 각각의 트랜잭션을 통해) 호출하는 클라이언트는 가장 먼저 커밋할 리소스 관리자 및 오류 발생 시 나머지 리소스 관리자를 처리할 방법을 신중하게 결정해야 합니다. 이는 기능적 요구 사항에 따라 결정됩니다. 웹 서비스와 같은 비 트랜잭션 리소스는 롤백이 불가능하고 보상만 가능하므로 훨씬 더 까다롭습니다. 기존 시스템에서 리소스 관리자가 했던 기능을 이제는 여러분이 직접 해결해야 합니다.

제 프로젝트 중 하나를 예로 들어보겠습니다. 나중에 참조할 몇 개의 레코드를 웹 서비스를 통해 별도의 시스템에 저장했습니다. 로컬 트랜잭션에서 오류가 발생할 때도 있으므로, 언제 오류가 발생했는지 그리고 어디서 보상 작업을 호출해야 하는지에 각별히 관심을 기울여야 했습니다. 결국 이 호출을 반복 가능한 호출로 만들었고 보상은 사용하지 않기로 했습니다. 어떤 트랜잭션이 롤백되면 그 다음 시도에서 동일한 레코드를 다시 기록하며(오버라이팅), 이전 시도의 어떤 잔재도 없이 참조를 위한 포인터를 확보했습니다.

또 다른 예에서는 증감 웹 서비스를 사용하여 마스터 데이터 레코드 사용의 참조 카운터를 다른 시스템에 유지해야 했습니다. 마스터 데이터 레코드를 참조하는 개체를 생성할 때마다(해당 ID 저장) 카운터를 늘리고 개체를 삭제하면서 카운터를 줄였습니다. 여기서 두 가지 문제가 발생했습니다. 첫째, 호출이 웹 서비스였기 때문에 Java 리소스 어댑터를 사용하여 롤백을 다뤄야 했습니다. 게다가 롤백 과정에서도 (네트워크 문제 등으로 인해) 오류가 발생했습니다. (더 심각한) 두 번째 문제는 카운터 감소가 100% 보상이 아니었다는 점입니다. 예컨대 롤백에서 값이 0에 도달하면 마스터 데이터 레코드가 삭제될 위험이 있었습니다. 물론 이 사실을 사전에 알 방법은 없었습니다. 결국 개체를 생성할 때만 카운팅했고, 이 마스터 데이터 레코드에 어떤 작업을 할 때마다 별도의 (트랜잭션) 로그 테이블에 기록했습니다. 그런 다음 별도의 야간 배치 작업으로 카운터 값을 수정했습니다.

이러한 사례는 많습니다. eBay도 사실상 트랜잭션 없는 디자인을 사용합니다. eBay의 데이터베이스 명령문 대부분은 자동 커밋(웹 서비스 호출과 비슷함)입니다. 명확하게 정의한 특별한 경우에 한해 여러 명령문을 결합하여 (단일 데이터베이스에 대한) 단일 트랜잭션을 만듭니다. 분산 트랜잭션은 허용되지 않습니다(“Scalability Best Practices: Lessons from eBay” 참조).

관련 연구

CAP 정리는 이와 관련 있는 분산 복제 데이터베이스의 시나리오에서 일관성, 가용성, 성능의 3대 기능 중에서 어느 임의의 시점에 실현 가능한 것은 2가지뿐이라고 설명합니다.  이는 단일 분산형 데이터베이스를 대상으로 정의된 것이지만, 위에서 살펴본 복수의 리소스를 포괄하는 트랜잭션에도 적용할 수 있습니다.

강력한 일관성을 완화하고 최종 일관성 윈도우를 허용하는 것이 “BASE” 패러다임에서 공식화되었습니다.

  • Basically Available(근본적인 가용성)
  • Soft state(연성 상태)
  • Eventual consistency(최종 일관성)

가용성이 강력한 일관성에 우선합니다. 그 결과가 연성 상태(soft state)이며, 이는 최신 값이 아니더라도 최종적으로 업데이트될 수 있습니다. 이 원칙은 데이터베이스 영역에 적용될 뿐 아니라 클라우드에 배치되는 웹 서비스 또는 마이크로서비스와도 관련 있습니다. 그러나 분산 데이터베이스에서는 시스템의 (최종) 일관성을 유지할 책임이 데이터베이스 벤더에게 있습니다. 여러 리소스를 사용하는 애플리케이션의 경우 위에서 보여준 것처럼 애플리케이션에서 스스로 해결해야 합니다.

이 새로운 최종 일관성 패러다임은 일부 기능적 시나리오에도 적합합니다. 예를 들어 “Providing a Consistent User Experience and Leveraging the Eventual Consistency Model to Scale to Large Datasets“에서 Google의 자료를 참조하십시오.

더 나아가 최종 일관성이 문제되지 않는 알고리즘을 찾아 사용해볼 수도 있습니다. 최근에 제안된 CALM 정리에 따르면 일부 프로그램, 즉 “단조성(monotonic)”을 띄는 프로그램은 최종 일관성 모델에서 안전하게 실행될 수 있습니다. 예를 들어 읽기(read) 작업을 사용하여 카운터에 1을 늘린 다음 이를 기록하면 일관성 문제에 취약해집니다. 그러나 “증가(increment)” 작업을 호출하여 카운터를 늘리면 일관성 문제를 염려하지 않아도 됩니다. 자세한 내용은 “Eventual Consistency Today: Limitations, Extensions, and Beyond” and “CALM: consistency as logical monotonicity“를 참조하십시오.

 

결론

일반 프로그래머의 입장에서는 분산 트랜잭션을 기반으로 하는 강력한 일관성 모델을 사용하지 않으면 더 힘들어질 수도 있습니다만 트랜잭션 관리자에게 맡겼던 작업, 특히 오류 처리 문제는 이제 프로그래머가 직접 해결해야 합니다. 다른 한편으로 한결 수월해진 면도 있습니다. 처음부터 최종 일관성을 염두에 두고 프로그래밍하면, 프로그램의 확장성이 크게 향상될 뿐 아니라 프로그램이 데이터 센터보다 훨씬 더 분산된 환경에서 일어나는 네트워크 지연 및 각종 문제에 대한 복원력도 강화됩니다. 물론 누구나 처음부터 eBay와 같은 규모로 애플리케이션을 만드는 건 아닙니다. 그래도 분산 트랜잭션은 중대한 오버헤드를 일으킬 수 있습니다. (제 솔직한 심정으로는, 단일 트랜잭션에서 하나 이상의 데이터베이스와 하나의 메시지를 사용할 수 있기를 바랍니다.)

그렇다면 분산 트랜잭션 없이 사용할 수 있는 방법은 무엇일까요? 몇 가지를 제안합니다.

  • 신규 시스템에서는 최종 일관성의 조건에서 비즈니스 문제를 해결하는 알고리즘을 찾아보십시오(CALM 정리 참조).
  • 강력한 일관성을 필요로 하는 비즈니스 요구 사항에 도전(이슈를 제기) 하십시오.
  • 호출하는 서비스는 이 아키텍처에 맞게 수정되어야 합니다. 반복 가능한(멱등성) 서비스가 되거나 적합한 보상을 제공하면 됩니다.
  • 기존 시스템에서는 여기서 소개한 방법을 참조하여 시스템에서 호출하는 서비스에 오류가 일어날 가능성이 더 적은, 더 간단한 알고리즘으로 수정하십시오.

클라우드에서는 더 용이하고 신속하게 새 버전을 배치하거나 애플리케이션을 확장할 수 있으나, 그로 인해 더 큰 난관에 봉착하기도 합니다. 여기서 트랜잭션 관리에 대해 알게 된 내용을 바탕으로 클라우드에서 더 효과적으로 작동하는 애플리케이션을 만들어보십시오.

이번 시리즈를 검토하고 의견을 주신 동료, Sven Höfgen, Jens Fäustl, Martin Kunz에게 감사드립니다.

 

토론 참가

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