회사에서 최근 업무를 진행하면서 트랜잭션을 사용하는 일이 매우 많다. 현재 MongoDB를 이용하여 개발하고 있는 프로젝트에서 트랜잭션을 사용하고 있는 이유는 아래와 같다.
- 대용량의 데이터를 벌크로 처리해야하는 경우가 많은데, 이 때 하나의 데이터라도 유효하지 않거나 실행 중 실패가 발생한다면 모든 처리 내용을 롤백 시켜야 함
- 여러 사람들이 하나의 데이터에 동시에 접근하여 수정을 가하는 경우가 발생할 확률이 기획 상 매우 높음
- 여러 컬렉션에 위치한 여러 도큐먼트에 대한 삽입이나 수정이 하나의 서비스 로직에서 수행되는 경우가 많음
위와 같은 이유들로 트랜잭션을 진행하였고, 이들은 곧 트랜잭션으로 얻을 수 있는 가장 큰 장점들이다. 이렇게 개발 과정에서 어느 경우에 트랜잭션을 사용하는지는 알고 있지만, 이에 대해 이론적으로 정리하여 말하기에는 부족한 정도로 알고 있다고 생각하여 자세히 알아보았다.
트랜잭션이란?
우선 트랜잭션의 정의부터 알아보자. 트랜잭션은 분할이 더 이상 불가능한 원자성을 가지는 하나의 처리 단위라고 할 수 있다.
물리학에서 원자는 물질의 가장 작은 단위로 더 이상 쪼개질 수 없다고 약 90년간 여겨져왔다. (물론 원자도 쪼개질 수 있음이 밝혀졌지만) 물리학에서의 원자처럼 컴퓨터 과학에서의 트랜잭션도 더 작은 단위로 나눌 수 없는 단위라고 생각하면 된다. 쉽게 말해서 반드시 한번에 처리되어야 하는 로직이라고 보면 된다.
가장 흔하게 실생활에서 접할 수 있는 트랜잭션을 예시로 들어보겠다.
A의 계좌에는 2000원이, B의 계좌에는 1000원이 있다고 해보자. A가 500원을 B의 계좌로 송금하는 상황을 생각해보자.
우선 A가 B에게 500원을 송금할 것이므로 은행 서버는 500원을 A의 계좌에서 출금한다. 그 후 즉시 출금액 500원을 B의 계좌에 입금한다. 이렇게 하면 A의 계좌에는 1500원, B의 계좌에도 1500원이 있게 됨으로써 500원 송금이 완료가 되는 것이다.
위의 사례에서 송금이라는 하나의 로직은
- A의 계좌에서 500원을 출금
- B의 계좌에 500원을 입금
이라는 두 개의 작업으로 이루어짐을 확인할 수 있다. 그렇다면 송금 로직을 위에서 나눈 것처럼 1과 2로 나누어서 처리해도 될까? 단순하게 생각했을 때는 문제가 없지만, 곰곰히 생각해보면 송금 로직이 나눠지면 안되는 이유, 즉 송금 로직을 하나의 트랜잭션으로 여겨야만 하는 이유를 알 수 있다. 만약 송금 과정에서 1번이 성공하였는데, 2번이 실패한 경우를 생각해보자. A 입장에서는 500원이 송금이 된 것처럼 보이지만, B 입장에서는 어떤 돈도 받지 못한 치명적인 상황이 발생하게 된다. 이를 방지하기 위해선 1번과 2번 과정은 반드시 둘 다 성공하거나 둘 다 실패해야하는 것이다.
위의 예시처럼 반드시 한꺼번에 수행되어야 하는 로직이 바로 트랜잭션인 것이다. 트랜잭션을 수행함으로써 우리는 4가지의 중요한 속성들을 보장받을 수 있다. 이 속성들이 그 유명한 트랜잭션의 ACID 속성으로 데이터의 유효성을 보장하여준다.
ACID란?
ACID는 트랜잭션의 속성인 Atomicity(원자성), Consistency(일관성), Isolation(고립성), Durability(영구성)의 앞글자를 딴 것이다. 이들을 하나하나 간단하게 알아보자.
- Atomicity(원자성): 원자성은 트랜잭션 내의 모든 작업들이 모두 실행되거나 모두 실행되지 않게(all-or-nothing) 단일 작업 단위로 취급되도록 하는 속성이다. 트랜잭션 중에 시스템 다운이나 제약 조건 위반 등의 오류가 발생하면 트랜잭션 중에 변경된 모든 내용이 롤백되어 시스템이 트랜잭션이 시작되기 전 상태로 복원된다. 데이터베이스가 일부 작업만 적용된 상태로 유지되지 않도록 한다.
- Consistency(일관성): 일관성은 트랜잭션 작업이 시작되기 전이 일관된 상태였다면, 작업이 종료되었을 때도 일관된 상태여야 한다는 것이다. 일관된 상태는 데이터베이스가 무결성 제약 조건 및 비즈니스 규칙을 포함하여 정의된 모든 제약 조건, 규칙 및 규정을 준수함을 의미한다. 예를 들어 계좌의 잔고가 0 이하로 떨어지지 않아야 하는 제약조건이 있는 경우 일관성은 이 규칙을 위반할 수 있는 모든 트랜잭션이 이를 제약조건을 준수하도록 수정되거나 그렇지 않다면 모두 거부되도록 한다.
- Isolation(고립성): 고립성은 동시에 처리 요청된 트랜잭션이 서로에게 영향을 끼치지 않아야한다는 것이다. 즉, 원래 트랜잭션이 커밋될 때까지 원래 트랜잭션에서의 처리 내용이 다른 동시에 작업 중인 트랜잭션에 표시되지 않아야 한다. 격리하지 않으면 한 사용자의 불완전한 트랜잭션이 다른 사용자에게 표시되어 잠재적으로 오류나 혼란을 초래할 수 있다.
- Durability(영구성): 영구성은 트랜잭션이 일단 커밋되면 시스템에 오류가 발생하더라도 커밋 결과가 영구적임이 보장되어야 한다는 것이다. 이는 커밋된 트랜잭션 처리 사항들을 하드디스크에 로깅하고, 시스템 오류가 발생한 경우 이러한 로그를 사용하여 데이터베이스를 일관된 마지막 상태로 복구하는 방법 등을 통해 실제 데이터베이스에서도 실현되고 있다.
위와 같은 속성들을 통해 우리는 데이터베이스 시스템을 사용하면서 발생할 수 있는 이상 현상을 최소화하고, 무결성을 가지며, 신뢰할 수 있는 데이터베이스를 구축해나갈 수 있다.
위의 은행 송금 서비스에서 트랜잭션을 통해 ACID 속성들이 갖춰질 때 어떤 장점들을 가질 수 있는지 예시를 들어 자세하게 설명해보겠다.
- 원자성 (Atomicity)
고객이 자신의 계좌에서 일정 금액을 이체하려고 할 때, 이체 과정은 한 트랜잭션으로 묶이게 된다.
그렇기에 이체 과정 중 어느 한 과정이 잔액 부족 등의 문제로 인해 실패하면, 이전 상태로 롤백되어 돈은 전혀 이체되지 않는다. - 일관성 (Consistency)
이체 과정에서 고객의 계좌 잔액은 이체된 금액만큼 감소되어야 한다.
이체 과정에서 수신자의 계좌 잔액은 이체된 금액만큼 증가되어야 한다.
이러한 조건들은 트랜잭션을 통해 일관된 상태로 유지된다. 이처럼 특정 규칙이나 규정이 일관적으로 준수될 수 있다. - 격리성 (Isolation)
한 고객의 이체 작업이 다른 고객의 이체 작업에 영향을 주지 않음이 보장된다.
그렇기에 동시에 여러 고객이 이체를 시도하더라도 각각의 이체 작업은 서로 격리되어 동시에 실행될 수 있다. - 지속성 (Durability)
이체가 성공적으로 완료되면 데이터베이스에 이체된 금액의 기록이 영구적으로 저장된다.
시스템 장애 또는 다른 문제가 발생하더라도 고객의 이체 내역과 이로 인한 계좌 잔액의 변화는 유지된다.
위와 예시들을 통해 트랜잭션를 통해 보장되는 ACID 속성들이 실제 서비스 되는 환경에서 어떤 장점을 가질지 확인할 수 있다.
그렇다면 이제 마지막으로 트랜잭션이 실제 데이터베이스 관리 시스템(DBMS)에서 어떻게 연산되는지 확인해보자.
트랜잭션의 연산 과정
위의 그림은 트랜잭션이 연산 되는 과정에서 단계가 어떻게 변화되는지 도식화한 전이도이다.
그림에 나타난 각 트랜잭션의 단계를 상세하게 설명해보겠다.
- 활성: 트랜잭션이 정상적으로 시작된 상태이다. 트랜잭션이 시작된 후, 연산들의 실행 결과에 따라 이후 상태가 결정된다.
- 부분 완료: 트랜잭션의 마지막 연산까지 정상적으로 실행된 경우의 상태이다. 정상적으로 모든 명령을 완료한 뒤, 최종적으로 연산 결과를 어떻게 처리할 것인지 대기하는 상태이다.
- 실패: 트랜잭션의 연산들을 실행하던 중 시스템 장애, 제약조건 위반 등의 오류가 발생한 상태이다. 부분 완료 상태에서 모든 명령이 완료된 경우에도 Abort 명령이 들어오면 실패 상태로 변화되기도 한다.
- 완료: 부분완료 상태에서 연산이 완료된 결과들을 최종적으로 commit 명령을 통해 DB에 업데이트하고 트랜잭션을 정상적으로 종료한다.
- 철회: 트랜잭션이 실패 상태에서 비정상적으로 종료된 것이므로 rollback 명령을 통해 트랜잭션 처리 과정에서의 변경사항을 초기화하고, 시작 이전 상태로 DB를 되돌린 상태이다. 즉, 최종적으로 commit된 DB의 상태로 되돌리는 것이다.
'개발 > CS' 카테고리의 다른 글
Python은 Call by value일까, Call by reference일까? (2) | 2024.05.22 |
---|---|
(CS) 무중단 배포 (0) | 2024.01.08 |
(CS) 계수 정렬 & 기수 정렬 (1) | 2023.12.25 |
(CS) 트리 & 트라이 (0) | 2023.12.25 |
(CS) 객체와 객체지향이란? (0) | 2023.12.19 |