<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>짱정연의 짱개발자 도전기</title>
    <link>https://leeeeeyeon-dev.tistory.com/</link>
    <description>https://github.com/leeeeeyeon</description>
    <language>ko</language>
    <pubDate>Tue, 21 Jul 2026 12:55:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>짱정연</managingEditor>
    <image>
      <title>짱정연의 짱개발자 도전기</title>
      <url>https://tistory1.daumcdn.net/tistory/6399910/attach/69935f93e99b4a9bb2acbdefe66a265c</url>
      <link>https://leeeeeyeon-dev.tistory.com</link>
    </image>
    <item>
      <title>코테를 위한 Java 문법 정리</title>
      <link>https://leeeeeyeon-dev.tistory.com/133</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bChzWX/btsKhQfmaeC/XqKmwKUZZ0S5IFQnkbKylk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bChzWX/btsKhQfmaeC/XqKmwKUZZ0S5IFQnkbKylk/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bChzWX/btsKhQfmaeC/XqKmwKUZZ0S5IFQnkbKylk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbChzWX%2FbtsKhQfmaeC%2FXqKmwKUZZ0S5IFQnkbKylk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;181&quot; height=&quot;181&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;평소 C++을 코테 주 언어로 사용하는데, 백엔드 직무의 경우 Java로 코테를 응시해야 하는 경우가 있어 &lt;s&gt;벼락치기용으로&lt;/s&gt; Java 문법을 정리하고자 한다. (틈틈이 업데이트 중)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입출력&lt;/h2&gt;
&lt;pre id=&quot;code_1729780334702&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        int k = Integer.parseInt(st.nextToken());
        
      	String str = br.readLine();

        System.out.println(n);
        System.out.println(m);
        System.out.println(k);
        System.out.println(str);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BufferedReader가 Scanner보다 빠르다.&lt;/li&gt;
&lt;li&gt;정수 타입 변수인 n, m, k를 저장할 때 str.nextToken()의 결과는 String 타입이고, 숫자 타입으로 저장하기 위해 parseInt 과정이 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;length, length(), size()&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;length: 배열의 길이&lt;/li&gt;
&lt;li&gt;length(): 문자열(String)의 길이&lt;/li&gt;
&lt;li&gt;size(): 컬렉션의 요소의 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1729782220769&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] numbers = {1, 2, 3, 4, 5};
System.out.println(numbers.length);

String text = &quot;Hello, World!&quot;;
System.out.println(text.length());

ArrayList&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
list.add(&quot;A&quot;);
list.add(&quot;B&quot;);
System.out.println(list.size());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; String&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자열 뒤집기&lt;/h3&gt;
&lt;pre id=&quot;code_1729782723942&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String str = &quot;abcde&quot;;
StringBuffer sb = new StringBuffer(str);
str = sb.reverse().toString();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자열 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;==의 경우 참조 주소를 비교하므로, 값을 비교할 때는 equals()를 사용하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2차원 ArrayList를 배열로 변환&lt;/h2&gt;
&lt;pre id=&quot;code_1729820822639&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[][] answer = new int[arr1.length][arr1[0].length];

