<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>local-kim.log</title>
        <link>https://velog.io/</link>
        <description>백엔드 개발자😎</description>
        <lastBuildDate>Thu, 02 Jan 2025 07:15:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>local-kim.log</title>
            <url>https://velog.velcdn.com/images/local-kim/profile/0d53b642-4441-4d1a-9258-392483843863/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. local-kim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/local-kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[트랜잭션 격리수준과 동시성 제어]]></title>
            <link>https://velog.io/@local-kim/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B2%A9%EB%A6%AC%EC%88%98%EC%A4%80%EA%B3%BC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4</link>
            <guid>https://velog.io/@local-kim/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B2%A9%EB%A6%AC%EC%88%98%EC%A4%80%EA%B3%BC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4</guid>
            <pubDate>Thu, 02 Jan 2025 07:15:08 GMT</pubDate>
            <description><![CDATA[<p>데이터베이스의 트랜잭션은 ACID 속성을 만족해야한다. ACID 중 I인 격리성(Isolation)은 두 개 이상의 트랜잭션이 동시에 실행되어도 서로 격리된 환경에서 실행되는 것과 같은 결과가 나와야한다는 의미이다. </p>
<p>트랜잭션 사이의 격리성이 높아질수록 동시성이 낮아진다. 즉, 격리성과 동시성은 서로 반비례 관계이다. 그렇기 때문에 DBMS는 트랜잭션 격리 수준을 설정할 수 있도록 제공하고, 사용자는 이 값을 조절하여 격리성을 얼마나 엄격하게 지킬지 설정할 수 있다.</p>
<h1 id="트랜잭션-격리-수준">트랜잭션 격리 수준</h1>
<h3 id="read-uncommitted">Read Uncommitted</h3>
<ul>
<li>커밋하지 않은 데이터를 읽을 수 있는 격리수준</li>
<li>구현 방법<ul>
<li>락을 걸지 않음(=아무 조치를 하지 않음)</li>
</ul>
</li>
<li>발생할 수 있는 문제<ul>
<li>Dirty Read, Non-repeatable Read, Phantom Read, Lost Update</li>
</ul>
</li>
</ul>
<h3 id="read-committed">Read Committed</h3>
<ul>
<li>다른 트랜잭션에서 커밋한 데이터를 읽을 수 있는 격리수준</li>
<li>다른 트랜잭션의 커밋 여부에 따라 조회결과가 달라질 수 있다</li>
<li>구현 방법<ul>
<li>변경된 데이터이면 Undo log로부터 변경되기 전의 데이터를 읽고, 커밋된 데이터이면 테이블(버퍼 캐시)에서 읽는다</li>
<li>쓰기 작업에 배타락을 건다</li>
</ul>
</li>
<li>발생할 수 있는 문제<ul>
<li>Non-repeatable Read, Phantom Read, Lost Update</li>
</ul>
</li>
</ul>
<h3 id="repeatable-read">Repeatable Read</h3>
<ul>
<li>한 트랜잭션 내에서는 데이터의 조회결과가 같은 격리수준</li>
<li>구현 방법<ul>
<li>1) MVCC 이용<ul>
<li>트랜잭션이 시작된 후 변경된 데이터이면 Undo log로부터 변경되기 전의 데이터를 읽는다</li>
<li>배타락을 거는 경우엔 추가된 데이터는 조회된다(언두로그를 잠그는 것이 불가능하여 버퍼 캐쉬의 데이터를 가져오기 때문) → Phantom Read 발생</li>
<li>MySQL에서는 Gap Lock을 이용해 Phantom Read가 발생하지 않는다</li>
</ul>
</li>
<li>2) 읽을 때 공유락을 걸고 트랜잭션이 종료될 때까지 유지한다</li>
</ul>
</li>
<li>발생할 수 있는 문제<ul>
<li>Phantom Read, Lost Update</li>
</ul>
</li>
</ul>
<h3 id="serializable">Serializable</h3>
<ul>
<li>절대 여러 트랜잭션에서 같은 데이터에 접근할 수 없는 가장 엄격한 격리수준</li>
<li>데이터 부정합 문제가 전혀 발생하지 않는다</li>
<li>구현 방법<ul>
<li>조회한 모든 데이터에 대하여 공유락을 걸고 트랜잭션이 종료될 때까지 유지한다(1개든 여러개든)</li>
</ul>
</li>
<li>데드락이 발생할 확률이 높다<ul>
<li>일반적으로 데드락은 여러 트랜잭션이 각자 자원을 점유한 채로 서로의 자원을 대기할 때 발생한다(여러 개의 자원)</li>
<li>하지만 여기서 데드락은 여러 트랜잭션이 하나의 자원에 공유락을 건 채로 서로 배타락을 걸기 위해 대기할 때 발생한다(하나의 자원)</li>
</ul>
</li>
<li>발생할 수 있는 문제<ul>
<li>없음</li>
</ul>
</li>
</ul>
<br>

