트랜잭션
하나 이상의 항목에 대한 작업의 집합을 의미한다.
아이템을 구매하는 예시를 하나 들어보자. 유저가 상대방에게 아이템을 구매하는 시나리오는 아래와 같다.
1. 유저의 골드 차감
2. 상대방의 아이템 제거
3. 유저에게 아이템 제공
4. 상대방에게 골드 제공
즉 1, 2, 3, 4의 경우에서 하나라도 빠지면 아이템 구매를 하는 데 버그가 발생할 수 있다. 즉 각각의 작업으로 존재하지만 묶음으로 처리가 되어야 하는 작업을 의미한다.
1번 API 전송 -> 2번 API 전송 -> 3번 API 전송 -> 4번 API 전송
이렇게 해도 동작만 한다면 문제가 발생하지 않는다. 그러나 묶음으로 처리가 되어야 하는 성격을 가진다면 하나의 API에서 1, 2, 3, 4번의 작업을 수행해서 리소스를 절약할 수 있다는 특징이 있다.
ACID 원칙
ACID 원칙은 트랜잭션을 설명하는 가장 중요한 원칙이다.
1. 원자성 (Atomicity)
2. 일관성 (Consistency)
3. 격리성, 독립성 (Isolation)
4. 영속성, 지속성 (Durability)
트랜잭션은 위 4가지 특징을 전부 만족해야 한다.
원자성
트랜잭션에 속해있는 각각의 명령을 하나의 단위로 취급한다. 즉 전부 성공하거나 중간에 하나가 실패하면 전부 실패해야 한다.
아이템 구매의 예시를 생각해 보면 1, 2, 3, 4번이 전부 수행되어야 아이템 구매가 성공한 것.
만약에 2번 작업(상대방의 아이템 제거)을 수행하다가 버그가 발생했다면? 3번은 수행되면 안 된다. 그리고 1번의 작업은 취소(롤백)가 되어야 한다.
그래서 트랜잭션은 전부 성공하면 진행(커밋), 하나라도 실패하면 이전 작업을 취소(롤백)를 진행한다.
커밋(Commit) : 트랜잭션 내의 모든 작업이 완료됨을 데이터베이스에게 알리는 명령. 커밋이 되면 데이터베이스에 모든 변경 사항을 반영한다.
롤백(Rollback) : 트랜잭션 내의 작업 중 하나라도 실패하거나 오류가 발생했을 때, 이전에 수행된 모든 변경 사항을 취소한다. 즉 초기 상태로 돌린다.
간단한 예시코드다.
private static final Long GOLD = 100000L;
public static void main(String[] args) throws IOException {
Item item = new Item("최강의 무기");
User userA = new User(10000L, new ArrayList<>(List.of(item)));
User userB = new User(150000L, new ArrayList<>());
// 구매 서비스
purchaseItem(userA, userB, item, GOLD);
System.out.println(userA.toString());
System.out.print(userB.toString());
}
유저 A는 최강의 무기와 만원을 가지고 있다. 유저 B는 이 무기를 10만 원에 구매하려고 한다.
그리고 구매 절차를 진행한다.
private static void purchaseItem(User seller, User buyer, Item item, Long price){
// 1. 구매 유저의 골드 차감
buyer.decreasedGold(price);
// 2. 판매 유저의 아이템 제거
seller.removeItem(item);
// 3. 구매 유저에게 아이템 제공
buyer.addItem(item);
// 4. 판매자에게 골드 제공
seller.increasedGold(price);
}
유저 A는 아이템이 사라지고 돈은 11만원이 되었고, 유저 B는 아이템이 생겼으며 돈은 5만 원이 되었다.
즉 정상적으로 구매 로직이 동작했다.
만약에 A 유저의 골드는 차감했지만 아이템을 제거하는 과정에서 에러가 발생한다면 어떤 결과가 나올까?
에러 상황을 발생시키기 위해 코드를 수정했다.
private static final Long GOLD = 100000L;
public static void main(String[] args) throws IOException {
Item item = new Item("최강의 무기");
User userA = new User(10000L, new ArrayList<>(List.of(item)));
User userB = new User(150000L, new ArrayList<>());
try{
purchaseItem(userA, userB, item, GOLD);
} catch (Exception e){
System.out.println(userA.toString());
System.out.print(userB.toString());
}
}
private static void purchaseItem(User seller, User buyer, Item item, Long price){
// 1. 구매 유저의 골드 차감
buyer.decreasedGold(price);
// 2. 판매 유저의 아이템 제거
throw new IllegalArgumentException("에러 발생");
// seller.removeItem(item);
// 3. 구매 유저에게 아이템 제공
// buyer.addItem(item);
// 4. 판매자에게 골드 제공
// seller.increasedGold(price);
}
2번 과정에서 임의로 예외를 발생시켜 에러 상황으로 가정했다.
그 결과 유저 A의 무기는 제거하다 에러가 발생했지만 1번으로 수행했던 골드 차감은 진행된 상태로 남아있다.
이는 원자성을 위반한 것이다.
원자성의 특징을 다시 보면 전부 성공하거나 하나라도 실패하면 이전 작업을 롤백하는 것이다.
즉 예외가 발생하면 이전 작업을 취소하는 롤백 작업을 따로 설정해야 한다.
일관성
트랜잭션이 실행되기 전과 후에 데이터베이스가 일관된 상태를 유지해야 한다는 의미를 가진다.
즉 데이터베이스가 정의된 모든 규칙, 제약 조건, 데이터 무결성 제약 조건 등을 항상 일관되게 만족해야 한다는 의미이다.
데이터베이스에는 외래 키 제약, 유일성 제약 등등 다양한 제약 조건을 걸어두게 되는데 트랜잭션 전과 후에 이런 제약 조건이 변경되면 안 된다는 말이다.
만약에 무기를 구매하기 위해 구매자의 골드를 차감했는데 음수로 바뀌어서 제약 조건을 만족하지 못하는 상태라면 롤백을 진행해야 하는 것이다.
다시 정리하면 트랜잭션 전에도 제약 조건을 만족한 상태여야 하고, 트랜잭션 이후에도 제약 조건을 만족한 상태를 유지해야 한다는 것.
격리성, 독립성
동시에 실행되는 트랜잭션이 서로에게 영향을 주면 안 된다는 의미이다. 서로 영향을 끼치지 못하게 만들어서 데이터베이스의 일관성을 유지하고 충돌을 피할 수 있다.
만약에 두 트랜잭션이 격리성이 보장이 안 되면 어떤 일이 발생할까?
유저 B가 아이템을 판매하고, 유저 A가 아이템을 구매하려고 한다.
이 중간에 유저 B의 또 다른 아이템이 판매되었다고 가정해보자.(여기서는 아이템은 신경 쓰지 않고 금액만 다루겠습니다.)
1. 유저 A는 아이템을 20만원에 구매하기 위해 본인의 잔고 100만 원을 읽어서 20만 원 차감 진행
2. 유저 B는 본인의 잔고 200만원을 읽어서 20만 원을 추가하려고 함.
3. 그 사이에 유저 B의 또 다른 아이템이 판매되어 200만원 + 50만 원이 돼서 250만 원으로 변경됨.
4. 그러나 유저 B의 초기 read는 200만원으로 읽었기 때문에 200 + 20만 원 해서 최종 유저 B의 잔고는 220만 원으로 업데이트.
트랜잭션 간의 격리성이 지켜지지 않아서 발생한 문제인 것이다. 격리 수준을 설정해서 이런 문제를 방지할 수 있다.
(격리 수준은 여기서 다루진 않겠습니다.)
영속성, 지속성
트랜잭션을 성공적으로 수행했다면 트랜잭션 결과가 영구적으로 반영되어야 한다는 의미이다.
즉 트랜잭션이 커밋되면 그 변경 사항은 반드시 데이터베이스에 저장되어야 하며, 이후 어떤 문제가 발생하더라도 이 변경 사항은 유지되어야 한다는 의미이다.
만약에 무기를 정상적으로 구매했고 커밋까지 됐으면 시스템 장애가 발생해도 아래의 변경 사항은 무조건 보장되어야 한다.
- 유저 A의 아이템 제거
- 유저 A의 골드 추가
- 유저 B의 아이템 추가
- 유저 B의 골드 제거
스프링에서는 트랜잭션을 AOP를 이용해서 보장하기 위해 @Transactional을 제공한다.
https://qkrqkrrlrl.tistory.com/132
@Transactional 어노테이션의 역할
트랜잭션의 개념을 여러분들은 많이 들어보셨을 것이다. 트랜잭션작업을 처리하는 명령의 모임.이 작업은 모두 성공하거나, 중간에 하나라도 실패하면 이전 성공도 모두 실패로 돌아간다는 원
qkrqkrrlrl.tistory.com
참고 자료
[데이터베이스] 트랜잭션의 ACID 성질 - 하나몬
트랜잭션이란 여러 개의 작업을 하나로 묶은 실행 유닛을 말한다. 데이터베이스 트랜잭션은 ACID라는 특성을 가지고 있다. ACID는 데이터베이스 내에서 일어나는 하나의 트랜잭션(transaction)의 안
hanamon.kr
https://docs.bigchaindb.com/en/latest/korean/transaction-concepts_ko.html
트랜잭션 개념 — BigchainDB 2.2.2 documentation
트랜잭션은 물건 (예 : 자산)을 등록, 발행, 생성 또는 전송하는 데 사용됩니다. 트랜잭션은 BigchainDB가 저장하는 가장 기본적인 종류의 레코드입니다. CREATE 트랜잭션과 TRANSFER 트랜잭션의 두 종류
docs.bigchaindb.com
https://f-lab.kr/insight/transaction-management-and-acid-principles
트랜잭션 관리와 ACID 원칙의 중요성
데이터베이스 시스템에서 트랜잭션 관리와 ACID 원칙의 중요성에 대해 설명하고, 실제 시스템에서의 적용 사례를 제공하는 글입니다.
f-lab.kr
https://www.youtube.com/watch?v=sLJ8ypeHGlM
'CS지식' 카테고리의 다른 글
CSRF 공격을 막기 위한 CSRF 토큰 (0) | 2024.07.31 |
---|---|
spring-oauth-client 라이브러리의 동작 흐름 정리 (0) | 2024.06.27 |
SOP 정책 (Feat, CORS 및 Preflight) (0) | 2024.05.25 |
4-way handshake(feat, TCP 연결 해제) (1) | 2024.05.21 |