for (int i = 0; i &amp;lt; result.size(); i++) {
    for (int j = 0; j &amp;lt; result.get(0).size(); j++) {
        answer[i][j] = result.get(i).get(j);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ArrayList 정렬&lt;/h2&gt;
&lt;pre id=&quot;code_1729781499458&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 오름차순
Collections.sort(list);

// 내림차순
Collections.sort(list, Collections.reverseOrder());

// 커스텀 정렬 with 람다식
list.sort((Integer a, Integer b) -&amp;gt; {
	return a - b;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pair&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에는 C++의 pair나 tuple 같은 자료형을 제공하지 않으므로 클래스로 직접 구현해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730115913930&quot; class=&quot;angelscript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;static class Pair {
	int x;
    int y;
    
    Pair(int x, int y) {
    	this.x = x;
        this.y = y;
    }
}

Queue&amp;lt;Pair&amp;gt; q = new LinkedList&amp;lt;&amp;gt;();
q.offer(new Pair(1, 2));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;큐와 우선순위 큐&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;offer(): 원소 추가, 값 추가 실패 시 false 반환&lt;/li&gt;
&lt;li&gt;poll(): 첫 번째 값을 반환하고 제거, 비어있으면 null&lt;/li&gt;
&lt;li&gt;peek(): 값을 꺼내지 않고 확인만&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;add, remove는 실패 시 에러가 발생하므로 offer, poll을 사용하자. 큐와 우선순위 큐 둘 다 함수는 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐를 구현할 때 구현체로 LinkedList와 ArrayDeque를 사용할 수 있지만, 메모리와 속도 측면에서 ArrayDeque가 더 많은 장점을 가지고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1729825430370&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 큐 선언
Queue&amp;lt;Integer&amp;gt; q = new ArrayDeque&amp;lt;&amp;gt;();

// 우선순위 큐 선언 1 (작은 숫자 먼저)
PriorityQueue&amp;lt;Integer&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;();

// 우선순위 큐 선언 2 (큰 숫자 먼저)
PriorityQueue&amp;lt;Integer&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;(Collections.reverseOrder());

q.offer(1);
q.offer(6);
q.offer(2);

while(!q.isEmpty()) {
	System.out.println(q.poll());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HasmMap 순회&lt;/h2&gt;
&lt;pre id=&quot;code_1730453866246&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;&amp;gt;();

for (String key : map.keySet()) {
	map.get(key);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리스트 회전&lt;/h2&gt;
&lt;pre id=&quot;code_1730467900588&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; arr = new ArrayList&amp;lt;&amp;gt;();
for (int i = 0; i &amp;lt; 5; i++) {
	arr.add(i);
}

System.out.println(arr); // [0, 1, 2, 3, 4]
Collections.rotate(arr, 1);
System.out.println(arr); // [4, 0, 1, 2, 3]&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Coding Test</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/133</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/133#entry133comment</comments>
      <pubDate>Thu, 24 Oct 2024 23:33:28 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 트라이(Trie)</title>
      <link>https://leeeeeyeon-dev.tistory.com/132</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 트라이(Trie)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트라이(Trie)는 어떤 문자열이 특정 문자열에 해당되는지 알아낼 때 효율적으로 사용할 수 있는 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트라이는 문자열 집합 내에서 중복되는 접두사들에 대응되는 노드들이 서로 연결된 트리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnRgPr/btsKeHVYgn9/wGa3K4U28xxrzN6JsScZA0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnRgPr/btsKeHVYgn9/wGa3K4U28xxrzN6JsScZA0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnRgPr/btsKeHVYgn9/wGa3K4U28xxrzN6JsScZA0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnRgPr%2FbtsKeHVYgn9%2FwGa3K4U28xxrzN6JsScZA0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;238&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트는 빈 문자열로 시작하여, 리프 노드는 문자열이 된다. 만약 위와 같은 트라이에서 'ten'이라는 단어를 찾을 때, 루트 노드에서 시작하여 t를 찾는다. 그 후 자식 노드들 중 e를 찾고, 또 n을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리프 노드까지 도달했다면, 해당 문자열이 우리가 찾던 문자열이 되고 탐색이 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트라이는 모든 문자열을 O(log n)으로 탐색이 가능하여 문자열 탐색이 굉장히 빠르다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 알파벳은 총 26개이므로 문자열을 포함하는 각 노드는 26개의 자식 노드 포인터를 가지게 되어 메모리를 많이 사용한다. 그러므로, 공간복잡도를 고려하여 문제에서 사용해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. C++로 트라이 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리 형태를 나타내기 위해서 &lt;b&gt;구조체&lt;/b&gt;로 각 노드를 표현해야 한다. 그리고 트라이에 문자열을 삽입하는 &lt;b&gt;insert 함수&lt;/b&gt;와, 트라이에서 문자열을 찾는 &lt;b&gt;find 함수&lt;/b&gt;가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드 구조체
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;26개의 자식 노드 포인터와 finish 여부(리프 노드에 도달했는지)를 변수로 가짐&lt;/li&gt;
&lt;li&gt;자식 노드 포인터는 O(1) 탐색을 위해 정적 배열이나 해시맵으로 관리하면 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;insert
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;루트부터 단어의 글자를 차례대로 탐색&lt;/li&gt;
&lt;li&gt;현재 노드 자식 중 해당 자식이 있으면 이동, 없다면 새로운 자식을 생성&lt;/li&gt;
&lt;li&gt;단어 삽입 후 마지막 노드에 입력된 단어 정보를 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;find
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;루트에서 시작하여 차례대로 탐색&lt;/li&gt;
&lt;li&gt;현재 노드 자식 중 해당 자식이 있다면 자식으로 이동, 없으면 탐색 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729566698262&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;unordered_map&amp;gt;

using namespace std;

struct TrieNode {
    // 해시맵으로 자식 노드 포인터를 구현
    unordered_map&amp;lt;char, TrieNode*&amp;gt; children;
    // 리프 노드에 도달했는지
    bool finish;

    // 생성자
    TrieNode() : children(), finish(false) {}

    void insert(string&amp;amp; key, int idx) {
        // 문자열의 마지막 글자에 도달
        if (idx == key.length() - 1) finish = true;
        else {
            // 현재 탐색 중인 글자
            char next = key[idx];
            // 자식 노드에 없다면 새로운 자식 생성
            if (children[next] == nullptr) children[next] = new TrieNode;

            // 다음 글자 탐색
            children[next]-&amp;gt;insert(key, idx+1);
        }
    }

    // 접두사로 검색되면 true 리턴
    bool findPrefix(string&amp;amp; key, int depth) {
        // 문자열의 마지막 글자에 도달
        if (depth == key.length()-1) return true;

        // 현재 탐색 중인 글자
        char next = key[depth];
        // 자식 노드에 없다면 탐색 실패
        if (children[next] == nullptr) return false;

        // 다음 글자 재귀 탐색
        return children[next]-&amp;gt;findPrefix(key, depth+1);
    }

    // 완전히 일치하는 단어 단위로 찾아 true 리턴
    bool findString(string&amp;amp; key, int depth) {
        // 문자열의 마지막 글자에 도달 + 리프 노드에 도달 = 완전히 일치하는 단어
        if (depth == key.length()-1 &amp;amp;&amp;amp; finish) return true;
        // 문자열의 마지막 글자에 도달 + 리프 노드가 아님 = 접두사
        if (depth == key.length()-1 &amp;amp;&amp;amp; !finish) return false;

        char next = key[depth];
        // 현재 글자의 자식 노드가 없어 탐색 실패
        if (children[next] == nullptr) return false;

        // 다음 글자 재귀 탐색
        return children[next]-&amp;gt;findString(key, depth+1);
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    vector&amp;lt;string&amp;gt; words = { &quot;what&quot;, &quot;when&quot;, &quot;yours&quot;, &quot;apple&quot;, &quot;her&quot;, &quot;his&quot;, &quot;you&quot; };

    TrieNode root;
    for (string word : words) {
        root.insert(word, 0);
    }

    string target = &quot;wh&quot;;

    if (root.findPrefix(target, 0)) {
        cout &amp;lt;&amp;lt; target &amp;lt;&amp;lt; &quot;가 검색 결과에 있습니다.\n&quot;;
    } else {
        cout &amp;lt;&amp;lt; target &amp;lt;&amp;lt; &quot;가 검색 결과에 없습니다.\n&quot;;
    }

    if (root.findString(target, 0)) {
        cout &amp;lt;&amp;lt; target &amp;lt;&amp;lt; &quot;가 검색 결과에 있습니다.\n&quot;;
    } else {
        cout &amp;lt;&amp;lt; target &amp;lt;&amp;lt; &quot;가 검색 결과에 없습니다.\n&quot;;
    }

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://namu.wiki/w/%ED%8A%B8%EB%9D%BC%EC%9D%B4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://namu.wiki/w/%ED%8A%B8%EB%9D%BC%EC%9D%B4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://8156217.tistory.com/51&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://8156217.tistory.com/51&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ansohxxn.github.io/algorithm/trie/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ansohxxn.github.io/algorithm/trie/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Coding Test/Algorithm &amp;amp; Data Structure</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/132</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/132#entry132comment</comments>
      <pubDate>Tue, 22 Oct 2024 12:11:53 +0900</pubDate>
    </item>
    <item>
      <title>공유 락(Shared Lock)과 배타 락(Exclusive Lock), 그리고 교착 상태(Deadlock)</title>
      <link>https://leeeeeyeon-dev.tistory.com/131</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며&lt;/h2&gt;
&lt;figure id=&quot;og_1729217556054&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;트랜잭션 격리 수준과 격리 수준에 따른 문제점&quot; data-og-description=&quot;1. 트랜잭션 격리성트랜잭션은 데이터베이스에서 하나의 논리적 기능을 수행하기 위한 작업의 단위를 말한다.트랜잭션에는 ACID라는 네 가지 특징이 있다. 그 중 격리성(Isolation)은 트랜잭션 수행&quot; data-og-host=&quot;leeeeeyeon-dev.tistory.com&quot; data-og-source-url=&quot;https://leeeeeyeon-dev.tistory.com/121&quot; data-og-url=&quot;https://leeeeeyeon-dev.tistory.com/121&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zclr2/hyXhT32HI8/aipYODFVAZqhegaoNBTt70/img.png?width=800&amp;amp;height=459&amp;amp;face=0_0_800_459,https://scrap.kakaocdn.net/dn/POCek/hyXhUaNLUC/KKYTLQFSMqp3QRM96H3wSk/img.png?width=800&amp;amp;height=459&amp;amp;face=0_0_800_459,https://scrap.kakaocdn.net/dn/dkBB3i/hyXlHgyaMO/4Ak3ehxvlI2c8LQUqICWp1/img.png?width=1976&amp;amp;height=1162&amp;amp;face=0_0_1976_1162&quot;&gt;&lt;a href=&quot;https://leeeeeyeon-dev.tistory.com/121&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leeeeeyeon-dev.tistory.com/121&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zclr2/hyXhT32HI8/aipYODFVAZqhegaoNBTt70/img.png?width=800&amp;amp;height=459&amp;amp;face=0_0_800_459,https://scrap.kakaocdn.net/dn/POCek/hyXhUaNLUC/KKYTLQFSMqp3QRM96H3wSk/img.png?width=800&amp;amp;height=459&amp;amp;face=0_0_800_459,https://scrap.kakaocdn.net/dn/dkBB3i/hyXlHgyaMO/4Ak3ehxvlI2c8LQUqICWp1/img.png?width=1976&amp;amp;height=1162&amp;amp;face=0_0_1976_1162');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션 격리 수준과 격리 수준에 따른 문제점&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 트랜잭션 격리성트랜잭션은 데이터베이스에서 하나의 논리적 기능을 수행하기 위한 작업의 단위를 말한다.트랜잭션에는 ACID라는 네 가지 특징이 있다. 그 중 격리성(Isolation)은 트랜잭션 수행&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leeeeeyeon-dev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 트랜잭션 격리 수준과 그에 따른 문제점에 대해 살펴보며, 트랜잭션이 동시에 수행될 때 발생할 수 있는 문제점에 대해 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션들이 동시에 수행될 때, 일관성을 해치는 것을 방지하기 위해 데이터 접근을 제어하는 &lt;b&gt;동시성 제어가 필요&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 제어를 위해 데이터에 잠금을 거는 &lt;b&gt;락 기법&lt;/b&gt;을 사용할 수 있다. 데이터베이스에 따라 다양한 락이 존재하지만 크게 공유 락과 배타 락으로 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 공유 락(Shared Lock, S-Lock)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 락(Read Lock)으로도 불린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 락이 걸린 데이터에 대해서는 읽기 연산만 실행 가능하며, 쓰기 연산을 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 공유 락이 걸린 데이터에 대해서는 다른 트랜잭션도 공유 락을 획득할 수 있다. 즉, 두 트랜잭션이 해당 데이터를 동시에 읽는 것이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 공유 락이 걸린 데이터에 배타 락을 거는 것을 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 배타 락(Exclusive Lock, X-Lock)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기 락(Write Lock)으로도 불린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배타 락을 획득한 트랜잭션은 해당 데이터에 대해 읽기 연산과 쓰기 연산 모두 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 다른 트랜잭션은 배타 락이 걸린 데이터에 대해 읽기 연산이나 쓰기 연산을 수행할 수 없다. 즉, 배타 락을 실행한 트랜잭션만 해당 데이터에 대한 독점권을 가지는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 락과 배타 락의 양립 가능성을 표로 정리하면 아래와 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;S-Lock&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;X-Lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;S-Lock&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;⭕️&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot;&gt;❌&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;X-Lock&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot;&gt;❌&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot;&gt;❌&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 블로킹(Blocking)과 교착 상태(Deadlock)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 트랜잭션에 의해 락이 걸려서 데이터에 접근하지 못할 때, 즉 두 트랜잭션의 락이 양립 불가능할 때 해당 트랜잭션은 작업을 진행하지 못하고 대기 상태가 되며, 이를 &lt;b&gt;블로킹(Blocking)&lt;/b&gt; 상태라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 두 개의 트랜잭션이 각각의 트랜잭션이 가지고 있는 데이터의 Lock 획득을 시도하면, 둘 다 블로킹 상태가 되어 무한 대기하게 되는 문제가 발생하며, 이를 &lt;b&gt;교착 상태(Deadlock)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시를 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1768&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTi3J0/btsKbi9eRTu/ZLjxUDhq1kllevMlWkyiLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTi3J0/btsKbi9eRTu/ZLjxUDhq1kllevMlWkyiLK/img.png&quot; data-alt=&quot;교착 상태가 발생하는 예시 상황&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTi3J0/btsKbi9eRTu/ZLjxUDhq1kllevMlWkyiLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTi3J0%2FbtsKbi9eRTu%2FZLjxUDhq1kllevMlWkyiLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;349&quot; data-origin-width=&quot;1768&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;교착 상태가 발생하는 예시 상황&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;트랜잭션 1&lt;/b&gt;이 &lt;b&gt;리소스 1에 대한 배타 락&lt;/b&gt;을 획득한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트랜잭션 2&lt;/b&gt;가 &lt;b&gt;리소스 2에 대한 공유 락&lt;/b&gt;을 획득한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트랜잭션 1&lt;/b&gt;이 리소스 1을 점유하고 있는 상태에서, 리소스 2에 대한 배타 락 획득을 시도하지만 얻을 수 없어 &lt;b&gt;블로킹 상태에 진입&lt;/b&gt;한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트랜잭션 2&lt;/b&gt;가 리소스 2를 점유하고 있는 상태에서, 리소스 1에 대한 공유 락 획득을 시도하지만 얻을 수 없어 &lt;b&gt;블로킹 상태에 진입&lt;/b&gt;한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 교착 상태 해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교착 상태를 해결할 수 있는 방법은 예방 기법, 회피 기법(Wait-Die 방식, Wound-Wait 방식), 낙관적 병행 기법, 빈도 낮추기가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-1. 예방 기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 시작되기 전에 필요한 모든 데이터에 대한 락을 획득한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 데이터가 많이 필요한 경우, 모든 데이터를 잠궈야 하므로 트랜잭션의 병행성이 떨어진다. 또한, 몇몇 트랜잭션은 계속 작업을 처리하지 못하는 기아 상태가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-2. 회피 기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자원을 할당할 때 타임스탬프를 활용하여 교착 상태가 일어나지 않도록 회피한다. 회피 방법으로는 Wait-Die 방식과 Wound-Wait 방식이 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Wait-Die 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 트랜잭션이 데이터를 점유하고 있을 때 먼저 들어온 트랜잭션이라면 대기(wait)하고, 나중에 들어온 트랜잭션이라면 포기(die)하고 나중에 다시 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래된 프로세스에게 대기의 기회를 제공하고, 최신 프로세스는 자주 복귀하기 때문에 오버헤드의 가능성이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J0hVp/btsKaNPm54d/ZUdZ2kVT4k71NMwABaG26k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J0hVp/btsKaNPm54d/ZUdZ2kVT4k71NMwABaG26k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J0hVp/btsKaNPm54d/ZUdZ2kVT4k71NMwABaG26k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ0hVp%2FbtsKaNPm54d%2FZUdZ2kVT4k71NMwABaG26k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;153&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Wound-Wait 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 트랜잭션이 데이터를 점유하고 있을 때 먼저 들어온 트랜잭션이 접근하면 선점(wound)하고, 나중에 들어온 트랜잭션이라면 대기(wait)한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAuiRH/btsKbzpq3qt/qnS1nHvrrSzB5JwotSW95K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAuiRH/btsKbzpq3qt/qnS1nHvrrSzB5JwotSW95K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAuiRH/btsKbzpq3qt/qnS1nHvrrSzB5JwotSW95K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAuiRH%2FbtsKbzpq3qt%2FqnS1nHvrrSzB5JwotSW95K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;473&quot; height=&quot;150&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-3. 낙관적 병행 기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 실행되는 동안에는 검사를 하지 않고, 트랜잭션이 다 실행된 이후에 검사하여 문제가 있다면 되돌린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-4. 빈도 낮추기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교착상태의 빈도를 낮추기 위해 아래와 같은 방법을 시도한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션을 자주 커밋&lt;/li&gt;
&lt;li&gt;트랜잭션들이 동일한 테이블 순으로 접근&lt;/li&gt;
&lt;li&gt;읽기 잠금의 사용을 피한다. (SELECT ~ FOR UPDATE)&lt;/li&gt;
&lt;li&gt;테이블 단위의 락을 획득해 갱신을 직렬화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테으블의 복수행을 복수의 연결에서 순서 없이 갱신하면 교착 상태가 자주 발생한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/ZXV6ZqMyJLg&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/ZXV6ZqMyJLg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hudi.blog/mysql-8.0-shared-lock-and-exclusive-lock/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hudi.blog/mysql-8.0-shared-lock-and-exclusive-lock/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://parkmuhyeun.github.io/etc/database/2022-07-03-Deadlock/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://parkmuhyeun.github.io/etc/database/2022-07-03-Deadlock/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jaehoney.tistory.com/162&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jaehoney.tistory.com/162&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Database</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/131</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/131#entry131comment</comments>
      <pubDate>Fri, 18 Oct 2024 12:19:37 +0900</pubDate>
    </item>
    <item>
      <title>[Packy] Github Actions과 Flyway를 사용한 엔티티-테이블 매핑 검증 자동화</title>
      <link>https://leeeeeyeon-dev.tistory.com/129</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l2gAz/btsJnrfglNx/JPBtkiUyj2RcrdcOpxi8e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l2gAz/btsJnrfglNx/JPBtkiUyj2RcrdcOpxi8e0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l2gAz/btsJnrfglNx/JPBtkiUyj2RcrdcOpxi8e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl2gAz%2FbtsJnrfglNx%2FJPBtkiUyj2RcrdcOpxi8e0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;319&quot; height=&quot;182&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며&lt;/h2&gt;
&lt;figure id=&quot;og_1725174501524&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring boot 프로젝트에 Flyway 도입하기 (feat. 기존 데이터가 있을 경우)&quot; data-og-description=&quot;Flyway란? Flyway는 데이터베이스의 형상 관리를 목적으로 하는 오픈소스 마이그레이션 툴이다. Flyway를 통해 변경 사항을 추적하고, 업데이트나 롤백을 보다 쉽게 할 수 있다. 우리가 개발하며 필수&quot; data-og-host=&quot;leeeeeyeon-dev.tistory.com&quot; data-og-source-url=&quot;https://leeeeeyeon-dev.tistory.com/12&quot; data-og-url=&quot;https://leeeeeyeon-dev.tistory.com/12&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/I3Al0/hyWV12krMj/CvKg5s76mgiJQln7LEFmmK/img.png?width=800&amp;amp;height=330&amp;amp;face=0_0_800_330,https://scrap.kakaocdn.net/dn/gwXVf/hyWVZQXNrQ/mg5fhdyTVkvmiBujCaJfI1/img.png?width=800&amp;amp;height=330&amp;amp;face=0_0_800_330,https://scrap.kakaocdn.net/dn/bj2rwz/hyWZfkizJT/53oH9my44SQLRQMqENAy41/img.png?width=896&amp;amp;height=578&amp;amp;face=0_0_896_578&quot;&gt;&lt;a href=&quot;https://leeeeeyeon-dev.tistory.com/12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leeeeeyeon-dev.tistory.com/12&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/I3Al0/hyWV12krMj/CvKg5s76mgiJQln7LEFmmK/img.png?width=800&amp;amp;height=330&amp;amp;face=0_0_800_330,https://scrap.kakaocdn.net/dn/gwXVf/hyWVZQXNrQ/mg5fhdyTVkvmiBujCaJfI1/img.png?width=800&amp;amp;height=330&amp;amp;face=0_0_800_330,https://scrap.kakaocdn.net/dn/bj2rwz/hyWZfkizJT/53oH9my44SQLRQMqENAy41/img.png?width=896&amp;amp;height=578&amp;amp;face=0_0_896_578');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring boot 프로젝트에 Flyway 도입하기 (feat. 기존 데이터가 있을 경우)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flyway란? Flyway는 데이터베이스의 형상 관리를 목적으로 하는 오픈소스 마이그레이션 툴이다. Flyway를 통해 변경 사항을 추적하고, 업데이트나 롤백을 보다 쉽게 할 수 있다. 우리가 개발하며 필수&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leeeeeyeon-dev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 운영하고 있는 패키 프로젝트에서는 Flyway를 사용하여 DDL 생성을 자동화하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flyway를 사용하면 엔티티와 테이블 매핑이 일치하지 않을 때 애플리케이션 실행에 실패한다. 그렇기 때문에 테이블 컬럼을 수정하지 않아 나중에 서비스를 운영하다가 비즈니스 로직이 실패하는 문제 상황을 예방할 수 있다.&lt;/p&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jgmwx/btsJmf1jMc3/Fzjw9Z4DI5DXpnxqWPvQS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jgmwx/btsJmf1jMc3/Fzjw9Z4DI5DXpnxqWPvQS1/img.png&quot; data-alt=&quot;애플리케이션 실행 실패 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jgmwx/btsJmf1jMc3/Fzjw9Z4DI5DXpnxqWPvQS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJgmwx%2FbtsJmf1jMc3%2FFzjw9Z4DI5DXpnxqWPvQS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;245&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;애플리케이션 실행 실패 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 해당 기능을 활용하여 개발/운영 환경을 배포하기 전에 로컬에서 profile을 dev와 prod로 변경한 뒤 애플리케이션을 실행해보며 배포 실패를 방지하고 있다. 최근에는 해당 단계를 깜빡하여 개발 환경 배포 시 애플리케이션 실행에 실패한 경험이 있다 ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 Github Actions에서 애플리케이션 실행을 대신하여 이러한 반쪽짜리 자동화를 발전시켜보자 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. FlywayApplication&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 패키는 멀티 모듈 프로젝트로 구성되어 있으며, 실제 SpringApplication이 실행되는 모듈은 api 모듈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 flyway 관련 설정을 domain 모듈에서 진행했는데 해당 모듈은 api 모듈과 의존성을 가지고 있어 api 모듈과 독립된 flyway 모듈을 만들어 해당 모듈로 flyway 관련 설정을 옮겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725174920897&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EntityScan(basePackages = {&quot;com.dilly.*&quot;})
@SpringBootApplication
public class FlywayApplication {

    public static void main(String[] args) {
        System.setProperty(&quot;spring.config.name&quot;,
            &quot;application-domain, application-flyway&quot;);

        ConfigurableApplicationContext ctx = SpringApplication.run(FlywayApplication.class, args);
        ctx.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Actions에서 엔티티-테이블 매핑 검증 자동화를 위해 사용할 애플리케이션이다. 해당 애플리케이션은 검증에만 사용되고 계속 실행될 필요가 없으므로 close 메서드로 애플리케이션을 종료시켜주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Github Actions 스크립트 수정&lt;/h2&gt;
&lt;pre id=&quot;code_1725175198828&quot; class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: packy API Server - CI

on:
  pull_request:
    branches:
      - main
      - develop

permissions:
  contents: read

jobs:
  CI:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.ACTION_TOKEN }}
          submodules: true

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4

      - name: Run build Flyway Module
        run: ./gradlew clean packy-flyway:build

      - name: Run FlywayApplication
        run: java -Dspring.profiles.active=${{ env.PROFILE }} -jar ./packy-flyway/build/libs/packy-flyway-0.0.1-SNAPSHOT.jar
        env:
          PROFILE: ${{ github.ref == 'refs/heads/main' &amp;amp;&amp;amp; 'prod' || 'dev' }}

      - name: Run build with Gradle Wrapper
        run: ./gradlew clean build --parallel

      - name: Report to CodeCov
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          file: 'packy-support/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 step 중 Run build Flyway Module, Run FlywayApplication이 매핑 검증 단계이고 나머지는 기존 CI 스크립트에 있던 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradle build를 할 경우 모든 모듈이 빌드되기 때문에 flyway 모듈만 빌드하도록 하였으며, dev 또는 prod로 profile을 설정하여 FlywayApplication을 실행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티와 테이블 매핑이 일치하지 않는 상황을 만들기 위해 Member 엔티티에 임의로 test라는 컬럼을 추가하고 워크플로우를 실행시켜보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1725175437774&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Entity
public class Member extends BaseTimeEntity {
	@Id
	@GeneratedValue(strategy = IDENTITY)
	private Long id;
    
    // 중략 ...

	String test;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2706&quot; data-origin-height=&quot;1276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bovnAS/btsJm8G24vK/rX7bf7IXY9mUBN1taKkWEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bovnAS/btsJm8G24vK/rX7bf7IXY9mUBN1taKkWEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bovnAS/btsJm8G24vK/rX7bf7IXY9mUBN1taKkWEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbovnAS%2FbtsJm8G24vK%2FrX7bf7IXY9mUBN1taKkWEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;811&quot; height=&quot;382&quot; data-origin-width=&quot;2706&quot; data-origin-height=&quot;1276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마 불일치로 인해 어플리케이션 실행에 실패하여 워크플로우가 실패한 모습을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 직접 SpringApplication을 실행해보는 작업을 하지 않아도 배포 이전에 엔티티-테이블 스키마가 일치하는지 확인할 수 있도록 자동화 단계를 추가하였다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 Flyway를 도입한 것도 자동화였지만, 일부 수동 작업이 필요한 자동화였다. 이번 경험을 통해 이미 자동화를 적용한 영역에서도 불편함을 찾아 자동화 단계를 보다 개선할 수 있음을 배웠고, 앞으로도 자동화를 거친 단계에 수동으롤 반복되는 작업이 더 없는가 다시 고민해보고 개선하는 자세로 프로젝트를 관리해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://mindybughunter.com/flyway%EC%99%80-github-actions%EC%9D%84-%ED%86%B5%ED%95%9C-entity-table-%EB%A7%A4%ED%95%91-%EA%B2%80%EC%A6%9D-%EC%9E%90%EB%8F%99%ED%99%94/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mindybughunter.com/flyway%EC%99%80-github-actions%EC%9D%84-%ED%86%B5%ED%95%9C-entity-table-%EB%A7%A4%ED%95%91-%EA%B2%80%EC%A6%9D-%EC%9E%90%EB%8F%99%ED%99%94/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Github</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/129</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/129#entry129comment</comments>
      <pubDate>Sun, 1 Sep 2024 16:32:39 +0900</pubDate>
    </item>
    <item>
      <title>[Packy] 선물박스 열기 동시성 문제 해결 2 - Redisson으로 분산락 구현</title>
      <link>https://leeeeeyeon-dev.tistory.com/128</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;729&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh3rhG/btsJg8gZXFm/DRQMp9wbWuQtdvbn4Zhv4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh3rhG/btsJg8gZXFm/DRQMp9wbWuQtdvbn4Zhv4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh3rhG/btsJg8gZXFm/DRQMp9wbWuQtdvbn4Zhv4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh3rhG%2FbtsJg8gZXFm%2FDRQMp9wbWuQtdvbn4Zhv4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;290&quot; height=&quot;165&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;729&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며&lt;/h2&gt;
&lt;figure id=&quot;og_1724819975018&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Packy] 선물박스 열기 동시성 문제 해결 1 - 분산 락을 선택한 이유&quot; data-og-description=&quot;1. 들어가며&amp;nbsp;패키에서는 선물박스를 받게 되면 Receiver 테이블에 받은 유저 ID와 선물박스 ID를 저장한다.지금은 하나의 선물박스를 1명만 받을 수 있지만(PRIVATE), 나중에 하나의 선물박스를 여러 &quot; data-og-host=&quot;leeeeeyeon-dev.tistory.com&quot; data-og-source-url=&quot;https://leeeeeyeon-dev.tistory.com/127&quot; data-og-url=&quot;https://leeeeeyeon-dev.tistory.com/127&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cTBasV/hyWSpbFlmp/W8IuZeHtrAk869I7wNbXw1/img.png?width=800&amp;amp;height=455&amp;amp;face=0_0_800_455,https://scrap.kakaocdn.net/dn/dQML9b/hyWV2ly0aA/oIUszzZlD42V30j0kSpAq1/img.png?width=800&amp;amp;height=455&amp;amp;face=0_0_800_455,https://scrap.kakaocdn.net/dn/cRyQFX/hyWSkBppPE/muuAxfk70pvcniZ2kPOVX0/img.png?width=2412&amp;amp;height=912&amp;amp;face=0_0_2412_912&quot;&gt;&lt;a href=&quot;https://leeeeeyeon-dev.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leeeeeyeon-dev.tistory.com/127&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cTBasV/hyWSpbFlmp/W8IuZeHtrAk869I7wNbXw1/img.png?width=800&amp;amp;height=455&amp;amp;face=0_0_800_455,https://scrap.kakaocdn.net/dn/dQML9b/hyWV2ly0aA/oIUszzZlD42V30j0kSpAq1/img.png?width=800&amp;amp;height=455&amp;amp;face=0_0_800_455,https://scrap.kakaocdn.net/dn/cRyQFX/hyWSkBppPE/muuAxfk70pvcniZ2kPOVX0/img.png?width=2412&amp;amp;height=912&amp;amp;face=0_0_2412_912');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Packy] 선물박스 열기 동시성 문제 해결 1 - 분산 락을 선택한 이유&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 들어가며&amp;nbsp;패키에서는 선물박스를 받게 되면 Receiver 테이블에 받은 유저 ID와 선물박스 ID를 저장한다.지금은 하나의 선물박스를 1명만 받을 수 있지만(PRIVATE), 나중에 하나의 선물박스를 여러&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leeeeeyeon-dev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 포스트에서 선물박스 열기 로직에 발생할 수 있는 동시성 문제에 대해 알아보고, 이를 해결할 수 있는 방법에 대해 찾아본 뒤 최종적으로 &lt;span style=&quot;color: #3a4954; text-align: start;&quot;&gt;Redis의 Message Broker 방식을 활용한 분산 락으로 해결하기로 결정하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 Redisson으로 분산 락을 구현하는 과정을 코드와 함께 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 의존성 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redisson 라이브러리를 build.gradle에 추가해준다&lt;/p&gt;
&lt;pre id=&quot;code_1724820113834&quot; class=&quot;gradle&quot; data-ke-language=&quot;gradle&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.redisson:redisson-spring-boot-starter:3.34.1'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 테크 블로그를 참고하여 구현하며 3.27.0 버전을 사용하였는데 아래와 같이 빈을 생성하지 못하는 에러가 발생했는데 공식 문서 상 최신 버전은 3.34.1로 업그레이드하여 해결하였다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Error creating bean with name 'redissonClient' defined in class path resource [com/dilly/global/config/RedissonConfig.class]: Failed to instantiate [org.redisson.api.RedissonClient]: Factory method 'redissonClient' threw exception with message: 'io.netty.resolver.dns.DnsNameResolverBuilder io.netty.resolver.dns.DnsNameResolverBuilder.socketChannelType(java.lang.Class, boolean)'&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. RedissonConfig&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RedissonClient를 사용하기 위한 설정을 빈으로 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1724820583303&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class RedissonConfig {

    @Value(&quot;${spring.data.redis.host}&quot;)
    private String host;

    @Value(&quot;${spring.data.redis.port}&quot;)
    private int port;

    private static final String REDISSION_HOST_PREFIX = &quot;redis://&quot;;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        String address = REDISSION_HOST_PREFIX + host + &quot;:&quot; + port;
        config.useSingleServer().setAddress(address);

        return Redisson.create(config);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. RedissonLock&lt;/h2&gt;
&lt;figure id=&quot;og_1724821130500&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;AOP와 Spring AOP에 대해 알아보자&quot; data-og-description=&quot;1. AOP란? AOP는 Aspect Oriented Programming의 약자로, 관점 지향 프로그래밍을 의미한다. 핵심적인 비즈니스 로직으로부터 횡단 관심사를 분리하는 것을 목적으로 한다. 로깅, 보안, 트랜잭션 처리와 같&quot; data-og-host=&quot;leeeeeyeon-dev.tistory.com&quot; data-og-source-url=&quot;https://leeeeeyeon-dev.tistory.com/49&quot; data-og-url=&quot;https://leeeeeyeon-dev.tistory.com/49&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b23Yn3/hyWVS4lzsn/tgjG4QwCzB3tb0zrhRCs70/img.png?width=574&amp;amp;height=330&amp;amp;face=0_0_574_330,https://scrap.kakaocdn.net/dn/bXs2xv/hyWSnZdOQN/RJrgAEyn6pDh60k6dnsvO0/img.png?width=574&amp;amp;height=330&amp;amp;face=0_0_574_330,https://scrap.kakaocdn.net/dn/DAhDX/hyWVVzYXuN/852UXPcYnddKCDEUxhECEk/img.png?width=1984&amp;amp;height=1178&amp;amp;face=0_0_1984_1178&quot;&gt;&lt;a href=&quot;https://leeeeeyeon-dev.tistory.com/49&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leeeeeyeon-dev.tistory.com/49&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b23Yn3/hyWVS4lzsn/tgjG4QwCzB3tb0zrhRCs70/img.png?width=574&amp;amp;height=330&amp;amp;face=0_0_574_330,https://scrap.kakaocdn.net/dn/bXs2xv/hyWSnZdOQN/RJrgAEyn6pDh60k6dnsvO0/img.png?width=574&amp;amp;height=330&amp;amp;face=0_0_574_330,https://scrap.kakaocdn.net/dn/DAhDX/hyWVVzYXuN/852UXPcYnddKCDEUxhECEk/img.png?width=1984&amp;amp;height=1178&amp;amp;face=0_0_1984_1178');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AOP와 Spring AOP에 대해 알아보자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. AOP란? AOP는 Aspect Oriented Programming의 약자로, 관점 지향 프로그래밍을 의미한다. 핵심적인 비즈니스 로직으로부터 횡단 관심사를 분리하는 것을 목적으로 한다. 로깅, 보안, 트랜잭션 처리와 같&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leeeeeyeon-dev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 락 코드를 Service 클래스에 작성할 경우 비즈니스 로직(선물박스 열기)과 관련이 없는 코드가 섞이게 되고, 추후 다른 로직에 분산 락을 적용할 경우 중복되는 코드가 생길 수 있어 &lt;b&gt;AOP를 활용하여 분산 락을 구현&lt;/b&gt;할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP에 대한 개념은 이전에 정리해둔 포스트가 있으니 위에 있는 포스트를 참고하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RedissonLock이라는 커스텀 어노테이션을 만들고, 해당 어노테이션이 있는 메서드가 실행되면 AOP를 수행하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1724820677653&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedissonLock {

    String value(); // 락의 이름
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS; // 시간 단위
    long waitTime() default 5_000L; // 락 획득을 위해 waitTime만큼 대기
    long leaseTime() default 5_000L; // 락 획득 후 leaseTime만큼 락 유지
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redisson Lock은 value, timeUnit, waitTime, leaseTime 네 가지 값이 있으며 각 값의 역할은 주석에 적은 것과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value 외의 값들은 값을 명시하지 않을 경우 기본값을 사용할 수 있도록 기본값을 지정해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;waitTime이 짧을 경우 선착순에 들었지만 대기 시간이 짧아 기회가 박탈될 수 있고, leaseTime이 짧을 경우 로직이 진행되다가 락이 해제되는 경우가 발생할 수 있으므로 적절하게 시간을 조절해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. RedissonLockAspect&lt;/h2&gt;
&lt;pre id=&quot;code_1724821262705&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RedissonLockAspect {

    @Value(&quot;${spring.profiles.active}&quot;)
    private String profilePrefix;

    private final RedissonClient redissonClient;
    private final AopForTransaction aopForTransaction;

    @Around(&quot;@annotation(com.dilly.global.aop.RedissonLock)&quot;)
    public Object redissonLock(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RedissonLock annotation = method.getAnnotation(RedissonLock.class);

        String lockKey =
            profilePrefix + &quot;:&quot; + method.getName() + CustomSpringELParser.getDynamicValue(
            signature.getParameterNames(), joinPoint.getArgs(), annotation.value());

        RLock lock = redissonClient.getLock(lockKey);

        try {
            boolean lockable = lock.tryLock(annotation.waitTime(), annotation.leaseTime(),
                annotation.timeUnit());
            if (!lockable) {
                log.info(&quot;Lock 획득 실패: {}&quot;, lockKey);
                throw new ConcurrencyFailedException();
            }

            log.info(&quot;Lock 획득 성공: {}&quot;, lockKey);
            return aopForTransaction.proceed(joinPoint);
        } catch (InterruptedException e) {
            log.info(&quot;에러 발생&quot;);
            throw e;
        } finally {
            if (lock != null &amp;amp;&amp;amp; lock.isHeldByCurrentThread()) {
                log.info(&quot;Lock 해제&quot;);
                lock.unlock();
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Redisson이라는 라이브러리에 분산 락 메커니즘과 재시도 로직이 구현되어 있기 때문에 해당 라이브러리를 사용하여 분산 락을 구현할 수 있다. 우리는 락 획득 시도, 락 획득 성공 후 처리, 락 획득 실패 로직을 구현하면 된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP를 활용하여 구현한 분산 락 로직이다. 락 획득 시도, 락 획득 성공 후 처리, 락 획득 실패 로직을 포함하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버비를 아끼기 위해 ㅠuㅠ ElastiCache를 하나만 사용하려고 하기 때문에 profile 값(dev, prod)를 lockKey 이름에 포함하여 구분하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CustomSpringELParser는 Spring 표현식을 좀 더 자세하게 파싱할 수 있도록 만든 파서로, 구체적인 코드는 아래를 확인하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redissonClient.getLock()으로 락을 획득하고, tryLock()으로 락 획득을 시도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락 획득에 성공했다면 선물박스 열기 로직을 수행하고, 락 획득에 실패했으면 ConcurrencyFailedException이라는 커스텀 비즈니스 예외를 던지도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP의 기본 proceed() 메소드를 사용하지 않고, ApoForTransaction 클래스의 proceed() 메소드를 사용했음을 주의하자. 관련하여 자세한 내용은 잠시 후 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 finally 구문에서는 만약 락을 획득한 상태라면 락을 해제하도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. CustomSpringELParser&lt;/h2&gt;
&lt;pre id=&quot;code_1724821316310&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CustomSpringELParser {

    public static Object getDynamicValue(String[] parameterNames, Object[] args, String key) {
        SpelExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();

        for (int i = 0; i &amp;lt; parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }

        return parser.parseExpression(key).getValue(context);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. AopForTransaction&lt;/h2&gt;
&lt;pre id=&quot;code_1724821343078&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class AopForTransaction {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Object proceed(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RedissonLock이 선언된 메소드는 트랜잭션 옵션을 REQUIRES_NEW로 지정하여 부모 트랜잭션의 유무와 관계없이 별도의 트랜잭션으로 동작하여 &lt;b&gt;트랜잭션이 커밋된 이후 락이 해제&lt;/b&gt;되도록 한다. 그 이유는 아래 그림으로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽은 락의 해제 시점이 커밋보다 먼저인 경우, 오른쪽은 락의 해제 시점이 커밋 이후인 경우이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vu0ao/btsJin55UaP/M7UfaXVk7ATL995mjlB6JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vu0ao/btsJin55UaP/M7UfaXVk7ATL995mjlB6JK/img.png&quot; data-alt=&quot;출처: https://helloworld.kurly.com/blog/distributed-redisson-lock/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vu0ao/btsJin55UaP/M7UfaXVk7ATL995mjlB6JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVu0ao%2FbtsJin55UaP%2FM7UfaXVk7ATL995mjlB6JK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2178&quot; height=&quot;1038&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://helloworld.kurly.com/blog/distributed-redisson-lock/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 시나리오에서는 Client 1과 Client 2가 동시에 재고를 차감했으므로 2개가 차감되어야 하는데 1개만 차감되어 데이터 정합성이 깨지는 문제가 발생하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 오른쪽 시나리오에서는 정상적으로 2개가 차감되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 기존 테스트 코드에 분산 락 적용되지 않도록 하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키 프로젝트의 테스트 코드는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트의 편리함을 위해 @Transactional 어노테이션을 사용&lt;/li&gt;
&lt;li&gt;비즈니스 계층(Service)와 영속성 계층(Repository)을 합쳐 통합 테스트를 진행하고 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 특징이 있다. 통합 테스트를 위해 선물박스 접근 권한이나 삭제 로직을 테스트할 때 openGiftBox 메소드(선물박스 열기 로직)로 미리 선물박스를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 openGiftBox 메소드를 사용하던 기존 테스트 코드들이 분산 락 도입 이후 실패하는 상황이 발생하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AopForTransaction에서 별도의 트랜잭션을 사용하면서 테스트 레이어의 트랜잭션과 openGiftBox 메소드의 트랜잭션 범위가 달라져 발생한 문제인 것 같다. 그래서 기존 테스트 코드에서는 분산 락을 적용하지 않도록 결정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 동시성이나 분산 락 관련 로직을 테스트할 일이 있으면 GiftBoxConcurrencyTest 클래스나 새로운 테스트 클래스에서 테스트를 진행하고, 통합 테스트에서는 단일 요청을 기준으로 테스트 코드를 작성하는 방식으로 테스트 코드를 운영하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1724895719544&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ConditionalOnExpression(&quot;${ableRedissonLock:true}&quot;)
public class RedissonLockAspect {
	// 중략 ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ConditionalOnExpression 어노테이션을 사용하여 ableRedissonLock라는 환경변수가 true일 때만 동작하도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 속성값이 없으면 true가 default 값이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 통합 테스트를 관리하는 추상 클래스인 IntegrationTestSupport 클래스에서는 @TestPropertySource 어노테이션을 사용하여 ableRedissonLock을 false로 지정해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1724895933078&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@TestPropertySource(properties = &quot;ableRedissonLock=false&quot;)
public abstract class IntegrationTestSupport {
	// 중략 ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 포스트에서 알아본 동시성 문제와 분산 락에 이어 이번 포스트에서는 직접 코드로 분산 락을 구현해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드, 락, 트랜잭션, AOP 등 다양한 CS와 Spring 이론이 필요했던 문제 해결 과정이었다. 특히 트랜잭션 관련 삽질을 많이 하면서 트랜잭션에 대한 공부가 많이 되었던 시간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 예시로, 테스트 코드를 작성하며 테스트 코드의 @Transactional과 Service의 @Transactional이 다르다는 충격적인 사실을 배울 수 있었다. 아래 글이 테스트 코드에서의 @Transactional 어노테이션 사용에 대해 정리가 잘 되어 있어 정말 도움이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1724896267434&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;@Transactional 이모저모 1 - 테스트코드와 @Transactional&quot; data-og-description=&quot;이 포스팅 보고 테스트 짰다면 얼마나 좋았을까?&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@backfox/%EA%B9%80%EC%B6%98%EB%B0%B0%EC%99%80-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Transactional-%EC%9D%B4%EB%AA%A8%EC%A0%80%EB%AA%A8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C#%EC%9B%90%EC%9D%B8&quot; data-og-url=&quot;https://velog.io/@backfox/김춘배와-알아보는-Transactional-이모저모-1-테스트코드&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cJIpAb/hyWVRYVndI/04eVgeheBcXA3zEeeB5E2K/img.png?width=1192&amp;amp;height=622&amp;amp;face=0_0_1192_622,https://scrap.kakaocdn.net/dn/Bdbdj/hyWVXdJLS6/kV3elNYneUWJ07K8Heoxkk/img.png?width=1192&amp;amp;height=622&amp;amp;face=0_0_1192_622,https://scrap.kakaocdn.net/dn/9K5Ws/hyWV6Plb30/Cg3NArg0S11JXhofsufBu0/img.png?width=1244&amp;amp;height=1824&amp;amp;face=869_433_934_1188&quot;&gt;&lt;a href=&quot;https://velog.io/@backfox/%EA%B9%80%EC%B6%98%EB%B0%B0%EC%99%80-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Transactional-%EC%9D%B4%EB%AA%A8%EC%A0%80%EB%AA%A8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C#%EC%9B%90%EC%9D%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@backfox/%EA%B9%80%EC%B6%98%EB%B0%B0%EC%99%80-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Transactional-%EC%9D%B4%EB%AA%A8%EC%A0%80%EB%AA%A8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C#%EC%9B%90%EC%9D%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cJIpAb/hyWVRYVndI/04eVgeheBcXA3zEeeB5E2K/img.png?width=1192&amp;amp;height=622&amp;amp;face=0_0_1192_622,https://scrap.kakaocdn.net/dn/Bdbdj/hyWVXdJLS6/kV3elNYneUWJ07K8Heoxkk/img.png?width=1192&amp;amp;height=622&amp;amp;face=0_0_1192_622,https://scrap.kakaocdn.net/dn/9K5Ws/hyWV6Plb30/Cg3NArg0S11JXhofsufBu0/img.png?width=1244&amp;amp;height=1824&amp;amp;face=869_433_934_1188');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;@Transactional 이모저모 1 - 테스트코드와 @Transactional&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 포스팅 보고 테스트 짰다면 얼마나 좋았을까?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://hudi.blog/distributed-lock-with-redis/&quot;&gt;https://hudi.blog/distributed-lock-with-redis/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://helloworld.kurly.com/blog/distributed-redisson-lock/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://helloworld.kurly.com/blog/distributed-redisson-lock/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devnm.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://devnm.tistory.com/37&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kirinman.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kirinman.tistory.com/97&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devnm.tistory.com/24&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://devnm.tistory.com/24&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/128</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/128#entry128comment</comments>
      <pubDate>Thu, 29 Aug 2024 10:55:24 +0900</pubDate>
    </item>
    <item>
      <title>[Packy] 선물박스 열기 동시성 문제 해결 1 - 분산 락을 선택한 이유</title>
      <link>https://leeeeeyeon-dev.tistory.com/127</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JF6Dn/btsJg5q4IJ6/Ow5qKsKOZeuHZw129UX7t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JF6Dn/btsJg5q4IJ6/Ow5qKsKOZeuHZw129UX7t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JF6Dn/btsJg5q4IJ6/Ow5qKsKOZeuHZw129UX7t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJF6Dn%2FbtsJg5q4IJ6%2FOw5qKsKOZeuHZw129UX7t0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;419&quot; height=&quot;239&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SwFea/btsJiU2a2G5/AknLKK0SFxKnYADIwtV8gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SwFea/btsJiU2a2G5/AknLKK0SFxKnYADIwtV8gk/img.png&quot; data-alt=&quot;패키 ERD 일부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SwFea/btsJiU2a2G5/AknLKK0SFxKnYADIwtV8gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSwFea%2FbtsJiU2a2G5%2FAknLKK0SFxKnYADIwtV8gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;675&quot; height=&quot;255&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;패키 ERD 일부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키에서는 선물박스를 받게 되면 Receiver 테이블에 받은 유저 ID와 선물박스 ID를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 하나의 선물박스를 1명만 받을 수 있지만(PRIVATE), 나중에 하나의 선물박스를 여러 유저가 받을 수 있는 상황(SEMI_PUBLIC, PUBLIC)을 고려하여 다대일로 관계를 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여러 유저가 동시에 선물박스 열기(=선물박스 받기)를 시도할 경우 어떻게 될까? 테스트 코드로 상황을 시뮬레이션 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 선물박스 열기 테스트 코드 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 유저가 동시에 선물박스 열기를 시도하는 상황을 어떻게 구현할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot에서는 내장된 서블릿 컨테이너(TomCat)에서 다중 요청을 처리한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;톰캣은 부팅할 때 스레드의 컬렉션인 Thread Pool을 생성&lt;/li&gt;
&lt;li&gt;유저 요청(HttpServletRequest)가 들어오면 Thread Pool에서 하나씩 Thread를 할당&lt;/li&gt;
&lt;li&gt;작업을 모두 수행하고 Thread는 Thread Pool로 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 &lt;b&gt;여러 스레드를 사용해서 유저가 동시에 접근하는 상황을 구현&lt;/b&gt;하면 된다. 코드는 아래와 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  giftBoxType은 PRIVATE이라고 가정한다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 나는 서비스 레이어 테스트 코드에 @Transactional 어노테이션을 추가하여 진행했는데 어노테이션을 사용할 경우 데이터가 의도한 것처럼 저장되지 않아 기존 GiftBoxServiceTest에 테스트 메서드를 작성하는 대신 @Transactional을 사용하지 않는 GiftBoxConcurrencyTest 테스트 클래스를 만들어 테스트를 진행하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1724812698626&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class GiftBoxConcurrencyTest {

    // 의존성 주입, BeforeEach 중략 ...
    
    @DisplayName(&quot;선물박스 열기 동시성 테스트&quot;)
    @Test
    void multipleUserOpenGiftBox() throws InterruptedException {
        // given
        int memberCount = 10;
        int giftBoxAmount = 1;

        List&amp;lt;Member&amp;gt; receivers = new ArrayList&amp;lt;&amp;gt;();
        Long lastMemberId = memberReader.count();
        for (long i = lastMemberId + 1; i &amp;lt;= lastMemberId + memberCount; i++) {
            Member member = memberWriter.save(NORMAL_MEMBER_RECEIVER.createMember());
            receivers.add(member);
        }

        GiftBox giftBox = giftBoxWriter.save(
            sendGiftBoxFixtureWithGift(MEMBER_SENDER, letter));

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CountDownLatch countDownLatch = new CountDownLatch(memberCount);

        AtomicInteger successCount = new AtomicInteger();
        AtomicInteger failCount = new AtomicInteger();

        Long receiverBefore = receiverReader.countByGiftBox(giftBox);

        // when
        for (int i = 0; i &amp;lt; memberCount; i++) {
            Member member = receivers.get(i);
            Long memberId = member.getId();
            executorService.submit(() -&amp;gt; {
                try {
                    createSecurityContextWithMockUser(memberId.toString());
                    giftBoxService.openGiftBox(giftBox.getId());
                    successCount.incrementAndGet();
                } catch (Exception e) {
                    e.printStackTrace();
                    failCount.incrementAndGet();
                } finally {
                    countDownLatch.countDown();
                }
            });
        }

        countDownLatch.await();

        Long receiverAfter = receiverReader.countByGiftBox(giftBox);
        List&amp;lt;Receiver&amp;gt; receiverList = receiverReader.findByGiftBox(giftBox);

        System.out.println(&quot;successCount = &quot; + successCount);
        System.out.println(&quot;failCount = &quot; + failCount);

        countDownLatch.await();

        // then
        assertThat(successCount.get()).isEqualTo(giftBoxAmount);
        assertThat(receiverAfter).isEqualTo(giftBoxAmount);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExecutorService를 사용하여 ThreadPool을 이용한 작업을 진행하고, CountDownLatch를 필요한 스레드 수의 값으로 초기화한 뒤 스레드 작업이 완료되는 것을 기다릴 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;1250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ToRDs/btsJhFrC5yy/nMgkSkSJcjIDx3O9jtWWjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ToRDs/btsJhFrC5yy/nMgkSkSJcjIDx3O9jtWWjk/img.png&quot; data-alt=&quot;테스트 코드 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ToRDs/btsJhFrC5yy/nMgkSkSJcjIDx3O9jtWWjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FToRDs%2FbtsJhFrC5yy%2FnMgkSkSJcjIDx3O9jtWWjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;237&quot; height=&quot;452&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;1250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트 코드 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선물박스 열기를 시도한 모든 유저가 RECEIVER로 변경되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1명만 열 수 있는 선물박스를 10명이 모두 열 수 있게 저장되는 문제가 발생하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 어떻게 해결해야 할까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;synchronized&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;synchronized는 Java에서 제공하는 키워드로, 모니터 기반 상호 배제(mutual exclusion) 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 synchronized를 사용하는 것에는 2가지 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;같은 프로세스에서만 상호 배제를 보장하기 때문에 분산 환경에서는 보장되지 않을 수 있다.&lt;/li&gt;
&lt;li&gt;@Transactional 어노테이션과 함께 사용할 경우 문제가 발생할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;1492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzrXiE/btsJhRehKt5/QJe3rd3AHrvlDJQJkWHnpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzrXiE/btsJhRehKt5/QJe3rd3AHrvlDJQJkWHnpK/img.png&quot; data-alt=&quot;출처: https://tecoble.techcourse.co.kr/post/2023-08-16-concurrency-managing/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzrXiE/btsJhRehKt5/QJe3rd3AHrvlDJQJkWHnpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzrXiE%2FbtsJhRehKt5%2FQJe3rd3AHrvlDJQJkWHnpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;366&quot; height=&quot;306&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;1492&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://tecoble.techcourse.co.kr/post/2023-08-16-concurrency-managing/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional 어노테이션이 붙은 메서드가 호출되면, Spring은 프록시 객체를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 프록시 객체는 원복 객체를 감싸며, 메서드 전후로 transaction begin과 transaction commit을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;transaction begin, commit은 synchronize의 영향을 받지 않기 때문에 위 그림처럼 T1 스레드에서 commit되기 전 T2 스레드가 시작하는 상황이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Lock&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락(Lock)은 데이터가 읽힌 후 사용될 때까지 데이터가 변경되는 것을 방지하기 위한 조치이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에서 제공하는 락(S-lock, X-lock 등...)도 있지만 어플리케이션 레벨에서의 락 기법도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 문제 해결을 위해 대표적으로 사용되는 락은 낙관적 락, 비관적 락, 분산 락이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3가지 락의 특징에 대해 간단하게 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;낙관적 락&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 트랜잭션 간 충돌이 일어나지 않을 것이라고 '낙관적'으로 가정하고, 실제 충돌이 발생했을 때만 대응한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에 대한 변경이 드물고, 충돌 가능성이 낮은 환경에서 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에 version 컬럼을 추가하여 버전 불일치 여부를 확인하는 방식으로 동작한다. 버전 불일치로 인한 실패 처리는 어플리케이션 레벨에서 처리해주어야 한다. (예외 처리 &amp;amp; 실패 후 재시도 로직)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;비관적 락&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 트랜잭션 간 충돌이 일어날 것이라고 '비관적'으로 가정하고, 데이터를 처음부터 잠그는 방식으로 충돌을 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 변경이 자주 발생하고, 충돌 가능성이 높은 환경에서 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 DB 환경에만 적용 가능하고, 많은 대기 시간이 발생한다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 많은 요청이 들어왔을 때, 사실상 하나의 요청씩 작업할 수 있기 때문에 요청이 많아지면 대기 시간이 길어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;분산 락&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 환경에서 상호 배제를 구현하여 동시성 문제를 다루기 위해 등장한 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;락에 대한 정보를 어딘가에 공통적으로 보관&lt;/b&gt;하고, 여러 대의 서버는 &lt;b&gt;공통된 어딘가&lt;/b&gt;를 바라보며 자신이 임계 영역에 접근할 수 있는지 확인하는 방법으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'어딘가'는 MySQL 네임드 락, Redis, Zookeeper 등의 기술이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어떤 락을 사용해야 할까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3가지 락을 비교해보았을 때, 낙관적 락과 비관적 락은 &lt;b&gt;특정 엔티티에 대한 동시 접근 제어&lt;/b&gt;에 관심이 있다. 즉, 이미 존재하는 엔티티에 동시 접근하는 상황(ex. update 쿼리)에 적합한 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 현재 문제 상황에서는 이미 존재하는 엔티티가 아니라 Receiver라는 &lt;b&gt;새로운 엔티티의 생성 개수 제한&lt;/b&gt;에 문제가 발생가 발생하므로 낙관적 락과 비관적 락은 적합하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 &lt;b&gt;분산 락을 최종적으로 선택&lt;/b&gt;하여 동시성 문제를 해결하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 분산 락을 구현하는 2가지 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 락을 구현하는 방법은 1️⃣ SENTX를 활용해 스핀 락을 구현하는 방법과 2️⃣ Message Broker를 사용하는 2가지 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SENTX를 활용한 스핀 락 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SENTX는 Redis의 명령어로 키에 값이 존재하지 않을 때만 값을 설정할 수 있도록 하는 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 연산을 사용하여 스핀 락 방식을 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스핀 락은 락을 사용할 수 있을 때까지 지속적으로 확인하며 기다리는 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1724817633065&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 락을 획득할 때까지 대기하는 예시 코드
while(!redisLockRepository.lock(giftBoxId)) {
	Thread.sleep(100);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 제공되는 Redis Client인 Lettuce만으로 간단하게 구현 가능하다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 락을 사용할 수 있을 때까지 계속 접근을 시도하므로 Reids 서버에 불필요한 부하를 주게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 애플리케이션 장애로 인해 락을 획득하지 못한다면 무한 루프를 돌거나, 락을 잡고 있던 서버가 죽어 락 해제를 하지 못하는 상황도 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Message Broker 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 캐시뿐만 아니라 Pub/Sub 구조의 Message Broker로도 사용이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subscribe라는 명령어로 subscriber는 특정 channel을 구독할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;publisher는 publish 명령으로 특정 channel에 메시지를 발행할 수 있고, 해당 channel을 구독한 subscriber는 메시지를 받을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;503&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CNyxt/btsJig5SNGH/CP1q43obmeASXA2w4Kzs70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CNyxt/btsJig5SNGH/CP1q43obmeASXA2w4Kzs70/img.png&quot; data-alt=&quot;출처: https://www.geeksforgeeks.org/redis-publish-subscribe/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CNyxt/btsJig5SNGH/CP1q43obmeASXA2w4Kzs70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCNyxt%2FbtsJig5SNGH%2FCP1q43obmeASXA2w4Kzs70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;463&quot; height=&quot;233&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://www.geeksforgeeks.org/redis-publish-subscribe/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 락에서는 락을 해제하는 측이 락을 대기하는 프로세스에게   이제 락 획득 시도 해제해도 돼~   라고 메시지를 발행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redisson이라는 라이브러리에 분산 락 메커니즘과 재시도 로직이 구현되어 있기 때문에 해당 라이브러리를 사용하여 분산 락을 구현할 수 있다. 우리는 락 획득 시도, 락 획득 성공 후 처리, 락 획득 실패 로직을 구현하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 방식을 비교해보았을 때, 스핀 락은 락을 획득하지 못하는 상황이나 장애 발생 상황에 대해 추가적으로 구현할 로직이 많고 이를 빼먹을 경우 선물박스 열기라는 핵심 비즈니스 로직이 아예 동작하지 않는 심각한 상황이 발생할 수 있어 &lt;b&gt;더 안정적인 Message Broker 방식을 사용&lt;/b&gt;하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 선물박스 열기 로직에 동시성 제어를 도입하려는 이유를 소개하고 테스트 코드를 통한 문제 상황 파악하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 synchronized 키워드와 3가지 락에 대한 개념을 기본적으로 알아보고, 최종적으로 Redis의 Message Broker 방식을 활용한 분산 락으로 해결할 것을 결정 지었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redisson을 이용한 구체적인 구현 과정을 이번 포스트에서 적으면 글이 너무 길어질 것 같아 다음 포스트(아래 링크 참고)에서 작성하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1732068369658&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Packy] 선물박스 열기 동시성 문제 해결 2 - Redisson으로 분산락 구현&quot; data-og-description=&quot;1. 들어가며&amp;nbsp;[Packy] 선물박스 열기 동시성 문제 해결 1 - 분산 락을 선택한 이유1. 들어가며&amp;nbsp;패키에서는 선물박스를 받게 되면 Receiver 테이블에 받은 유저 ID와 선물박스 ID를 저장한다.지금은 하나&quot; data-og-host=&quot;leeeeeyeon-dev.tistory.com&quot; data-og-source-url=&quot;https://leeeeeyeon-dev.tistory.com/128&quot; data-og-url=&quot;https://leeeeeyeon-dev.tistory.com/128&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/clwjuT/hyXDg3Q2jR/PK1DQmSECe85DuyYnxtF01/img.png?width=800&amp;amp;height=455&amp;amp;face=0_0_800_455,https://scrap.kakaocdn.net/dn/y79CE/hyXzVG4CoQ/ZUvsEdiKWElUULmOmkc3t1/img.png?width=800&amp;amp;height=455&amp;amp;face=0_0_800_455,https://scrap.kakaocdn.net/dn/HPDwD/hyXzOVurdH/imBxB6kX1Tk8FZiIpL4cPk/img.png?width=2178&amp;amp;height=1038&amp;amp;face=0_0_2178_1038&quot;&gt;&lt;a href=&quot;https://leeeeeyeon-dev.tistory.com/128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leeeeeyeon-dev.tistory.com/128&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/clwjuT/hyXDg3Q2jR/PK1DQmSECe85DuyYnxtF01/img.png?width=800&amp;amp;height=455&amp;amp;face=0_0_800_455,https://scrap.kakaocdn.net/dn/y79CE/hyXzVG4CoQ/ZUvsEdiKWElUULmOmkc3t1/img.png?width=800&amp;amp;height=455&amp;amp;face=0_0_800_455,https://scrap.kakaocdn.net/dn/HPDwD/hyXzOVurdH/imBxB6kX1Tk8FZiIpL4cPk/img.png?width=2178&amp;amp;height=1038&amp;amp;face=0_0_2178_1038');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Packy] 선물박스 열기 동시성 문제 해결 2 - Redisson으로 분산락 구현&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 들어가며&amp;nbsp;[Packy] 선물박스 열기 동시성 문제 해결 1 - 분산 락을 선택한 이유1. 들어가며&amp;nbsp;패키에서는 선물박스를 받게 되면 Receiver 테이블에 받은 유저 ID와 선물박스 ID를 저장한다.지금은 하나&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leeeeeyeon-dev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hudi.blog/distributed-lock-with-redis/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hudi.blog/distributed-lock-with-redis/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2023-08-16-concurrency-managing/?fbclid=IwAR2jTEluMZ7aRZDR45ryDgao0Gw7TiA9LX4yFf6Mn51iUqS3jnS8tdnndoo_aem_Ab6XHScx9dCEKmt-gZcJrH3NptNkqCJmb2thytRUzegpL6H017gfOfiLNODaPqp3M_M&amp;amp;mibextid=Zxz2cZ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tecoble.techcourse.co.kr/post/2023-08-16-concurrency-managing/?fbclid=IwAR2jTEluMZ7aRZDR45ryDgao0Gw7TiA9LX4yFf6Mn51iUqS3jnS8tdnndoo_aem_Ab6XHScx9dCEKmt-gZcJrH3NptNkqCJmb2thytRUzegpL6H017gfOfiLNODaPqp3M_M&amp;amp;mibextid=Zxz2cZ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/127</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/127#entry127comment</comments>
      <pubDate>Wed, 28 Aug 2024 13:25:15 +0900</pubDate>
    </item>
    <item>
      <title>[LitmusChaos] M1에서 LitmusChaos 실행하고 카오스 주입하기</title>
      <link>https://leeeeeyeon-dev.tistory.com/126</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7048M/btsIKSYS5KA/uC1RzHmXuUGcvT4M3TSBp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7048M/btsIKSYS5KA/uC1RzHmXuUGcvT4M3TSBp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7048M/btsIKSYS5KA/uC1RzHmXuUGcvT4M3TSBp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7048M%2FbtsIKSYS5KA%2FuC1RzHmXuUGcvT4M3TSBp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;68&quot; height=&quot;68&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 카오스 엔지니어링과 LitmusChaos 간단 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카오스 엔지니어링은 간단히 말해 시스템의 복원력을 테스트하기 위해 의도적으로 장애를 일으켜보며 취약점을 찾고 보강하는 방식의 엔지니어링 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LitmusChaos는 쿠버네티스 플랫폼 위에서 카오스 엔지니어링을 진행할 수 있도록 도와주는 오픈소스 프로젝트이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1922&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lmruH/btsIJJuZPfj/gqgJfWY2GxRVQP8TQIiWr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lmruH/btsIJJuZPfj/gqgJfWY2GxRVQP8TQIiWr0/img.png&quot; data-alt=&quot;LitmusChaos 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lmruH/btsIJJuZPfj/gqgJfWY2GxRVQP8TQIiWr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlmruH%2FbtsIJJuZPfj%2FgqgJfWY2GxRVQP8TQIiWr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;292&quot; data-origin-width=&quot;1922&quot; data-origin-height=&quot;980&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LitmusChaos 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 준비물&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Minikube: 쿠버네티스를 로컬 환경에서 사용할 수 있게 만든 가벼운 구현체&lt;/li&gt;
&lt;li&gt;Helm: 쿠버네티스 패키지 매니저 (like Mac의 brew, Node의 npm ...)&lt;/li&gt;
&lt;li&gt;카오스를 주입할 &lt;a title=&quot;데모 서비스&quot; href=&quot;https://github.com/GoogleCloudPlatform/microservices-demo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;데모 서비스&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Minikube와 Helm 설치 과정은 생략하고, 데모 서비스의 경우 아래 명령어로 실행할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1721740581261&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Clone Repo
git clone https://github.com/GoogleCloudPlatform/microservices-demo

# Move Directory
cd microservices-demo/

# Install using mainfest file
kubectl apply -f ./release/kubernetes-manifests.yaml

# Access the demo
minikube service frontend-external --url&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wv6qS/btsIJDuJalS/9vwgRhjFaBAda06H3NEHOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wv6qS/btsIJDuJalS/9vwgRhjFaBAda06H3NEHOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wv6qS/btsIJDuJalS/9vwgRhjFaBAda06H3NEHOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwv6qS%2FbtsIJDuJalS%2F9vwgRhjFaBAda06H3NEHOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1480&quot; height=&quot;162&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 명령어를 실행하여 나온 URL로 데모 서비스에 접속할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;1908&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UbmUg/btsIKd99oYi/JA6cEP0H6394CuC1hEwT20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UbmUg/btsIKd99oYi/JA6cEP0H6394CuC1hEwT20/img.png&quot; data-alt=&quot;데모 서비스 접속 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UbmUg/btsIKd99oYi/JA6cEP0H6394CuC1hEwT20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUbmUg%2FbtsIKd99oYi%2FJA6cEP0H6394CuC1hEwT20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;297&quot; height=&quot;300&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;1908&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데모 서비스 접속 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. LitmusChaos 설치&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  지금부터 나오는 설치 과정은 M1을 기준으로 작성되었습니다&lt;br /&gt;  포스트 작성일(2024.07.23)을 기준으로 최신 버전(3.9.1)의 auth-server에 임시적인 로그인 이슈가 있어 3.8.0 버전으로 실습을 진행하였습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 LitmusChaos Helm repo를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721740817528&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Helm repo 추가
helm repo add litmuschaos https://litmuschaos.github.io/litmus-helm/

# Helm repo 확인
helm repo list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 쿠버네티스 네임스페이스를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721740916970&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 네임스페이스 생성
kubectl create ns litmus

# 네임스페이스 확인
kubectl get ns&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ARM 기반 MongoDB를 사용하는&amp;nbsp; ChaosCenter를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721741156756&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install chaos litmuschaos/litmus --namespace=litmus \
--set portal.frontend.service.type=NodePort \
--set mongodb.image.registry=ghcr.io/zcube \
--set mongodb.image.repository=bitnami-compat/mongodb \
--set mongodb.image.tag=6.0.5 \
--set litmuschaos.image.tag=3.8.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 사진처럼 모든 파드가 정상적으로 실행되는지 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qPROI/btsIKg6Y4Fc/xhY9aJzhMfSAcdUVwSs6U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qPROI/btsIKg6Y4Fc/xhY9aJzhMfSAcdUVwSs6U0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qPROI/btsIKg6Y4Fc/xhY9aJzhMfSAcdUVwSs6U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqPROI%2FbtsIKg6Y4Fc%2FxhY9aJzhMfSAcdUVwSs6U0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;151&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Chaos Center 접속 및 Environment 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 입력하여 나온 URL로 Chaos Center(LitmusChaos 관리 페이지)에 접속할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1721741450271&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; minikube service chaos-litmus-frontend-service -n litmus --url&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mYB4D/btsIJZqSKhh/EEwxLZ1FKkwM1VCwGbWD4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mYB4D/btsIJZqSKhh/EEwxLZ1FKkwM1VCwGbWD4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mYB4D/btsIJZqSKhh/EEwxLZ1FKkwM1VCwGbWD4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmYB4D%2FbtsIJZqSKhh%2FEEwxLZ1FKkwM1VCwGbWD4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;559&quot; height=&quot;65&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 로그인 아이디와 비밀번호는 admin / litmus이고, 로그인 후 비밀번호를 바꿔야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 로그인하면 아래와 같은 팝업 창이 나오는데 버튼을 눌러 Environment를 만들어주자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3438&quot; data-origin-height=&quot;1888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQ66JF/btsILktVQfi/L7Ju9KHg3xjtYCo3M2LW5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQ66JF/btsILktVQfi/L7Ju9KHg3xjtYCo3M2LW5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQ66JF/btsILktVQfi/L7Ju9KHg3xjtYCo3M2LW5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQ66JF%2FbtsILktVQfi%2FL7Ju9KHg3xjtYCo3M2LW5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;291&quot; data-origin-width=&quot;3438&quot; data-origin-height=&quot;1888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;New Environment 버튼을 눌러주고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;1902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kfuyn/btsIKhY7Tq1/nPzyKsuP7RAwr02Fn5qROK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kfuyn/btsIKhY7Tq1/nPzyKsuP7RAwr02Fn5qROK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kfuyn/btsIKhY7Tq1/nPzyKsuP7RAwr02Fn5qROK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKfuyn%2FbtsIKhY7Tq1%2FnPzyKsuP7RAwr02Fn5qROK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;367&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;1902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적당한 이름을 작성해주고 타입은 pre-production을 선택하여 Environment를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Environment Type은 스테이징 관련인 것 같은데 지금은 간단한 실습을 해보는 것이므로 어떤 것을 선택하든 상관 없을 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1098&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs0BJy/btsIKD8HtsS/LBPnw7lOrGuwDezS9nycA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs0BJy/btsIKD8HtsS/LBPnw7lOrGuwDezS9nycA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs0BJy/btsIKD8HtsS/LBPnw7lOrGuwDezS9nycA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs0BJy%2FbtsIKD8HtsS%2FLBPnw7lOrGuwDezS9nycA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;298&quot; height=&quot;303&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1098&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XPK0B/btsIJtMFoqT/q4zhxnLhbupfJk27nI4VWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XPK0B/btsIJtMFoqT/q4zhxnLhbupfJk27nI4VWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XPK0B/btsIJtMFoqT/q4zhxnLhbupfJk27nI4VWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXPK0B%2FbtsIJtMFoqT%2Fq4zhxnLhbupfJk27nI4VWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;146&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 Environment 안에 들어가 Chaos Infrastructure를 만들어주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Configuration은 기본 설정으로 해주었고, 마지막 창에 나오는 설정 파일을 다운로드 받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;1272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nuVzG/btsILTpa0iA/YKoJ0I7RAPazOfU6nZQ23K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nuVzG/btsILTpa0iA/YKoJ0I7RAPazOfU6nZQ23K/img.png&quot; data-alt=&quot;버튼 클릭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nuVzG/btsILTpa0iA/YKoJ0I7RAPazOfU6nZQ23K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnuVzG%2FbtsILTpa0iA%2FYKoJ0I7RAPazOfU6nZQ23K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;556&quot; height=&quot;247&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;1272&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;버튼 클릭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2312&quot; data-origin-height=&quot;1358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cysrD2/btsIJZdi6UR/C6lKatQl7WnZVlbtyYAd11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cysrD2/btsIJZdi6UR/C6lKatQl7WnZVlbtyYAd11/img.png&quot; data-alt=&quot;이름 작성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cysrD2/btsIJZdi6UR/C6lKatQl7WnZVlbtyYAd11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcysrD2%2FbtsIJZdi6UR%2FC6lKatQl7WnZVlbtyYAd11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;524&quot; height=&quot;308&quot; data-origin-width=&quot;2312&quot; data-origin-height=&quot;1358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이름 작성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2328&quot; data-origin-height=&quot;1348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r7zLs/btsIK5DEvUU/8HKsljQxdy9C3A0vDrIKAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r7zLs/btsIK5DEvUU/8HKsljQxdy9C3A0vDrIKAk/img.png&quot; data-alt=&quot;기본 설정 유지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r7zLs/btsIK5DEvUU/8HKsljQxdy9C3A0vDrIKAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr7zLs%2FbtsIK5DEvUU%2F8HKsljQxdy9C3A0vDrIKAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;294&quot; data-origin-width=&quot;2328&quot; data-origin-height=&quot;1348&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기본 설정 유지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2330&quot; data-origin-height=&quot;1362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xnDvG/btsIJzGbbua/zuHFgEkK6Ko2ICvxDQ0iH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xnDvG/btsIJzGbbua/zuHFgEkK6Ko2ICvxDQ0iH1/img.png&quot; data-alt=&quot;설정 파일 다운로드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xnDvG/btsIJzGbbua/zuHFgEkK6Ko2ICvxDQ0iH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxnDvG%2FbtsIJzGbbua%2FzuHFgEkK6Ko2ICvxDQ0iH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;505&quot; height=&quot;295&quot; data-origin-width=&quot;2330&quot; data-origin-height=&quot;1362&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;설정 파일 다운로드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  그러나 위에 있는 설정 파일을 그대로 배포하면 subscriber 파드가 배포에 실패하는 문제가 발생한다.  &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어로 확인한 ClusterIP와 TargetPort(PORT의 왼쪽 번호)로 설정 파일의 서버 주소를 변경해주고 apply를 진행한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;9091:32102에서 왼쪽에 적힌 값은 targetPort, 오른쪽에 적힌 값은 nodePort라고 한다.&lt;br /&gt;- targetPort: 클러스터 내 다른 파드가 서비스에 접근할 때 사용&lt;br /&gt;- nodePort: 클러스터 외부에서 노출되어 직접 접근할 때 사용&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJU4XR/btsIKJ8Ogaj/xDmSnQUCIKBcO7ITk3eerK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJU4XR/btsIKJ8Ogaj/xDmSnQUCIKBcO7ITk3eerK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJU4XR/btsIKJ8Ogaj/xDmSnQUCIKBcO7ITk3eerK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJU4XR%2FbtsIKJ8Ogaj%2FxDmSnQUCIKBcO7ITk3eerK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;44&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;1384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mlgiA/btsIJDIhpYK/WSyqOdez0oBBspfW9Tk6I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mlgiA/btsIJDIhpYK/WSyqOdez0oBBspfW9Tk6I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mlgiA/btsIJDIhpYK/WSyqOdez0oBBspfW9Tk6I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmlgiA%2FbtsIJDIhpYK%2FWSyqOdez0oBBspfW9Tk6I1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;464&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;1384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정까지 성공적으로 수행하면 Pending 상태였던 Chaos Infrastructure가 Connected 상태로 변한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2828&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnbPMz/btsIJuLvyoL/EvEzVYvdh5Kgktso13wQs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnbPMz/btsIJuLvyoL/EvEzVYvdh5Kgktso13wQs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnbPMz/btsIJuLvyoL/EvEzVYvdh5Kgktso13wQs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnbPMz%2FbtsIJuLvyoL%2FEvEzVYvdh5Kgktso13wQs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2828&quot; height=&quot;380&quot; data-origin-width=&quot;2828&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Probe 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카오스 엔지니어링에서는 JVM에 장애를 발생 시키기, DB나 Kafka에 발생 시키기, istio 없애기, 쿠버네티스 파드 없애기 등 다양한 장애 상황을 테스트해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 &lt;b&gt;cartservice의 파드를 제거&lt;/b&gt;하는 상황에 대해 테스트를 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Resilience Probes 탭에 들어가 Probe를 생성해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Probe는 카오스 실험을 진행했을 때 나오는 이상적인 결과를 정의한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) nginx에 HTTP 요청을 보냈을 때 200 요청이 오면 정상이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 '파드를 제거하더라도 파드가 존재한다'를 Probe로 정의할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어는 파드가 존재하는지 확인하는 명령어인데, 해당 명령어의 결과가 0보다 큰지 확인하는 것이 Probe가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BZ6ya/btsIJJIy5yT/A3hkKSzPhPqXxT8OQPopV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BZ6ya/btsIJJIy5yT/A3hkKSzPhPqXxT8OQPopV0/img.png&quot; data-alt=&quot;파드가 존재하는지 확인하는 명령어&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BZ6ya/btsIJJIy5yT/A3hkKSzPhPqXxT8OQPopV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBZ6ya%2FbtsIJJIy5yT%2FA3hkKSzPhPqXxT8OQPopV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;43&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;파드가 존재하는지 확인하는 명령어&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Probe 생성 버튼을 눌러 Command Probe를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mnbiA/btsIKYxThZi/klyMW9il3yaDi4hRrYwA71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mnbiA/btsIKYxThZi/klyMW9il3yaDi4hRrYwA71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mnbiA/btsIKYxThZi/klyMW9il3yaDi4hRrYwA71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmnbiA%2FbtsIKYxThZi%2FklyMW9il3yaDi4hRrYwA71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;206&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPusA6/btsIJvp9U5W/huE1ysk89hyw3sJ9LVEM61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPusA6/btsIJvp9U5W/huE1ysk89hyw3sJ9LVEM61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPusA6/btsIJvp9U5W/huE1ysk89hyw3sJ9LVEM61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPusA6%2FbtsIJvp9U5W%2FhuE1ysk89hyw3sJ9LVEM61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;465&quot; height=&quot;226&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 값은 아래와 같이 해주고 Optional 값은 생략하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/50oQV/btsIJs1embL/FynNhCjlx4rawt5nMjdbI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/50oQV/btsIJs1embL/FynNhCjlx4rawt5nMjdbI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/50oQV/btsIJs1embL/FynNhCjlx4rawt5nMjdbI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F50oQV%2FbtsIJs1embL%2FFynNhCjlx4rawt5nMjdbI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;275&quot; height=&quot;333&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;1168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'kubectl get pods -n default | grep cartservice | grep Running | wc -l' 명령어의 결과가 0보다 크다는 의미이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1960&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSm2jV/btsILTJusB5/33XH712tPmCGFKZNMdaO7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSm2jV/btsILTJusB5/33XH712tPmCGFKZNMdaO7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSm2jV/btsILTJusB5/33XH712tPmCGFKZNMdaO7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSm2jV%2FbtsILTJusB5%2F33XH712tPmCGFKZNMdaO7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;456&quot; height=&quot;285&quot; data-origin-width=&quot;1960&quot; data-origin-height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 카오스 주입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chaos Experiments 탭에서 New Experiment 버튼을 눌러 Chaos Experiment를 생성해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4번 과정에서 생성한 Chaos Infrastructure를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3272&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biGOrT/btsIJH42GYZ/Ty7Hm2KUd7MIAbIUIH0XIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biGOrT/btsIJH42GYZ/Ty7Hm2KUd7MIAbIUIH0XIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biGOrT/btsIJH42GYZ/Ty7Hm2KUd7MIAbIUIH0XIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiGOrT%2FbtsIJH42GYZ%2FTy7Hm2KUd7MIAbIUIH0XIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;150&quot; data-origin-width=&quot;3272&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2906&quot; data-origin-height=&quot;1794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rn8hB/btsIJcEtBHl/6KdLAkyjztEEB9luKWkelK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rn8hB/btsIJcEtBHl/6KdLAkyjztEEB9luKWkelK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rn8hB/btsIJcEtBHl/6KdLAkyjztEEB9luKWkelK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRn8hB%2FbtsIJcEtBHl%2F6KdLAkyjztEEB9luKWkelK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;414&quot; data-origin-width=&quot;2906&quot; data-origin-height=&quot;1794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blank Canvas를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3412&quot; data-origin-height=&quot;1286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xWBwh/btsIJe97Ezm/nRV9fu45JQnwKg2lGisoh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xWBwh/btsIJe97Ezm/nRV9fu45JQnwKg2lGisoh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xWBwh/btsIJe97Ezm/nRV9fu45JQnwKg2lGisoh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxWBwh%2FbtsIJe97Ezm%2FnRV9fu45JQnwKg2lGisoh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;230&quot; data-origin-width=&quot;3412&quot; data-origin-height=&quot;1286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Add 버튼을 누르고, pod-delete를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2878&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bld6Zm/btsIK5XVNbc/9eh83Ch7Lq3M1WN2LqsTEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bld6Zm/btsIK5XVNbc/9eh83Ch7Lq3M1WN2LqsTEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bld6Zm/btsIK5XVNbc/9eh83Ch7Lq3M1WN2LqsTEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbld6Zm%2FbtsIK5XVNbc%2F9eh83Ch7Lq3M1WN2LqsTEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;165&quot; data-origin-width=&quot;2878&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2414&quot; data-origin-height=&quot;1858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E4g14/btsILHbkIeP/Lv4ewqzhHEnYHvQUHfst40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E4g14/btsILHbkIeP/Lv4ewqzhHEnYHvQUHfst40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E4g14/btsILHbkIeP/Lv4ewqzhHEnYHvQUHfst40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE4g14%2FbtsILHbkIeP%2FLv4ewqzhHEnYHvQUHfst40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;384&quot; data-origin-width=&quot;2414&quot; data-origin-height=&quot;1858&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Target Application 탭에서 설정을 아래 사진처럼 설정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Oi0eS/btsIJXfv1do/VkcHJ9484fbbG2YXcW0G9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Oi0eS/btsIJXfv1do/VkcHJ9484fbbG2YXcW0G9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Oi0eS/btsIJXfv1do/VkcHJ9484fbbG2YXcW0G9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOi0eS%2FbtsIJXfv1do%2FVkcHJ9484fbbG2YXcW0G9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;558&quot; height=&quot;261&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 cartservice에 라벨이 없다면 아래 명령어로 라벨을 추가해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1721744448287&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl label deployment cartservice app=cartservice&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ewml0X/btsIKaZRLpD/9UWFrqQKkeXbfwZpNdCfB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ewml0X/btsIKaZRLpD/9UWFrqQKkeXbfwZpNdCfB1/img.png&quot; data-alt=&quot;라벨 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ewml0X/btsIKaZRLpD/9UWFrqQKkeXbfwZpNdCfB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fewml0X%2FbtsIKaZRLpD%2F9UWFrqQKkeXbfwZpNdCfB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;586&quot; height=&quot;203&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;라벨 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Probes 탭에 들어가 아까 만든 Probe를 EOT 모드로 추가해준 뒤 Apply Changes를 누르면 모든 설정이 끝난다  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;1494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgwgcV/btsIKCaTqP5/y8qMz3U7cs24Mfx8yS7q0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgwgcV/btsIKCaTqP5/y8qMz3U7cs24Mfx8yS7q0K/img.png&quot; data-alt=&quot;Select Probe 클릭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgwgcV/btsIKCaTqP5/y8qMz3U7cs24Mfx8yS7q0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgwgcV%2FbtsIKCaTqP5%2Fy8qMz3U7cs24Mfx8yS7q0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;370&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;1494&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Select Probe 클릭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGZWtG/btsILsZFUOY/zlBX6FdKKWoOrPYrZj0Dfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGZWtG/btsILsZFUOY/zlBX6FdKKWoOrPYrZj0Dfk/img.png&quot; data-alt=&quot;Add to Fault 클릭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGZWtG/btsILsZFUOY/zlBX6FdKKWoOrPYrZj0Dfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGZWtG%2FbtsILsZFUOY%2FzlBX6FdKKWoOrPYrZj0Dfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;432&quot; height=&quot;361&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1342&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Add to Fault 클릭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;1480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ntnUb/btsIKXeFTDh/EtUu0EEME2XynwPNXktKp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ntnUb/btsIKXeFTDh/EtUu0EEME2XynwPNXktKp1/img.png&quot; data-alt=&quot;EOT 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ntnUb/btsIKXeFTDh/EtUu0EEME2XynwPNXktKp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FntnUb%2FbtsIKXeFTDh%2FEtUu0EEME2XynwPNXktKp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;417&quot; height=&quot;390&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;1480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;EOT 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Run 버튼을 누르고 잠시 기다리면 ... 카오스 테스트가 성공하는 것을 볼 수 있다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2892&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q9Njw/btsIJxIn32D/JyLs0QtTo0ERxIv4IsZwyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q9Njw/btsIJxIn32D/JyLs0QtTo0ERxIv4IsZwyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q9Njw/btsIJxIn32D/JyLs0QtTo0ERxIv4IsZwyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq9Njw%2FbtsIJxIn32D%2FJyLs0QtTo0ERxIv4IsZwyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;139&quot; data-origin-width=&quot;2892&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Install-chaos-faults와 cleanup-chaos-resources 단계가 갑자기 추가되었는데, 카오스 실험에 필요한 리소스들을 설치하고 실험이 종료된 뒤 다시 삭제하는 단계인 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2738&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEX0ix/btsIKX6Psu7/4dYji8VSekguQPTB9DTQ30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEX0ix/btsIKX6Psu7/4dYji8VSekguQPTB9DTQ30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEX0ix/btsIKX6Psu7/4dYji8VSekguQPTB9DTQ30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEX0ix%2FbtsIKX6Psu7%2F4dYji8VSekguQPTB9DTQ30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;622&quot; height=&quot;141&quot; data-origin-width=&quot;2738&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VZ31t/btsIKYSbzIf/6kcISBwcysHTqojubC84LK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VZ31t/btsIKYSbzIf/6kcISBwcysHTqojubC84LK/img.gif&quot; data-alt=&quot;끝!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VZ31t/btsIKYSbzIf/6kcISBwcysHTqojubC84LK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/VZ31t/btsIKYSbzIf/6kcISBwcysHTqojubC84LK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;301&quot; height=&quot;220&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;끝!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.litmuschaos.io/docs/introduction/what-is-litmus&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.litmuschaos.io/docs/introduction/what-is-litmus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.litmuschaos.io/docs/architecture/architecture-summary&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.litmuschaos.io/docs/architecture/architecture-summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cncf.io/blog/2024/04/01/elevating-system-resilience-leveraging-litmuschaos-and-backstage-integration/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.cncf.io/blog/2024/04/01/elevating-system-resilience-leveraging-litmuschaos-and-backstage-integration/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/suyeon-jung-dev/suyeon-jung-dev/blob/main/learning-records/devops/litmus/installing_chaos.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/suyeon-jung-dev/suyeon-jung-dev/blob/main/learning-records/devops/litmus/installing_chaos.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/I2kki5pAqQ4?si=8M1MnM-SPZlljJ-H&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/I2kki5pAqQ4?si=8M1MnM-SPZlljJ-H&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/wnEYugH7kW0?si=_SqJT9s3KUCWsv9n&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/wnEYugH7kW0?si=_SqJT9s3KUCWsv9n&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Infrastructure</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/126</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/126#entry126comment</comments>
      <pubDate>Tue, 23 Jul 2024 23:51:33 +0900</pubDate>
    </item>
    <item>
      <title>[트러블슈팅] M1 minikube start 실패</title>
      <link>https://leeeeeyeon-dev.tistory.com/125</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문제 상황&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EiUTk/btsIH2Ad88k/sH64EDIDpKuusiqD7NmxL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EiUTk/btsIH2Ad88k/sH64EDIDpKuusiqD7NmxL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EiUTk/btsIH2Ad88k/sH64EDIDpKuusiqD7NmxL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEiUTk%2FbtsIH2Ad88k%2FsH64EDIDpKuusiqD7NmxL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;351&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 실습을 위해 minikube를 실행하려고 했는데 위와 같이 실패하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/minikube/issues/14477&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kubernetes/minikube/issues/14477&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721542143478&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Minikube didnt start &amp;middot; Issue #14477 &amp;middot; kubernetes/minikube&quot; data-og-description=&quot;What Happened? when hitting command &amp;quot;minikube start&amp;quot; on local machine, getting the below error. However, i tried minikube delete and start again but didnt resolve the issue. Docker Desktop is insta...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kubernetes/minikube/issues/14477&quot; data-og-url=&quot;https://github.com/kubernetes/minikube/issues/14477&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BQzBj/hyWCBhLoXa/lIywj1sL3rq12WfkT3u0Z1/img.png?width=1200&amp;amp;height=600&amp;amp;face=999_123_1074_205&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/minikube/issues/14477&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kubernetes/minikube/issues/14477&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BQzBj/hyWCBhLoXa/lIywj1sL3rq12WfkT3u0Z1/img.png?width=1200&amp;amp;height=600&amp;amp;face=999_123_1074_205');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Minikube didnt start &amp;middot; Issue #14477 &amp;middot; kubernetes/minikube&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What Happened? when hitting command &quot;minikube start&quot; on local machine, getting the below error. However, i tried minikube delete and start again but didnt resolve the issue. Docker Desktop is insta...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHoKlV/btsIHK7Fgw9/rNKPm0KVr69KNcMFxPuWb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHoKlV/btsIHK7Fgw9/rNKPm0KVr69KNcMFxPuWb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHoKlV/btsIHK7Fgw9/rNKPm0KVr69KNcMFxPuWb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHoKlV%2FbtsIHK7Fgw9%2FrNKPm0KVr69KNcMFxPuWb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;597&quot; height=&quot;191&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이슈와 댓글을 참고하여&lt;/p&gt;
&lt;pre id=&quot;code_1721542160564&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;minikube delete --all --purge&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 해주고 다시 start를 해주니 정상적으로 실행되었다.&lt;/p&gt;</description>
      <category>Infrastructure/Docker &amp;amp; k8s</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/125</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/125#entry125comment</comments>
      <pubDate>Sun, 21 Jul 2024 15:13:44 +0900</pubDate>
    </item>
    <item>
      <title>[Packy] 전략 패턴으로 복잡한 if문 리팩토링하기</title>
      <link>https://leeeeeyeon-dev.tistory.com/124</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키는 온라인 상으로 선물박스를 만들고 '&lt;b&gt;주고받는&lt;/b&gt;' 서비스이다. 패키에서는 선물박스를 접하는 입장에서 유저를 보내는 유저(Sender), 받는 유저(Receiver), 둘 다 아닌 유저(Stranger) 3가지로 구분할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 대부분의 기능에 대해 3가지 입장을 모두 고려하여 로직을 설계해야 한다. 그러다보니 하나의 메서드에서 if문이 늘어나고, 이는 한 메서드의 책임이 너무 커지는 문제를 초래하며 가독성도 같이 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 '선물박스 삭제하기' 로직을 대표로 예시를 들어 설명하겠다. 아래는 리팩토링을 진행하기 전 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if문이 너무 많이 중첩되어 있어 주석 없이 코드를 이해하는데 오랜 시간이 소요된다..&lt;/p&gt;
&lt;pre id=&quot;code_1719410405305&quot; class=&quot;pgsql&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public String deleteGiftBox(Long giftBoxId) {  
    Long memberId = SecurityUtil.getMemberId();  
    Member member = memberReader.findById(memberId);  
  
    GiftBox giftBox = giftBoxReader.findById(giftBoxId);  
    GiftBoxRole role = getGiftBoxRole(member, giftBox);  
  
    if (role.equals(MemberRole.STRANGER)) {
            throw new GiftBoxAccessDeniedException();
        }
    if (role.equals(GiftBoxRole.SENDER)) {  
        if (giftBox.getDeliverStatus().equals(DeliverStatus.DELIVERED)) {  
            giftBox.delete();  
        } else if (giftBox.getDeliverStatus().equals(DeliverStatus.WAITING)) {  
            letterWriter.delete(giftBox.getLetter());  
            giftBox.getPhotos().forEach(photo -&amp;gt; {  
                fileService.deleteFile(photo.getImgUrl());  
                photoWriter.delete(photo);  
            });  
            if (giftBox.getGift().getGiftType().equals(GiftType.PHOTO)) {  
                fileService.deleteFile(giftBox.getGift().getGiftUrl());  
            }  
            giftBox.getGiftBoxStickers().forEach(giftBoxStickerWriter::delete);  
            giftBoxWriter.delete(giftBox);  
        }  
    } else if (role.equals(GiftBoxRole.RECEIVER)) {  
        Receiver receiver = receiverReader.findByMemberAndGiftBox(member, giftBox);  
        receiver.delete();  
    }  
  
    return &quot;선물박스가 삭제되었습니다&quot;;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선물박스 삭제하기 로직의 if문의 가장 큰 특징은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;유저 타입(Sender, Receiver)에 따라 선물박스 삭제 로직이 다르다&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Stranger의 경우 선물박스 삭제 권한이 없으므로 예외를 던진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 식으로 리팩토링을 할까 찾아보다가 &lt;b&gt;전략 패턴&lt;/b&gt;이라는 디자인 패턴을 알게 되었고, 지금 상황에 알맞는 방법인 것 같아 전략 패턴을 사용하여 코드를 리팩토링해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 전략 패턴이 뭐죠?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전략 패턴의 정의는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;객체들이 할 수 있는 행위 각각에 대한 전략 클래스를 생성하고, 유사한 행위들을 캡슐화하는 인터페이스를 정의하여, 동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것만으로 행위의 수정이 가능하도록 만든 패턴&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의만 봐서는 감이 안 올 수 있어 전략 패턴 적용 구조를 선물박스 삭제 사례와 연관지어 도식화하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cX6DPr/btsIcWuVtAt/xPV7rYW5ZKbYHSzMLwZxK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cX6DPr/btsIcWuVtAt/xPV7rYW5ZKbYHSzMLwZxK1/img.png&quot; data-alt=&quot;전략 패턴 사용 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cX6DPr/btsIcWuVtAt/xPV7rYW5ZKbYHSzMLwZxK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcX6DPr%2FbtsIcWuVtAt%2FxPV7rYW5ZKbYHSzMLwZxK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;633&quot; height=&quot;204&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;전략 패턴 사용 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GiftBoxService의 deleteGiftBox()는 전략 패턴의 &lt;b&gt;클라이언트&lt;/b&gt;로, 전략 객체를 전달하여 전략을 등록하거나 변경하여 전략 알고리즘을 실행한 결과를 얻는다.&lt;/li&gt;
&lt;li&gt;GiftBoxActionProvider는 &lt;b&gt;컨텍스트&lt;/b&gt;로, 알고리즘을 실행해야 할 때마다 해당 알고리즘과 연결된 전략 객체의 메소드를 호출한다.&lt;/li&gt;
&lt;li&gt;GiftBoxStrategy는 전략 구현체에 대한 &lt;b&gt;공용 인터페이스&lt;/b&gt;이며, SenderStrategy와 ReceiverStrategy는 이를 &lt;b&gt;구현한 클래스&lt;/b&gt;들이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GiftBoxActionProvider는 유저 타입에 따라 동적으로 전략을 제공하는데, 유저 타입이 변경되더라도 해당 클래스에만 전략을 추가하고 매핑해도 되기 때문에 시스템의 &lt;b&gt;유연성을 향상&lt;/b&gt;시켜주고, &lt;b&gt;OCP(개방-폐쇄 원칙)을 준수&lt;/b&gt;하는 구조를 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 전략 패턴을 통해 구현 클래스로 책임을 분리하여 &lt;b&gt;단일 책임 원칙을 준수&lt;/b&gt;할 수 있게 되며 이 외에도 합성(composition), 다형성, 캡슐화 등 OOP 기술이 총 집합된 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 프로젝트 코드 개선 과정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkWvHc/btsIeDgkG9j/kIiymKsNM870hKLiMDmlKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkWvHc/btsIeDgkG9j/kIiymKsNM870hKLiMDmlKK/img.png&quot; data-alt=&quot;패키지 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkWvHc/btsIeDgkG9j/kIiymKsNM870hKLiMDmlKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkWvHc%2FbtsIeDgkG9j%2FkIiymKsNM870hKLiMDmlKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;239&quot; height=&quot;232&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;패키지 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 공통 인터페이스를 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1719412137896&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@FunctionalInterface
public interface GiftBoxStrategy {

    void delete(Member member, GiftBox giftBox);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@FunctionalInterface는 Object Class의 메서드를 제외하고 구현해야 할 추상 메서드가 하나만 정의된 인터페이스임을 검증 및 명시해주는 어노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 전략을 분리해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1719412232418&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@Component
public class SenderStrategy implements GiftBoxStrategy {

    private final FileService fileService;
    private final GiftBoxWriter giftBoxWriter;
    private final LetterWriter letterWriter;
    private final PhotoWriter photoWriter;
    private final GiftBoxStickerWriter giftBoxStickerWriter;

    @Override
    public void delete(Member member, GiftBox giftBox) {
        if (giftBox.getDeliverStatus().equals(DeliverStatus.DELIVERED)) {
            giftBox.delete();
        } else if (giftBox.getDeliverStatus().equals(DeliverStatus.WAITING)) {
            letterWriter.delete(giftBox.getLetter());
            giftBox.getPhotos().forEach(photo -&amp;gt; {
                fileService.deleteFile(photo.getImgUrl());
                photoWriter.delete(photo);
            });
            if (giftBox.getGift().getGiftType().equals(GiftType.PHOTO)) {
                fileService.deleteFile(giftBox.getGift().getGiftUrl());
            }
            giftBox.getGiftBoxStickers().forEach(giftBoxStickerWriter::delete);
            giftBoxWriter.delete(giftBox);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GiftBox의 DevliverStatus에 따라 분기하는 코드도 전략 패턴을 적용할 수 있겠지만 ...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) DeliverStatus가 현재 Enum 값 외에 추가될 가능성이 거의 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 전략 패턴을 연속으로 2번 사용할 경우 추상화 레벨이 높아져 혼란스러울 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라는 이유로 우선은 SenderStrategy의 delete 메서드는 추가적인 리팩토링을 진행하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@Component
public class ReceiverStrategy implements GiftBoxStrategy {

    private final ReceiverReader receiverReader;

    @Override
    public void delete(Member member, GiftBox giftBox) {
        Receiver receiver = receiverReader.findByMemberAndGiftBox(member, giftBox);
        receiver.delete();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 ActionProvider는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ActionProvider는 전략 클래스들 중 적절한 클래스를 선택하는 역할을 수행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1719412427202&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class GiftBoxActionProvider {

    private final Map&amp;lt;MemberRole, GiftBoxStrategy&amp;gt; giftBoxActions;

    public GiftBoxActionProvider(
        final SenderStrategy senderStrategy,
        final ReceiverStrategy receiverStrategy
    ) {
        this.giftBoxActions = new EnumMap&amp;lt;&amp;gt;(MemberRole.class);
        this.giftBoxActions.put(MemberRole.SENDER, senderStrategy);
        this.giftBoxActions.put(MemberRole.RECEIVER, receiverStrategy);
    }

    public GiftBoxStrategy getStrategy(MemberRole memberRole) {
        return giftBoxActions.get(memberRole);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다 만든 전략 패턴을 클라이언트에게 적용시키면 ...&lt;/p&gt;
&lt;pre id=&quot;code_1719412499098&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String deleteGiftBox(Long giftBoxId) {
    Long memberId = SecurityUtil.getMemberId();
    Member member = memberReader.findById(memberId);

    GiftBox giftBox = giftBoxReader.findById(giftBoxId);
    MemberRole memberRole = getMemberRole(member, giftBox);

    if (memberRole.equals(MemberRole.STRANGER)) {
        throw new GiftBoxAccessDeniedException();
    }

    final GiftBoxStrategy giftBoxStrategy = giftBoxActionProvider.getStrategy(memberRole);
    giftBoxStrategy.delete(member, giftBox);

    return &quot;선물박스가 삭제되었습니다&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 18줄짜리 로직을 2줄로 줄일 수 있게 되었다 !!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx2TsR/btsIcS0AasN/RpepGdKKcPWf6vJi4Y14A1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx2TsR/btsIcS0AasN/RpepGdKKcPWf6vJi4Y14A1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx2TsR/btsIcS0AasN/RpepGdKKcPWf6vJi4Y14A1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx2TsR%2FbtsIcS0AasN%2FRpepGdKKcPWf6vJi4Y14A1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;340&quot; height=&quot;339&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이전에는 리팩토링을 진행하게 되면 대부분 private 함수로 분리하는 정도로 그쳤기 때문에 하나의 클래스에 코드 줄이 길어지고 위 아래를 스크롤해가며 보아야 해서 불편했다. OOP를 적극적으로 활용하여 클래스 수준으로 코드를 분리하니 훨씬 관리가 쉽고, 이해하기 쉬운 코드가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이번 리팩토링을 계기로 더 많은 디자인 패턴을 공부하고 다음 리팩토링에도 사용해보고 싶어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/292&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://victorydntmd.tistory.com/292&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tjdtls690.github.io/studycontents/java/2023-11-22-strategy_pattern_refactoring/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tjdtls690.github.io/studycontents/java/2023-11-22-strategy_pattern_refactoring/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%84%EB%9E%B5Strategy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%84%EB%9E%B5Strategy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/124</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/124#entry124comment</comments>
      <pubDate>Wed, 26 Jun 2024 23:40:02 +0900</pubDate>
    </item>
    <item>
      <title>[Packy] 선물박스 PK 생성 전략을 TSID로 변경한 이유</title>
      <link>https://leeeeeyeon-dev.tistory.com/123</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 패키 업데이트에 웹 버전을 출시하여 앱을 설치하지 않은 사람도 웹으로 선물박스를 확인할 수 있도록 할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 출시 이전에는 아래 플로우에 따라 카카오톡으로 받은 선물박스를 앱에서만 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트는 선물박스 만들기 API의 응답값으로 받은 선물박스 ID를 사용하여 선물박스를 열 수 있는 딥링크를 생성한다.&lt;/li&gt;
&lt;li&gt;생성된 딥링크이 추가된 카카오톡 템플릿을 생성한다.&lt;/li&gt;
&lt;li&gt;선물박스를 받은 유저는 카카오톡에서 '패키 앱에서 열어보기' 버튼을 눌러 선물박스를 앱에서 확인할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8m3Aa/btsIaEAEp3c/NqmqUqu49z71nrml70rN01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8m3Aa/btsIaEAEp3c/NqmqUqu49z71nrml70rN01/img.png&quot; data-alt=&quot;카카오톡 예시 사진&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8m3Aa/btsIaEAEp3c/NqmqUqu49z71nrml70rN01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8m3Aa%2FbtsIaEAEp3c%2FNqmqUqu49z71nrml70rN01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;152&quot; height=&quot;322&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;카카오톡 예시 사진&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 업데이트에서는 2, 3번 과정에서 딥링크 대신 제작한 웹 링크( &lt;b&gt;packyforyou.com?box={giftBoxId}&lt;/b&gt; )를 넣고, 웹 사이트 내에 디퍼드 링크를 넣어 웹에서 앱으로 유저를 이동시키도록 하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 앱 서비스만 운영할 때는 선물박스 식별 값을 PK 값인 id를 사용하였다. 현재 PK 생성 전략으로는 auto increment 전략을 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 웹 링크에서도 id를 사용하게 된다면 다른 선물박스 id를 쉽게 유추하여 다른 선물박스를 열람할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 선물박스 만들기 API를 만들 때 응답값으로 id와 함께 UUID를 넘기도록 구현해두었기 때문에, giftBoxId로 UUID 값을 사용하고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 문제 상황&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;But . . .&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbGp30/btsIa462cqv/fzaVrKxvbXyT8a4OOu3nek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbGp30/btsIa462cqv/fzaVrKxvbXyT8a4OOu3nek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbGp30/btsIa462cqv/fzaVrKxvbXyT8a4OOu3nek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbGp30%2FbtsIa462cqv%2FfzaVrKxvbXyT8a4OOu3nek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;66&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 개발자님의 개인 사정으로 &lt;b&gt;어플리케이션 단 코드를 수정하지 못한다&lt;/b&gt;는 제약이 발생하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오톡 템플릿에 들어가는 URL은 '딥링크 &amp;rarr; 패키 웹 도메인'으로 변경이 가능하지만, 어플리케이션 &amp;rarr; 카카오톡으로 데이터를 전달하는 것은 id로 구현되어 있기 때문에 이것을 UUID로 변경하지 못하는 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;gt;&amp;gt;&amp;gt;   Mission: 선물박스의 ID 값을&lt;br /&gt;1. 숫자로 이루어져야 함&lt;br /&gt;2. 고유성을 지켜야 함&lt;br /&gt;3. 쉽게 유추할 수 없어야 함&lt;br /&gt;이라는 조건을 지키는 값으로 변경해야 한다 !&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자로 이루어져야 하는 이유는 클라이언트 단 데이터 모델에서 선물박스의 id를 Int 또는 Long으로 받기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 선물박스 만들기 API의 응답값(DTO)에 들어가는 값만 난수로 변경하면 되지 않느냐?라는 생각도 해보았지만 선물박스 생성 이후 단계에서 id 값을 Query Parameter로 다른 API를 호출하기 때문에 서버 단 코드의 수정 양이 어마어마해진다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉! 위 조건을 충족하도록 선물박스 엔티티의 &lt;b&gt;PK 생성 전략&lt;/b&gt;을 수정해야 한다 !!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 PK 생성 전략을 찾아보았고, 그중 내가 최종적으로 선택한 것은 &lt;b&gt;TSID(Time-Sorted Unique Identifier)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 TSID를 선택한 이유는 '유추 불가능하며 숫자로만 구성됨'이 핵심이긴 하지만, TSID는 이뿐만 아니라 대규모 분산 시스템에서 고유성과 성능을 보장할 수 있는 기법이라는 장점도 있기 때문에 이 부분도 함께 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSID는 ULID와 트위터의 스노우 플레이크(snowflake) 아이디어를 결합한 것이므로, 이 2가지에 대해 먼저 간략히 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. TSID&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. ULID&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pv2HG/btsIaDPYYFa/RaywcPVl2rYy92lmHFOtFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pv2HG/btsIaDPYYFa/RaywcPVl2rYy92lmHFOtFk/img.png&quot; data-alt=&quot;ULID 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pv2HG/btsIaDPYYFa/RaywcPVl2rYy92lmHFOtFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpv2HG%2FbtsIaDPYYFa%2FRaywcPVl2rYy92lmHFOtFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;339&quot; height=&quot;130&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ULID 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;48bit의 Timestamp(UNIX Time, 밀리초 단위)와 80bit의 랜덤 문자열로 이루어져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밀리초 단위의 정밀도를 가지며, 밀리초 내에 동시 생성되면 무작위성에 따라 순서가 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ULID(26문자)는 Base32 인코딩을 사용하여 UUID(Base16 인코딩, 36문자)보다 짧은 문자열을 가짐&lt;/li&gt;
&lt;li&gt;특수문자, 대소문자 구분 X&lt;/li&gt;
&lt;li&gt;시간순 정렬 가능&lt;/li&gt;
&lt;li&gt;128bit로 상대적으로 용량이 큼&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 &lt;a title=&quot;ULID 공식 깃허브&quot; href=&quot;https://github.com/ulid/spec&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ULID 공식 깃허브&lt;/a&gt;를 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. 트위터의 스노우 플레이크(snowflake)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초당 6,000개 이상의 트윗이 생성되는 트위터는 고가용성 방식으로 초당 수만 개를 생성할 수 있으며 대략적으로 정렬이 가능한 ID 전략이 필요했고, 이를 위해 스노우 플레이크 기법을 제시하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N9zd6/btsIaFGZCL2/e2CKuZ1BqZhTsH4x4KrpXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N9zd6/btsIaFGZCL2/e2CKuZ1BqZhTsH4x4KrpXk/img.png&quot; data-alt=&quot;출처: https://keeplearning.dev/twitter-snowflake-approach-is-cool-3156f78017cb&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N9zd6/btsIaFGZCL2/e2CKuZ1BqZhTsH4x4KrpXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN9zd6%2FbtsIaFGZCL2%2Fe2CKuZ1BqZhTsH4x4KrpXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;493&quot; height=&quot;214&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://keeplearning.dev/twitter-snowflake-approach-is-cool-3156f78017cb&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sign, Timestamp, Worker Number, Sequence Number 4부분으로 구성되어 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sign (1bit): 음수와 양수를 구별&lt;/li&gt;
&lt;li&gt;Timestamp (41bit): UNIX Time 사용, 밀리초 단위
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;오버 플로우 방지를 위해 시작 시간을 1288834974657(2010년 11월 4일 10시 42분 54초)을 기준으로 함&lt;/li&gt;
&lt;li&gt;최댓값은 69년 (2^41 - 1 = 2199023255551ms)&lt;/li&gt;
&lt;li&gt;시간 순으로 정렬 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Worker Number (10bit): 데이터센터(5bit)와 서버(5bit)를 구분하는 역할
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;각각 32개의 데이터센터와 서버를 지원할 수 있음&lt;/li&gt;
&lt;li&gt;시스템 시작 시 결정되며, 일반적으로 시스템 운영 중에는 바뀌지 않음&lt;/li&gt;
&lt;li&gt;시작 시 Zookeeper를 통해 선택됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Sequence Number(12bit): 각 서버에서 ID를 생성할 때마다 1씩 증가
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;1밀리초가 경과할 때마다 0으로 초기화&lt;/li&gt;
&lt;li&gt;1밀리초 내에 두 개 이상의 ID가 생성되지 않는 한 일반적으로 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 환경에서는 유일한 키를 생성하기 위해 티켓 서버(Ticket Server)를 두는데, 이는 SPOF(Single Point of Failure)라는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스노우플레이크 기법은 Worker Number를 두기 때문에 특정 시스템에 의존한다는 티켓 서버의 문제점을 해결하였으며, 나아가 어떤 데이터센터나 서버에서 생성한 ID인지 알 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초당 생성 가능한 ID 수도 약 4,096,000개로 넉넉하며 타임스탬프를 앞 쪽에 배치하여 시간 순 정렬도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-3. TSID&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vDggN/btsIa6K910f/NsgqyKnqEPiD79EKAd0Pz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vDggN/btsIa6K910f/NsgqyKnqEPiD79EKAd0Pz1/img.png&quot; data-alt=&quot;TSID 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vDggN/btsIa6K910f/NsgqyKnqEPiD79EKAd0Pz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvDggN%2FbtsIa6K910f%2FNsgqyKnqEPiD79EKAd0Pz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;264&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TSID 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSID는 ULID와 스노우플레이크 기법을 결합한 아이디어로, 42bit의 타임스탬프와 22bit의 랜덤 구성 요소로 이루어져있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랜덤 구성 요소는 node와 counter로 이루어져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드는 인스턴스를 식별하는 번호이며, 카운터는 스노우플레이크의 sequence number와 동일하게 각 노드에서 생성할 때마다 1씩 증가하며 밀리초가 지나면 0으로 초기화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드는 기본값이 10bit(1024개)이며, 직접 설정하지 않으면 랜덤한 값으로 설정된다. 노드의 bit 수와 값은 직접 조절할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSID 또한 시간 순 정렬이 가능하며, 최대 69년 사용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Spring Boot에서 TSID 사용하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Spring Boot에서 TSID를 사용하는 방법을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 build.gradle에 의존성을 추가한다. 이때, Hibernate 버전에 따라 다른 버전을 사용하므로 자신에게 맞는 버전을 사용하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에 나오는 코드는 Hibernate 6을 기준으로 작성되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1719310515350&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.7.6'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리를 추가한 후 '@Tsid' 어노테이션을 사용하여 쉽게 사용할 수 있다!&lt;/p&gt;
&lt;pre id=&quot;code_1719310628501&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GiftBox {

    @Id
    @Tsid
    private Long id;
    
    // 중략 ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 노드 bit, epoch 기준 시간 등을 변경하고 싶으면 Supplier&amp;lt;TSID.Factory&amp;gt;를 확장하는 커스텀 클래스를 생성하고, @Tsid 어노테이션의 파라미터로 전달하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 가능한 값들은 TSID.Factory 구현 코드에서 더 확인 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1719310720542&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static class CustomTsidSupplier implements Supplier&amp;lt;TSID.Factory&amp;gt; {
⠀
    @Override
    public TSID.Factory get() {
        return TSID.Factory.builder()
            .withNodeBits(1)
            .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1719310744538&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GiftBox {

    @Id
    @Tsid(CustomTsidSupplier.class)
    private Long id;
    
    // 중략 ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 자바스크립트 관련 주의사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 정수의 표현은 53비트로 제한되어 있어, 아래 콘솔 결과처럼 뒷 숫자의 정확도를 잃을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 생성된 아이디를 &lt;b&gt;문자열&lt;/b&gt;로 다뤄주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7fvuy/btsIbxO5vDg/F6GKgnfz9KkPKYxMERvEz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7fvuy/btsIbxO5vDg/F6GKgnfz9KkPKYxMERvEz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7fvuy/btsIbxO5vDg/F6GKgnfz9KkPKYxMERvEz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7fvuy%2FbtsIbxO5vDg%2FF6GKgnfz9KkPKYxMERvEz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;395&quot; height=&quot;59&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@GeneratedValue의 GenerationType에서 제공하는 기본 키 생성 전략의 경우 특정 DB에 의존적이거나, 성능 이슈가 있고 PK 생성 전략을 직접 설계하기는 막막하였는데 TSID를 사용하여 현재 상황에 맞게 PK 생성 전략을 간편하게 수정할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 서버로 서비스를 운영해왔기 때문에 분산 시스템에서의 고유성 문제는 생각해보지 않은 영역이였는데 이번에 TSID에 대해 조사하며 좋은 인사이트를 얻었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/f4b6a3/tsid-creator&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/f4b6a3/tsid-creator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ssdragon.tistory.com/162&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ssdragon.tistory.com/162&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vladmihalcea.com/tsid-identifier-jpa-hibernate/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://vladmihalcea.com/tsid-identifier-jpa-hibernate/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jeong-pro.tistory.com/251&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jeong-pro.tistory.com/251&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@ssssujini99/%EA%B0%9C%EB%B0%9C-idPK-%EC%A7%81%EC%A0%91%ED%95%A0%EB%8B%B9-%EC%A0%84%EB%9E%B5-Random-UUID-TSID-%EA%B0%81%EA%B0%81-%EB%B9%84%EA%B5%90%EB%B6%84%EC%84%9D#82-tsid-%EC%9D%B4%EC%9A%A9%EC%8B%9C%EC%97%90-%EB%B0%9C%EC%83%9D%ED%95%9C-%EA%B8%B0%EC%96%B5%EC%97%90-%EB%82%A8%EC%95%98%EB%8D%98-%EC%9D%B4%EC%8A%88&quot;&gt;https://velog.io/@ssssujini99/%EA%B0%9C%EB%B0%9C-idPK-%EC%A7%81%EC%A0%91%ED%95%A0%EB%8B%B9-%EC%A0%84%EB%9E%B5-Random-UUID-TSID-%EA%B0%81%EA%B0%81-%EB%B9%84%EA%B5%90%EB%B6%84%EC%84%9D#82-tsid-%EC%9D%B4%EC%9A%A9%EC%8B%9C%EC%97%90-%EB%B0%9C%EC%83%9D%ED%95%9C-%EA%B8%B0%EC%96%B5%EC%97%90-%EB%82%A8%EC%95%98%EB%8D%98-%EC%9D%B4%EC%8A%88&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Database</category>
      <author>짱정연</author>
      <guid isPermaLink="true">https://leeeeeyeon-dev.tistory.com/123</guid>
      <comments>https://leeeeeyeon-dev.tistory.com/123#entry123comment</comments>
      <pubDate>Tue, 25 Jun 2024 19:35:10 +0900</pubDate>
    </item>
  </channel>
</rss>