<h2 id="격리-수준의-한계-→-동시성-제어가-필요한-이유">격리 수준의 한계? → 동시성 제어가 필요한 이유!</h2>
<p>동시성 문제를 해결하기 위해서 가장 엄격한 격리 수준인 Serializable로 높인다면, 동시성이 현저히 떨어지고 데드락 문제가 발생한다. 따라서 DBMS 수준의 트랜잭션 격리가 아닌 어플리케이션 단에서의 동시성 제어가 필요하다.</p>
<p>동시성 제어는 개발자가 선택적으로 적용할 수 있기 때문에 정합성이 중요한 곳에만 유연하게 사용할 수 있다.
<br><br></p>
<h1 id="동시성-제어">동시성 제어</h1>
<h3 id="pessimistic-lock">Pessimistic Lock</h3>
<ul>
<li>트랜잭션이 데이터를 읽을 때 락을 걸고 끝날 때까지 유지한다(공유락, 배타락 모두 가능)</li>
<li>동시성이 매우 저하된다</li>
<li>데드락이 발생할 수 있다<ul>
<li>공유락 : 여러 트랜잭션이 하나의 자원에 공유락을 건 채로 서로 배타락을 걸기 위해 대기할 때</li>
<li>배타락 : 여러 트랜잭션이 각자 자원을 배타락을 건 채로 서로의 자원에 배타락을 걸기 위해 대기할 때</li>
</ul>
</li>
</ul>
<h3 id="optimistic-lock">Optimistic Lock</h3>
<ul>
<li>테이블의 version 컬럼을 이용</li>
<li>version은 데이터가 변경될 때마다 1씩 증가하는 값</li>
<li>데이터를 업데이트 할 때 조회했던 version 값이랑 다르면(충돌) 트랜잭션을 롤백시킴</li>
<li>락을 실제로 거는게 아니기 때문에 비관적 락보다 동시성이 좋지만 충돌 발생 시 롤백을 해야함</li>
<li>충돌이 많이 발생하는 경우 성능 저하 문제</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Microservice Architecture]]></title>
            <link>https://velog.io/@local-kim/Microservice-Architecture</link>
            <guid>https://velog.io/@local-kim/Microservice-Architecture</guid>
            <pubDate>Thu, 19 Sep 2024 14:32:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/local-kim/post/b358fbea-33f4-40fb-9241-34e4a114f1bc/image.png" alt=""></p>
<h1 id="monolithic-architecture">Monolithic Architecture</h1>
<p><strong>모놀리식 아키텍처</strong>는 하나의 모듈로 이루어진 시스템을 말하는 용어이다. 모듈이 하나이기 때문에 코드 관리에 용이하고 개발과 배포가 쉽다는 등의 많은 장점이 있다. 하지만 시스템이 점점 거대해질수록 모듈을 빌드하는데에 오래 걸리게 되는데, 작은 변경사항이 생겨도 모듈 전체를 다시 빌드하고 배포해야하는 모놀리식 아키텍처에서는 매우 비효율적이다. 또한 어느 한 부분에 오류가 생기면 전체 시스템이 영향을 받게된다. 그래서 등장한 것이 바로 마이크로서비스 아키텍처이다.</p>
<h1 id="microservice-architecture">Microservice Architecture</h1>
<p><strong>마이크로서비스 아키텍처</strong>는 독립적인 작은 서비스들로 이루어진 시스템을 말한다. 각 서비스는 독립적인 데이터베이스를 가지고, 다른 서비스에 영향을 끼치지 않고 독립적으로 개발, 빌드, 배포된다. 그래서 서비스마다 다른 프레임워크나 데이터베이스를 사용하는 것이 가능하여 유연한 시스템을 구축할 수 있고 서비스를 확장하기에도 용이하다.</p>
<p>하지만 마이크로서비스 아키텍처에는 장점만 있는 것이 아니다. 마이크로서비스끼리 서로 통신하면서 여러 데이터베이스에 걸쳐 트랜잭션이 진행되어 데이터의 일관성을 관리하기 힘들다. <em>(다음 글에서 분산 트랜잭션의 두 가지 관리 방법에 대해서 설명한다)</em> 또한 각 서비스마다 생성되는 로그를 중앙집중화하여 관리해야한다.</p>
<br>

