[MSA] 5. MSA의 트랜잭션 이야기 1 - 트랜잭션의 이해와 MSA에서 바라보는 트랜잭션

이 글을 작성하기 전에 어떤 제목을 붙여야 할지 많은 고민을 하였습니다. MSA에서 트랜잭션의 문제는 현재 대두되고 있는 가장 큰 문제라고 할 수 있습니다. 

 

이번 포스트에서는 트랜잭션의 기본 이야기와 모놀리식의 트랜잭션, MSA에서 트랜잭션에 대해 정말 깊게 다뤄보는 시간을 가져보도록 하겠습니다. 3 파트로 나누어 진행될 예정이며 1번째 파트에서는 트랜잭션에 대한 기본을 이야기 하고, 모놀리식의 트랜잭션을 다뤄보고자 합니다.

 

 

 

What is Transaction ?

트랜잭션이란, 데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 단위를 말합니다. 데이터베이스에서 대표적으로 가능한 작업에는 검색(SELECT), 수정(UPDATE), 삽입(INSERT), 삭제(DELETE)가 있습니다. 이 중에서 데이터베이스의 변화를 가리키는 작업에는 수정, 삽입, 삭제가 있고, 이들 작업을 수행할 때는 반드시 트랜잭션이 따라옵니다.

 

트랜잭션이 수행된 뒤에는 커밋 혹은 롤백이 진행됩니다.

그런데, 데이터베이스에서 데이터를 삽입, 수정, 삭제할 때 왜 이러한 복잡한 과정을 진행하는 것일까요?

 

데이터베이스를 사용하기 이전에서 우리가 데이터를 저장할 때는 파일이라는 것을 사용했었습니다. 그러나 단일 파일 하나에 많은 것을 보관하거나 관계가 있는 데이터들을 매핑하는 데 있어 여러 개의 파일을 사용해야 했고, 이들의 파일을 각각 불러오는 것은 많은 리소스를 사용함과 동시에 프로그래밍에 있어 많은 불편함을 초래했습니다.

 

예를 들어 은행의 계좌 이체를 보면, 많은 사람들이 현금 인출기 혹은 ATM을 사용하여 계좌 이체를 하게 됩니다. 이들은 은행원에게 전달하는 방식이 아닌, 기계가 해주는 것입니다. 그런데, 계좌 이체 중에 기계가 고장이 나거나, 어느 시스템에 오류가 발생하여 카드가 나오지 않거나 기계가 멈출 경우 내 돈은 어떻게 되는 것일까요? 

 

만약, 파일에서 이러한 문제가 발생한다면, 그 경로를 추적하기가 굉장히 어렵습니다. 예를 들어 A 파일이 나의 계좌이고 B 파일이 상대방의 계좌라면 A 파일에서는 이미 그 돈이 빠져나갔다고 기록 되어 있지만, 중간에 오류가 발생했기 때문에 B 파일에서는 그러한 기록을 찾아볼 수 없기 때문입니다. 이러한 문제는 데이터의 원자성에서 이미 그 기능을 상실한 것이 됩니다.

 

그림으로 쉽게 설명해보자면, A 계좌에서는 5,000원이 이체 되었다는 기록이 있지만 중간에 기계에서 오류가 발생하면서 B 계좌에는 그 기록이 남지 않으므로 A 계좌에 있던 5,000원의 정보를 전산 시스템에서 찾을 수 없기 때문에 문제가 됩니다.

 

이러한 문제 때문에 생겨난 데이터베이스의 트랜잭션은 아래와 같은 특징을 가지고 있습니다.

 

  • Atomicity (원자성)

    트랜잭션 연산은 데이터베이스에 모두 반영이 되던지, 아니면 전혀 반영되지 않아야 함.

  • Consistency (일관성)

    트랜잭션이 성공적으로 완료가 되었다면, 언제나 일관성 있는 상태로 변환됨.

  • Isolation (독립성, 격리성)

    둘 이상의 트랜잭션이 동시에 병행 실행 되는 경우, 어느 하나의 트랜잭션 실행 중에 다른 트랜잭션 연산이 실행될 수 없음.

  • Durablity (영속성, 지속성)

    성공적으로 완료된 트랜잭션의 결과는 시스템에 오류가 발생해도 영구적으로 반영되어야 함.

 

위의 상황을 타기할 수 있는 특징에는 데이터베이스 원자성이 있습니다. 계좌 이체를 수행하던 중에 기계에 문제가 발생하여 제대로 수행되지 않았을 경우, 모든 데이터베이스에 그 기록 사항은 반영되지 않아야 합니다.

 

그렇다면 트랜잭션의 어떤 기능으로 이러한 특징을 발휘할 수 있는 것일까요?트랜잭션은 연산과 상태로 나누어, 해당 연산에 따라 반환되는 상태가 있습니다. 만약 트랜잭션 연산 수행 중, 문제가 발생한다면 완료, 그렇지 않으면 실패를 반환합니다. 이러한 결과를 데이터베이스의 트랜잭션 관리자가 중앙에 존재하여 관리하게 됩나다.

 

트랜잭션은 연산과 위 상태로 4가지의 특징을 모두 발휘할 수 있습니다. 트랜잭션이 성공했다면, 그 내용을 반영하고 성공이 아닌 다른 액션이 취해진다면 반영하지 않는 방향으로 쉽게 원자성을 지킬 수 있죠.

 