<p>두 아키텍처 중에 좋고 나쁜 것은 없다. 프로젝트 규모에 따라 더 적합한 아키텍처를 선택하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[분산 트랜잭션 관리 방법(2) : SAGA Pattern]]></title>
            <link>https://velog.io/@local-kim/%EB%B6%84%EC%82%B0-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EB%B2%952-SAGA-Pattern</link>
            <guid>https://velog.io/@local-kim/%EB%B6%84%EC%82%B0-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EB%B2%952-SAGA-Pattern</guid>
            <pubDate>Fri, 13 Sep 2024 15:21:11 GMT</pubDate>
            <description><![CDATA[<p>Microservice Architecture에서 분산 트랜잭션을 관리하기 위한 방법에는 이전 글에서 소개한 Two-phase Commit 외에도 SAGA Pattern이 있다.</p>
<h1 id="saga-pattern">SAGA Pattern</h1>
<p><span style="color:Gray"><em>‘Saga’는 아이슬란드어로 ‘이야기’라는 뜻이다.</em></span></p>
<p><strong>SAGA Pattern</strong>은 일련의 로컬 트랜잭션으로 이루어진 분산 트랜잭션의 일관성을 관리하는 방법이다. 로컬 트랜잭션이 성공하면 다음 단계의 로컬 트랜잭션을 실행하고, 로컬 트랜잭션이 실패하면 이전에 실행한 로컬 트랜잭션을 롤백하는 <strong>보상 트랜잭션(Compensate Transaction)</strong>을 실행한다. SAGA Pattern은 Saga 단계가 모두 완료되기 전까지는 일관성이 유지되지 않지만, 전체 Saga가 성공하거나 실패하여 완료된 후에는 일관성을 유지하는 <strong>결과적 일관성</strong>을 보장한다.</p>
<blockquote>
<p><strong>결과적 일관성(Eventual Consistency)</strong></p>
<p>분산 환경에서 고가용성을 위해 모든 노드에서 항상 즉각적으로 일관된 상태를 유지하는 것을 포기하고, 시간이 지나면 언젠가 반드시 일관된 상태에 도달하는 일관성 모델. 여기서 일관성이란 모든 노드에서 같은 시간에 같은 데이터를 볼 수 있는 것을 말함. (분산 시스템에서의 일관성과 ACID 트랜잭션의 일관성은 정의가 다름)</p>
</blockquote>
<blockquote>
<p><strong>가용성(Availability)</strong></p>
<p>서버, 네트워크, 프로그램 등의 시스템이 정상적으로 사용 가능한 정도</p>
</blockquote>
<br>

<h2 id="보상-트랜잭션">보상 트랜잭션</h2>
<p>Two-phase Commit과는 달리 SAGA Pattern에서는 각 로컬 트랜잭션이 실행을 마치면 데이터베이스에 커밋한 후에 다음 단계의 로컬 트랜잭션으로 넘어간다. 그래서 Saga가 실패했을 경우 이미 커밋된 로컬 트랜잭션의 실행을 취소할 <strong>보상 트랜잭션</strong>을 실행하여 직접 롤백을 해주어야 한다. 보상 트랜잭션은 데이터베이스가 제공하는 롤백 기능을 어플리케이션 레벨에서 구현한 것이다.</p>
<br>

<h2 id="피봇-트랜잭션">피봇 트랜잭션</h2>
<p><strong>피봇 트랜잭션(Pivot Transaction)</strong>은 전체 Saga의 성공과 실패를 결정하는 트랜잭션이다.</p>
<p>피봇 트랜잭션 이전의 트랜잭션은 Saga의 결과에 따라서 롤백할 가능성이 존재하기 때문에 <strong>보상 가능 트랜잭션(Compensatable Transaction)</strong>이라고 부른다. 물론 피봇 트랜잭션 이전이라도 조회만 하는 트랜잭션의 경우에는 보상 트랜잭션이 필요 없다.</p>
<p>피봇 트랜잭션 이후의 트랜잭션은 Saga가 성공했으므로 반드시 실행되어야하기 때문에 <strong>재시도 가능 트랜잭션(Retriable Transaction)</strong>이라고 부른다. 보상 트랜잭션 또한 Saga가 실패했으므로 반드시 실행되어야하기 때문에 재시도 가능 트랜잭션이다.</p>
<p>피봇 트랜잭션은 일반적으로 보상 가능 트랜잭션과 재시도 가능 트랜잭션 모두 아니지만, 보상 가능 트랜잭션의 마지막 단계나 재시도 가능 트랜잭션의 첫 단계로 볼 수도 있다.</p>
<br>

<h2 id="saga-state-machine">SAGA State Machine</h2>
<p>SAGA Pattern에서는 로컬 트랜잭션이 다음 로컬 트랜잭션이나 보상 트랜잭션을 트리거하기 때문에 State Machine으로 모델링하여 전체 Saga의 흐름을 관리하기에 적합하다. 주문 시스템을 예시로 들어 State Machine을 그려보면 다음과 같다.</p>
<p><span style="background-color:PaleGreen">초록색</span>은 보상 가능 트랜잭션, <span style="background-color:Gold">노란색</span>은 피봇 트랜잭션, <span style="background-color:PaleTurquoise">파란색</span>은 재시도 가능 트랜잭션, <span style="background-color:Pink">빨간색</span>은 보상 트랜잭션(이자 재시도 트랜잭션)이다. <span style="background-color:PaleTurquoise">파란색 화살표</span>는 로컬 트랜잭션이 성공하는 경우, <span style="background-color:Pink">빨간색 화살표</span>는 실패하는 경우, <span style="background-color:LightGray">검은색 화살표</span>는 반드시 실행되는 경우이다.</p>
<p><img src="https://velog.velcdn.com/images/local-kim/post/0bbd2b43-b842-44c8-9617-a42feaaa4f39/image.png" alt="출처 : 나"></p>
<ol>
<li>Saga가 시작되면 <strong>보상 가능 트랜잭션</strong>이 시작된다. 각 단계는 성공하면 로컬 트랜잭션을 커밋한 뒤 다음 단계로 넘어가고, 실패하면 로컬 트랜잭션을 롤백한 뒤 이전에 성공한 보상 가능 트랜잭션의 보상 트랜잭션을 실행한다. 여기서 <code>주문 생성</code> 단계는 이전에 실행된 보상 가능 트랜잭션이 없기 때문에 실패하면 로컬 트랜잭션을 롤백한 뒤 바로 Saga가 <code>실패</code> 상태가 된다. <code>재고 -1 변경</code> 단계가 성공하면 다음 단계로 넘어가고, 실패하면 <code>주문 생성</code> 단계의 보상 트랜잭션인 <code>주문 취소</code>를 실행한다.</li>
<li><strong>피봇 트랜잭션</strong>인 <code>결제</code> 단계는 보상 가능 트랜잭션과 동일한 로직으로 실행된다. 하지만 결제 단계에는 보상 트랜잭션이 존재하지 않기 때문에 여기서 피봇 트랜잭션은 보상 가능 트랜잭션도, 재시도 가능 트랜잭션도 아니다.</li>
<li>피봇 트랜잭션이 성공하면 <strong>재시도 가능 트랜잭션</strong>인 <code>주문 확인 메일 전송</code> 단계로 넘어가는데, 주문 확인 메일을 전송하는 것에 실패하더라도 계속 재시도하여 반드시 이 단계를 성공시켜야 한다. 모든 재시도 가능 트랜잭션이 완료되면 Saga는 <code>성공</code> 상태로 넘어간다.</li>
<li>보상 가능 트랜잭션이나 피봇 트랜잭션이 실패하면 <strong>보상 트랜잭션</strong>을 실행하는데, 보상 트랜잭션이 실행된다는 것은 전체 Saga가 실패했다는 의미이므로 보상 트랜잭션 또한 실패하더라도 계속 재시도하여 반드시 성공시켜야 한다. 모든 보상 트랜잭션이 완료되면 Saga는 <code>실패</code> 상태로 넘어간다.</li>
</ol>
<br>

<h2 id="구현-방식">구현 방식</h2>
<p>SAGA Pattern은 SAGA의 흐름을 제어하는 방식에 따라 두 가지로 나뉜다.</p>
<h3 id="choreography-based-saga">Choreography-based SAGA</h3>
<p><strong>Choreography-based SAGA</strong>는 마이크로 서비스끼리 직접 통신하여 Saga를 진행시키는 방식이다. (통신에는 동기 방식과 비동기 방식을 모두 사용할 수 있다.)</p>
<p>ex) Product 서비스에서 <code>재고 -1 변경</code> 단계가 성공하면 Payment 서비스와 통신하여 다음 단계인 <code>결제</code> 단계를 트리거하고, 실패하면 Order 서비스와 통신하여 보상 트랜잭션인 <code>주문 취소</code> 단계를 트리거한다.</p>
<ul>
<li>장점<ul>
<li>중앙 코디네이터가 없기 때문에 구현이 비교적 간단하다.</li>
</ul>
</li>
<li>단점<ul>
<li>전체 Saga의 흐름을 파악하기 어렵다. → 서비스의 확장과 디버깅이 어렵다.</li>
<li>마이크로 서비스들이 서로 의존성을 가지기 때문에 시스템의 결합도가 높아진다.</li>
<li>마이크로 서비스 사이에서 순환 의존성이 발생할 가능성이 있다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>순환 의존성(Cyclic Dependency)</strong></p>
<p>A가 B에 의존하고, B가 C에 의존하고, C가 A에 의존하여 결국 서로 순환적으로 의존하게 되는 것</p>
</blockquote>
<h3 id="orchestration-based-saga">Orchestration-based SAGA</h3>
<p><strong>Orchestration-based SAGA</strong>는 Saga의 흐름을 제어하는 중앙 코디네이터인 <strong>오케스트레이터(Orchestrator)</strong>가 마이크로 서비스들과 통신하여 Saga를 진행시키는 방식이다. 각 마이크로 서비스는 오케스트레이터의 명령을 받아 실행되고, 그 결과를 다시 오케스트레이터에게 전달한다.</p>
<p>ex) 오케스트레이터가 Product 서비스의 <code>재고 -1 변경</code> 단계를 트리거하고, Product 서비스로부터 받은 결과에 따라 Payment 서비스의 <code>결제</code> 단계를 트리거하거나 Order 서비스의 <code>주문 취소</code> 단계를 트리거한다.</p>
<ul>
<li>장점<ul>
<li>오케스트레이터가 Saga를 제어하기 때문에 전체 Saga의 흐름을 쉽게 파악할 수 있다. → 디버깅이 쉽다.</li>
<li>마이크로 서비스가 서로 어떤 일을 하는지 알 필요가 없고 서로 의존성을 가지지 않아 시스템의 결합도가 낮아진다.</li>
<li>오케스트레이터는 Saga 로직만 처리하고, 마이크로 서비스는 도메인 로직만 처리하도록 분리하여 로직이 단순해진다.</li>
</ul>
</li>
<li>단점<ul>
<li>오케스트레이터가 단일 장애 지점이 되기 때문에, 오케스트레이터에 장애가 발생하면 전체 시스템에 문제가 발생할 수 있다.</li>
<li>오케스트레이터에 많은 트래픽이 몰릴 수 있다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>의존성과 결합도, 응집도</strong></p>
<ul>
<li><p><strong>의존성(Dependency)</strong> : A 모듈이 B 모듈을 사용할 때, A는 B에 의존한다고 말한다. (A→B) 이때, B에 변경사항이 생기면 A에도 변경사항이 생길 수도 있다. 의존성이 높아질수록 하나의 변경이 다른 부분에 미치는 영향이 커진다.</p>
</li>
<li><p><strong>결합도(Coupling)</strong> : 두 모듈 간의 의존하는 정도. 결합도가 낮을수록 서로 독립적으로 동작하고, 좋은 시스템이라고 할 수 있다.</p>
</li>
<li><p><strong>응집도(Cohesion)</strong> : 한 모듈 내의 구성 요소들이 연관되어있는 정도. 응집도가 높을수록 모듈이 하나의 명확한 역할을 잘 수행하고, 좋은 시스템이라고 할 수 있다.</p>
</li>
</ul>
<hr>
<p><strong>의존성과 결합도의 관계</strong> : 두 모듈 간의 의존성이 많아질수록 결합도가 높아진다.</p>
<p><strong>결합도와 응집도의 관계</strong> : 결합도와 응집도는 상호 보완적인 관계로, 결합도가 낮고 응집도가 높아질수록 좋은 시스템이다.</p>
</blockquote>
<br>

<h2 id="어떻게-acid를-보장하는가">어떻게 ACID를 보장하는가?</h2>
<ul>
<li>Atomicity : Saga가 종료되고 나면 모든 로컬 트랜잭션이 commit 또는 rollback 된다.</li>
<li>Consistency : 각 로컬 트랜잭션에서 제약조건 등에 의해 일관성이 보장된다.</li>
<li><span style="color:Crimson">Isolation : 로컬 트랜잭션은 데이터베이스에 커밋되고 다음 단계로 넘어가기 때문에 다른 분산 트랜잭션에서 변경된 데이터에 접근할 수 있어 <strong>격리성이 보장되지 않는다.</strong></span></li>
<li>Durability : 각 로컬 트랜잭션은 Redo log와 Undo log를 기록하여 영속성을 보장한다.</li>
</ul>
<br>

<h2 id="isolation을-보장하기-위한-방법">Isolation을 보장하기 위한 방법</h2>
<h3 id="semantic-lock">Semantic Lock</h3>
<p>분산 트랜잭션의 격리성을 보장하는 방법 중의 하나는 어플리케이션 레벨의 Lock 기법인 <strong>Semantic Lock</strong>이다. 이를 위해 모든 테이블에는 Semantic Lock을 위한 플래그가 필요하다. 플래그가 설정된 레코드는 Saga 단계가 진행 중이라는 의미이므로 다른 Saga에서 접근하려고 할 때 처리를 해주어야 한다.</p>
<p>Semantic Lock의 플래그에서 가질 수 있는 상태는 <code>Saga 진행 중</code>, <code>Saga 진행 중이 아님</code> 뿐만 아니라 레코드가 가지는 상태와 합쳐져 다양한 상태를 정의할 수 있다. 예를 들어, Order가 <code>APPROVED</code>, <code>CANCELED</code>, <code>COMPLETED</code>의 상태를 갖고 있다고 하면, 플래그를 추가하여 <code>APPROVED_PENDING</code>, <code>APPROVED</code>, <code>CANCELED_PENDING</code>, <code>CANCELED</code>, <code>COMPLETED_PENDING</code>, <code>COMPLETED</code>의 상태를 가질 수 있다. 물론 비즈니스 로직을 정의하는 과정에서 더 다양한 플래그를 정의할 수도 있다.</p>
<p>Saga에서 Semantic Lock을 사용하는 흐름은 아래와 같다.</p>
<ol>
<li>보상 가능 트랜잭션에서 레코드를 생성하거나 변경하면 <code>Saga 진행 중</code>이라는 의미의 플래그를 설정한다.</li>
<li>Saga가 종료되기 전에 <code>Saga 진행 중이 아님</code>이라는 의미의 플래그로 다시 바꿔준다. 성공 시에는 재시도 가능 트랜잭션이 플래그를 바꾸고, 실패 시에는 보상 트랜잭션이 플래그를 바꾼다.</li>
</ol>
<br>