각 상태에 대해 간단히 알아보겠습니다.

 

  • Active (활동): 트랜잭션이 실행 중인 상태
  • Failed (실패): 트랜잭션 실행에 오류가 발생한 상태 (이 때, 실행된 트랜잭션은 중단됨)
  • Aborted (철회): 트랜잭션이 비정상적으로 종료되어 Rollback 연산을 수행한 상태
  • Partially Commited (부분적 완료): 트랜잭션이 마지막 연산까지 정상적으로 동작됨. (커밋 연산 전)
  • Commited (완료): 트랜잭션이 성공적으로 종료됨. (커밋 연산 끝.)

 

여기서 주의해야 할 점은 부분적 완료와 완료인데, 부분적 완료는 트랜잭션 연산이 정상적으로 모두 수행되었지만 커밋 연산 전이라고 표시되어 있습니다. 이 말은 연산은 끝났지만 아직 영구적으로 데이터베이스에 저장되어 있는 상태는 아니라는 점입니다. 이 지점부터 애플리케이션에서 무결성 등을 검증하는 단계를 거치는 등 다른 별도의 로직을 수행할 수 있고, 그 후에 커밋 명령이 떨어지면 그 때는 데이터베이스에 영구적으로 저장이 됩니다.

 

 

 

 

Transaction in Monolithic Architecture

이번 포스트의 메인은 트랜잭션이 아닌 마이크로 서비스 아키텍처의 트랜잭션입니다. 그러나 우리는 이 부분을 짚기 전에, MSA의 트랜잭션이 왜 별개의 타이틀이 되었고, 기존의 모놀리식과 어떻게 다르며 기존의 모놀리식을 왜 받아들일 수 없는지, 왜 설계를 별도로 해야하는지를 좀 더 얘기해보고자 이번 타이틀을 만들었습니다.

 

모놀리식 아키텍처를 가진 애플리케이션에서 트랜잭션은 어떻게 처리할까요?

 

모놀리식 아키텍처의 경우, 데이터베이스의 트랜잭션에 의존하게 됩니다. DB에서 제공하는 트랜잭션은 위에서 말했 듯이 4가지의 특징을 잘 제공하게 됩니다.

 

이런 것이 가능한 이유는 모놀리식 아키텍처를 가진 애플리케이션은 하나의 데이터베이스로 운영하기 때문입니다. 그렇기 때문에 DB에서 각각 테이블이 RelationShip(관계)를 가지고 있어도 그 에러를 알아서 처리해주기 때문에 우리는 이에 제공해주는 ORM 혹은 쿼리를 사용하면 이러한 트랜잭션에 대해서 신경쓰지 않아도 됩니다.

 

 

 

 

 

Transaction in Micro Service Architecture 

그렇다면 마이크로 서비스 아키텍처는 어떨까요?

 

마이크로 서비스 아키텍처는 각 서비스별로 인스턴스를 분리하는 형태입니다. 이 말은, 이와 연결되어 있는 Database Server 인스턴스 또한 분리시킨다는 것입니다. 즉 원하는 데이터를 DB에서 뽑아 쓰는 것이 아닌 각자의 API에서 가져오는 형태이며 각 서비스는 자신만의 데이터를 가지고 있고, 그 데이터만을 뽑을 수 있도록 분산을 시킨 것이지요.

 

그런데, 이 설계는 문제점이 있습니다. 기존의 테이블의 관계가 하나의 데이터베이스 서버 인스턴스의 트랜잭션에서 관리되었던 것들이 인스턴스 마저 분리되면서 DB의 트랜잭션 기능이 제 역할을 하지 못하게 되며 이를 제대로 하지 못함으로써 데이터의 일관성과 원자성을 보장 받을 수 없다는 단점이 생깁니다.

 

그렇다면 이러한 마이크로 서비스 아키텍처에서 트랜잭션을 할 수 있는 방법에는 어떤 것들이 있을까요?

 

가장 일반적으로 두 개 이상의 애플리케이션 혹은 분리되어 있는 서비스가 각각의 데이터베이스를 가진 경우, 이는 하나의 트랜잭션으로 묶을 수 있는 Two-Phase Commit으로 관리할 수 있습니다. 이 역시 기존의 모놀리식 아키텍처처럼 Transaction 관리자가 존재하여 이 매니저가 트랜잭션의 상태를 관리함으로써 DB 트랜잭션을 보장할 수 있습니다.

 

그러나 마이크로 서비스 아키텍처에서 위 방법은 한계가 있습니다. 왜냐하면 각 애플리케이션이 모두 관계형 데이터베이스를 쓴다면 얘기가 다르겠지만 NoSQL을 사용하는 등의 상황이 발생할 수도 있고, 더욱이 Transaction 관리자는 각 DB의 SQL 쿼리로 발생하여야 유효하지만 마이크로 서비스 아키텍처는 리소스를 가져오기 위해 API를 호출하기 때문에 이 방법을 적용하기에는 무리가 있습니다.

 

분산 트랜잭션 중에 가장 널리 알려진 패턴 중에 Saga 패턴이 있습니다. Saga 패턴은 단일 서비스 내 데이터를 갱신하는 일련의 로컬 트랜잭션입니다. 첫 번째 트랜잭션이 완료된 뒤, 두 번째 트랜잭션의 경우는 이전 작업 완료에 의해 트리거 되는 방식으로 구성되어 있어, 모든 서비스들이 메시지 의존하는 방법 등을 고안해야 합니다.

 

다음 포스트에서 Two-Phase Commit과 Saga 패턴에 대해 자세히 다뤄보도록 하겠습니다.

 

 

 

TAGS.
comments powered by Disqus

Tistory Comments 0