<h2 id="재시도-가능-트랜잭션의-멱등성">재시도 가능 트랜잭션의 멱등성</h2>
<p><strong>멱등성(Idempotency)</strong>이란 동일한 연산을 여러 번 수행해도 결과가 달라지지 않는 성질을 말한다. 예를 들어, <code>a = a + 3</code>이라는 연산은 멱등하지 않지만, <code>a = 3</code>이라는 연산은 멱등하다. 재시도 가능 트랜잭션은 실패해도 계속 재시도하여 반드시 성공시켜야 하기 때문에 멱등성을 보장하도록 구현해야 한다.</p>
<p>멱등성을 보장하기 위한 방법에는 여러가지가 있는데, 로컬 트랜잭션에 UUID와 같은 고유한 멱등키를 부여하여 해당 멱등키로 이미 처리되었는지 확인하는 로직을 추가하여 멱등성을 보장할 수 있다.</p>
<br>

<p><strong><em>참고 자료</em></strong></p>
<ul>
<li><a href="https://medium.com/@joudwawad/microservices-pattern-distributed-transactions-saga-92b5e933cea1">https://medium.com/@joudwawad/microservices-pattern-distributed-transactions-saga-92b5e933cea1</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga">https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga</a></li>
<li><a href="https://microservices.io/patterns/data/saga.html">https://microservices.io/patterns/data/saga.html</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[분산 트랜잭션 관리 방법(1) : Two-phase Commit]]></title>
            <link>https://velog.io/@local-kim/%EB%B6%84%EC%82%B0-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EB%B2%951-Two-phase-Commit</link>
            <guid>https://velog.io/@local-kim/%EB%B6%84%EC%82%B0-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EB%B2%951-Two-phase-Commit</guid>
            <pubDate>Thu, 12 Sep 2024 14:52:49 GMT</pubDate>
            <description><![CDATA[<p>Microservice Architecture에서는 각 마이크로 서비스가 독립적인 데이터베이스를 가진다. 트랜잭션이 발생하면 마이크로 서비스들이 서로 통신하며 여러 데이터베이스에 트랜잭션이 발생하는데, 이렇게 분산 환경에서 여러 시스템에 걸쳐있는 트랜잭션을 <strong>분산 트랜잭션</strong>이라고 한다. 분산 트랜잭션 또한 일반적인 트랜잭션처럼 <strong>ACID</strong> 속성을 가져야한다. 분산 트랜잭션을 관리하는 방법에 대해 알아보자.</p>
<h1 id="two-phase-commit2pc">Two-phase Commit(2PC)</h1>
<p>Two-phase Commit, 줄여서 2PC는 분산 트랜잭션을 관리하기 위한 알고리즘으로, 분산 트랜잭션이 모두 commit되거나 모두 rollback되도록 하여 Atomicity(원자성)를 보장한다.</p>
<p>분산 트랜잭션에 참여하는 데이터베이스 노드를 <strong>참여자(Participant)</strong>라고 부르고, 분산 트랜잭션을 제어할 <strong>코디네이터(Coordinator)</strong>가 존재한다.</p>
<p>2PC는 두 단계로 이루어진다.</p>
<p><img src="https://velog.velcdn.com/images/local-kim/post/9977200e-aaee-477d-947e-a3d816d64cff/image.png" alt=""></p>
<h2 id="prepare-단계">Prepare 단계</h2>
<ol>
<li>트랜잭션이 시작되면 코디네이터는 모든 참여자에게 <code>prepare</code> 메세지를 보낸다.</li>
<li>참여자는 <code>prepare</code> 메세지를 받으면 트랜잭션을 실행하고 코디네이터에게 성공하면 <code>yes</code>, 실패하면 <code>no</code> 메세지를 보낸다.</li>
</ol>
<h2 id="commit-단계">Commit 단계</h2>
<h3 id="성공">성공</h3>
<ol>
<li>코디네이터가 모든 참여자에게 <code>yes</code> 응답을 받으면 모든 참여자에게 <code>commit</code> 메세지를 보낸다.</li>
<li>참여자는 <code>commit</code> 메세지를 받으면 트랜잭션을 커밋하고 코디네이터에게 ack를 보낸다.</li>
</ol>
<h3 id="실패">실패</h3>
<ol>
<li>코디네이터가 한 참여자라도 <code>no</code> 메세지를 받으면 모든 참여자에게 <code>rollback</code> 메세지를 보낸다.</li>
<li>참여자는 <code>rollback</code> 메세지를 받으면 트랜잭션을 롤백하고 코디네이터에게 ack를 보낸다.</li>
</ol>
<br>

<h2 id="어떻게-acid를-보장하는가">어떻게 ACID를 보장하는가?</h2>
<ul>
<li>Atomicity : 코디네이터가 참여자를 조율하여 모든 참여자가 commit 또는 rollback 하도록 만든다.</li>
<li>Consistency : 참여자의 데이터베이스에서 제약조건 등에 의해 일관성이 보장된다.</li>
<li>Isolation : 참여자의 데이터베이스 격리 수준과 동시성 제어 기법에 의해 격리성이 보장된다.</li>
<li>Durability : 참여자의 데이터베이스에서 Redo log와 Undo log를 기록하여 영속성을 보장한다.</li>
</ul>
<blockquote>
<p><strong>Redo log와 Undo log</strong></p>
<p>데이터베이스에서 트랜잭션 관리와 복구를 위해 사용되는 로그. 트랜잭션이 커밋되면 로그는 디스크의 로그 파일에 영구적으로 저장되어 데이터베이스에 장애가 발생하더라도 Durability를 보장할 수 있음.</p>
<ul>
<li>Redo log : 트랜잭션에서 데이터를 변경할 때, 데이터의 변경사항을 기록하는 로그. 데이터베이스에 장애가 발생한 경우에 커밋된 트랜잭션을 복구하는데 사용됨</li>
<li>Undo log : 트랜잭션에서 데이터를 변경하기 전, 데이터의 원래 상태를 기록하는 로그. 트랜잭션을 롤백할 때 사용됨.</li>
</ul>
</blockquote>
<blockquote>
<p><strong>트랜잭션이 Commit 되는 과정</strong></p>
<ol>
<li>트랜잭션 중 데이터 변경 : 트랜잭션 실행 중 데이터가 변경되면, 변경된 데이터는 메모리의 Buffer Cache에 반영되고, Redo log는 메모리의 Redo log Buffer에 반영된다.</li>
<li>커밋 : 트랜잭션이 커밋되면 Redo log Buffer에 있던 Redo log는 디스크에 기록된다. (다른 조건을 충족하면 트랜잭션 중간에도 기록될 수 있다. 이 시점을 Checkpoint라 한다.)</li>
<li>변경된 데이터 기록 : Buffer Cache에 존재하는 변경된 데이터는 커밋 시점이 아니라 나중에 디스크에 기록된다.</li>
<li>Redo log로 트랜잭션 복구 : Buffer Cache에 존재하는 변경된 데이터가 디스크에 반영되지 못하고 데이터베이스에 장애가 생기면, Redo log를 이용하여 트랜잭션을 복구한다.</li>
</ol>
<p><strong>※</strong> 트랜잭션이 커밋되기 전에 장애가 생겨 Buffer Cache와 Redo log Buffer가 모두 사라질 경우? 커밋되지 않은 트랜잭션은 복구하지 않는다. 커밋된 트랜잭션에 한해서만 ACID를 보장한다.</p>
</blockquote>
<blockquote>
<p><strong>트랜잭션이 Rollback 되는 과정</strong></p>
<ol>
<li>트랜잭션 중 데이터 변경 : 트랜잭션 실행 중 데이터가 변경되면, 변경되기 전의 데이터는 Undo log에 기록되어 메모리의 Undo log Buffer에 반영되고, 변경된 데이터는 메모리의 Buffer Cache에 반영된다.</li>
<li>디스크에 Undo log 기록 : Checkpoint에 Undo log Buffer에 있던 Undo log는 디스크에 기록된다.</li>
<li>롤백 : 트랜잭션이 롤백되면 Undo log Buffer에 있던 Undo log를 사용하여 Buffer Cache에서 변경된 데이터를 원래 상태로 되돌린다. 또한 디스크에 이미 반영된 변경된 데이터도 Undo log를 사용하여 되돌린다.</li>
</ol>
</blockquote>
<br>

<h2 id="xa-프로토콜">XA 프로토콜</h2>
<p>XA 프로토콜은 분산 트랜잭션의 처리를 위해 X/Open에서 정의한 표준이다. XA 프로토콜은 Two-phase Commit 알고리즘을 이용하여 분산 트랜잭션을 처리한다. </p>
<br>

<h2 id="단점">단점</h2>
<ul>
<li>참여자는 코디네이터에 의존성을 가지고 있기 때문에 코디네이터에 장애가 발생할 경우, 코디네이터가 복구될 때까지 참여자는 prepare 상태에서 계속 대기해야해서 심각한 성능 저하를 일으킨다. (코디네이터는 단일 장애 지점)</li>
<li>참여자에 장애가 발생한 경우에도 코디네이터가 장애가 발생한 참여자의 응답을 계속 기다리며 다른 참여자들이 대기해야해서 성능 저하를 일으킨다.</li>
<li>MySQL에서 2PC가 단일 노드의 트랜잭션보다 최대 10배 느리다고 보고되었다.</li>
<li>XA 프로토콜을 지원하는 DBMS 간에는 2PC를 이용한 분산 트랜잭션 구현이 가능하지만, XA 프로토콜을 지원하지 않는 NoSQL 같은 DBMS에서는 사용이 불가능하다.</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>