<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>A steady developer</title>
    <link>https://back-stead.tistory.com/</link>
    <description>하루 한권 책으로 떠나는 개발여행</description>
    <language>ko</language>
    <pubDate>Sun, 7 Jun 2026 23:31:22 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코드기록사</managingEditor>
    <image>
      <title>A steady developer</title>
      <url>https://tistory1.daumcdn.net/tistory/6305081/attach/84da859b59f3471685170d28a4dd404d</url>
      <link>https://back-stead.tistory.com</link>
    </image>
    <item>
      <title>[JAVA] 멀티프로세스&amp;amp;멀티쓰레드란</title>
      <link>https://back-stead.tistory.com/115</link>
      <description>&lt;!-- 책갈피 소제목 --&gt;
&lt;div style=&quot;position: relative; background-color: #ffffff; padding: 0px 25px 0px 60px; border: 1px solid #d9d9d9;&quot;&gt;
&lt;div style=&quot;position: absolute; top: -1px; left: 14px; width: 30px; height: 47px; background-color: #5c636a;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;position: absolute; top: 17px; left: 14px; width: 0; height: 0; border: 15px solid; border-color: transparent transparent #ffffff transparent;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Java 멀티프로세스 &amp;amp; 멀티스레드 완전 정복&lt;/b&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 들어가며 --&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;div style=&quot;padding: 15px 20px; border-radius: 20px 0px; border: 2px solid #d9d9d9; line-height: 1.8;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 백엔드 개발을 하다 보면 멀티스레드, 동시성이라는 말을 자주 접하게 됩니다. 그런데 이런 생각을 한 번쯤 해봤을 것입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;멀티프로세스와 멀티스레드가 뭐가 다른 거지? 그냥 여러 개 동시에 돌리는 거 아닌가?&quot;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 '동시에 여러 작업을 처리'하지만, 메모리 구조와 동작 방식이 완전히 다릅니다. 이 글에서는 기본 개념부터 Java 코드 예제, 그리고 멀티스레드에서 발생하는 실제 문제점까지 차근차근 살펴보겠습니다.&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;1. 프로세스 vs 스레드 기본 개념&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 멀티프로세스 &amp;mdash; 구조, Java 예제, 한계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 멀티스레드 &amp;mdash; 구조, Java 예제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 멀티스레드 문제점 심화 (경쟁 조건, 데드락, 가시성)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 멀티프로세스 vs 멀티스레드 비교 &amp;amp; 선택 기준&lt;/p&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;!-- 1. 기본 개념 --&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;프로세스와 스레드란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티프로세스와 멀티스레드를 이해하려면, 먼저 &lt;b&gt;프로세스(Process)&lt;/b&gt;와 &lt;b&gt;스레드(Thread)&lt;/b&gt;가 무엇인지 알아야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;프로세스 (Process)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제로부터 독립적인 메모리 공간을 할당받은 &lt;b&gt;실행 단위&lt;/b&gt;입니다. 각 프로세스는 Code, Data, Heap, Stack 영역을 독립적으로 가지며, 다른 프로세스의 메모리에 직접 접근할 수 없습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;스레드 (Thread)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 내부에서 실행되는 &lt;b&gt;작업의 단위&lt;/b&gt;입니다. 같은 프로세스 내의 스레드들은 Code, Data, Heap 영역을 &lt;b&gt;공유&lt;/b&gt;하고, Stack만 독립적으로 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메모리 구조 비교&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;&lt;b&gt;영역&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;&lt;b&gt;멀티프로세스&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;&lt;b&gt;멀티스레드&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33%;&quot;&gt;Code 영역&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;각 프로세스 독립&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;스레드 간 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33%;&quot;&gt;Data 영역&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;각 프로세스 독립&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;스레드 간 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33%;&quot;&gt;Heap 영역&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;각 프로세스 독립&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;스레드 간 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33%;&quot;&gt;Stack 영역&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&gt;각 프로세스 독립&lt;/td&gt;
&lt;td style=&quot;width: 33%; text-align: center;&quot;&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;핵심 요약&lt;/b&gt;&lt;br /&gt;프로세스는 완전히 독립된 실행 공간이고, 스레드는 프로세스 안에서 메모리를 나눠 쓰는 실행 흐름입니다. 이 차이가 멀티프로세스와 멀티스레드의 모든 특성 차이로 이어집니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;!-- 2. 멀티프로세스 --&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;멀티프로세스 (Multi-Process)&lt;/h3&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;style1&quot;&gt;&lt;b&gt;실생활 비유&lt;/b&gt;&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;Java에서 새로운 프로세스를 생성할 때는 &lt;b&gt;ProcessBuilder&lt;/b&gt;를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_multiprocess&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

public class MultiProcessExample {

    public static void main(String[] args) throws Exception {

        List&amp;lt;Process&amp;gt; processes = new ArrayList&amp;lt;&amp;gt;();

        // 3개의 독립 프로세스 생성
        for (int i = 0; i &amp;lt; 3; i++) {
            ProcessBuilder pb = new ProcessBuilder(
                &quot;java&quot;, &quot;-cp&quot;, System.getProperty(&quot;java.class.path&quot;),
                &quot;Worker&quot;, String.valueOf(i)
            );
            pb.redirectErrorStream(true);
            Process process = pb.start();
            processes.add(process);

            System.out.println(&quot;프로세스 &quot; + i + &quot; 시작. PID: &quot; + process.pid());
        }

        // 모든 프로세스 종료 대기
        for (Process p : processes) {
            int exitCode = p.waitFor();
            System.out.println(&quot;프로세스 종료. 코드: &quot; + exitCode);
        }
    }
}

// 별도 프로세스에서 실행될 Worker 클래스
class Worker {
    public static void main(String[] args) throws InterruptedException {
        int id = Integer.parseInt(args[0]);
        System.out.println(&quot;Worker-&quot; + id + &quot;: 작업 시작&quot;);
        Thread.sleep(1000);
        System.out.println(&quot;Worker-&quot; + id + &quot;: 작업 완료&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;멀티프로세스의 한계&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로세스 간 데이터를 주고받으려면 IPC(Inter-Process Communication) &amp;mdash; 소켓, 파이프, 공유 메모리 등을 사용해야 합니다. 또한 프로세스 생성 시 메모리를 통째로 복제하므로 &lt;b&gt;생성 비용이 매우 크고&lt;/b&gt;, 컨텍스트 스위칭 시 PCB 전체를 교체해야 해 오버헤드가 큽니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;!-- 3. 멀티스레드 --&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;멀티스레드 (Multi-Thread)&lt;/h3&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;style1&quot;&gt;&lt;b&gt;실생활 비유&lt;/b&gt;&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;&lt;b&gt;방법 1 &amp;mdash; Thread 직접 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_basic_thread&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class BasicThreadExample {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -&amp;gt; {
            for (int i = 0; i &amp;lt; 3; i++) {
                System.out.println(&quot;[Thread-1] 작업 &quot; + i);
            }
        });

        Thread t2 = new Thread(() -&amp;gt; {
            for (int i = 0; i &amp;lt; 3; i++) {
                System.out.println(&quot;[Thread-2] 작업 &quot; + i);
            }
        });

        t1.start(); // start()로 실행 (run() 직접 호출)
        t2.start();

        t1.join();  // t1 종료 대기
        t2.join();  // t2 종료 대기

        System.out.println(&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;&lt;b&gt;방법 2 &amp;mdash; ExecutorService&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_executor&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.concurrent.*;

public class ExecutorExample {

    public static void main(String[] args) throws Exception {

        // 스레드 풀 생성 (CPU 코어 수만큼)
        ExecutorService executor = Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors()
        );

        List&amp;lt;Future&amp;lt;String&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();

        for (int i = 0; i &amp;lt; 5; i++) {
            final int taskId = i;
            Future&amp;lt;String&amp;gt; future = executor.submit(() -&amp;gt; {
                Thread.sleep(500);
                return &quot;Task-&quot; + taskId + &quot; 완료 by &quot; + Thread.currentThread().getName();
            });
            futures.add(future);
        }

        for (Future&amp;lt;String&amp;gt; f : futures) {
            System.out.println(f.get()); // 결과 대기
        }

        executor.shutdown(); // 반드시 종료 호출!
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;실무 팁&lt;/b&gt;&lt;br /&gt;Thread를 직접 생성하는 방식은 학습용으로만 사용하고, 실무에서는 &lt;b&gt;ExecutorService&lt;/b&gt; 또는 Spring의 &lt;b&gt;@Async + ThreadPoolTaskExecutor&lt;/b&gt;를 사용하세요. 스레드 생성&amp;middot;종료 비용을 줄이고 스레드 수를 제어할 수 있습니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;!-- 4. 문제점 심화 --&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;멀티스레드의 문제점&lt;/h3&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;!-- 문제 1 --&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;① 경쟁 조건 (Race Condition)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 스레드가 &lt;b&gt;동시에 같은 데이터를 읽고 쓸 때&lt;/b&gt; 의도치 않은 결과가 발생합니다. &lt;b&gt;count++&lt;/b&gt;는 단순해 보이지만, 실제로는 &lt;b&gt;읽기 &amp;rarr; 증가 &amp;rarr; 쓰기&lt;/b&gt; 3단계 연산입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1487&quot; data-origin-height=&quot;545&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjxHrx/dJMcagkDzNR/64YmGQr0MLqgrSfaI0IPKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjxHrx/dJMcagkDzNR/64YmGQr0MLqgrSfaI0IPKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjxHrx/dJMcagkDzNR/64YmGQr0MLqgrSfaI0IPKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjxHrx%2FdJMcagkDzNR%2F64YmGQr0MLqgrSfaI0IPKK%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;1487&quot; height=&quot;545&quot; data-origin-width=&quot;1487&quot; data-origin-height=&quot;545&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15%; text-align: center;&quot;&gt;&lt;b&gt;시점&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42%; text-align: center;&quot;&gt;&lt;b&gt;Thread-1&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42%; text-align: center;&quot;&gt;&lt;b&gt;Thread-2&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15%; text-align: center;&quot;&gt;T1&lt;/td&gt;
&lt;td style=&quot;width: 42%;&quot;&gt;count 읽음 &amp;rarr; 0&lt;/td&gt;
&lt;td style=&quot;width: 42%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15%; text-align: center;&quot;&gt;T2&lt;/td&gt;
&lt;td style=&quot;width: 42%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 42%;&quot;&gt;count 읽음 &amp;rarr; 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15%; text-align: center;&quot;&gt;T3&lt;/td&gt;
&lt;td style=&quot;width: 42%;&quot;&gt;0+1 = 1 &amp;rarr; count에 씀&lt;/td&gt;
&lt;td style=&quot;width: 42%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15%; text-align: center;&quot;&gt;T4&lt;/td&gt;
&lt;td style=&quot;width: 42%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 42%;&quot;&gt;0+1 = 1 &amp;rarr; count에 씀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15%; text-align: center;&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 85%;&quot; colspan=&quot;2&quot;&gt;count = 1 (기대값: 2) &amp;mdash; 데이터 유실 발생!&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;pre id=&quot;code_race&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.concurrent.atomic.*;

public class RaceConditionDemo {

    static int unsafeCount = 0;                        // 위험
    static AtomicInteger safeCount = new AtomicInteger(0); // 안전

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -&amp;gt; {
            for (int i = 0; i &amp;lt; 1000; i++) safeCount.incrementAndGet();
        });
        Thread t2 = new Thread(() -&amp;gt; {
            for (int i = 0; i &amp;lt; 1000; i++) safeCount.incrementAndGet();
        });

        t1.start(); t2.start();
        t1.join();  t2.join();

        System.out.println(&quot;결과: &quot; + safeCount.get()); // 항상 2000
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;br /&gt;&lt;b&gt;synchronized&lt;/b&gt; 키워드로 임계 구역 보호, &lt;b&gt;AtomicInteger&lt;/b&gt; 등 java.util.concurrent.atomic 패키지 활용, &lt;b&gt;ReentrantLock&lt;/b&gt; 사용&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 문제 2 --&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;② 데드락 (Deadlock)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 스레드가 서로가 가진 락을 기다리며 &lt;b&gt;영원히 멈춰버리는 상태&lt;/b&gt;입니다. 프로그램이 아무 오류 없이 응답만 안 하는 원인 1위입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1257&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oJE2B/dJMcaa5Oyzk/EOmLzSiy8oIKd2hdB4duwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oJE2B/dJMcaa5Oyzk/EOmLzSiy8oIKd2hdB4duwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oJE2B/dJMcaa5Oyzk/EOmLzSiy8oIKd2hdB4duwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoJE2B%2FdJMcaa5Oyzk%2FEOmLzSiy8oIKd2hdB4duwK%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;1257&quot; height=&quot;650&quot; data-origin-width=&quot;1257&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt;Thread-A&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt;Thread-B&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Lock-1 보유 중&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Lock-2 보유 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Lock-2 요청 &amp;rarr; 대기&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Lock-1 요청 &amp;rarr; 대기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot; colspan=&quot;2&quot;&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;pre id=&quot;code_deadlock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DeadlockDemo {
    static final Object lockA = new Object();
    static final Object lockB = new Object();

    // 데드락 발생 코드 &amp;mdash; 락 획득 순서가 반대
    static void badThread1() {
        synchronized (lockA) {
            synchronized (lockB) { }  // lockB 대기 &amp;rarr; 데드락!
        }
    }

    static void badThread2() {
        synchronized (lockB) {
            synchronized (lockA) { }  // lockA 대기 &amp;rarr; 데드락!
        }
    }

    // 해결: 모든 스레드가 동일한 순서로 락 획득
    static void safeThread1() {
        synchronized (lockA) {        // 항상 A &amp;rarr; B 순서
            synchronized (lockB) { }
        }
    }

    static void safeThread2() {
        synchronized (lockA) {        // 동일하게 A &amp;rarr; B 순서
            synchronized (lockB) { }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;br /&gt;&lt;b&gt;락 획득 순서를 모든 스레드에서 동일하게&lt;/b&gt; 유지, &lt;b&gt;tryLock() 타임아웃&lt;/b&gt; 활용, 락 범위 최소화&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;!-- 문제 3 --&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;③ 가시성 문제 (Visibility Problem)&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 CPU 코어는 &lt;b&gt;캐시 메모리&lt;/b&gt;를 가집니다. 한 스레드가 값을 변경해도 다른 스레드의 CPU 캐시에는 아직 이전 값이 남아 있을 수 있어, 변경 사항을 인식하지 못하는 문제입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;673&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b14Irx/dJMcac3A7kf/E4sQFKpkVUIPpOouLnk680/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b14Irx/dJMcac3A7kf/E4sQFKpkVUIPpOouLnk680/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b14Irx/dJMcac3A7kf/E4sQFKpkVUIPpOouLnk680/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb14Irx%2FdJMcac3A7kf%2FE4sQFKpkVUIPpOouLnk680%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;1205&quot; height=&quot;673&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;673&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_volatile&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class VisibilityDemo {

    // volatile 없으면: Thread-1이 변경해도 Thread-2가 인식 못할 수 있음
    private volatile boolean running = true;

    public void start() {
        // Thread-1: running 플래그 모니터링
        Thread worker = new Thread(() -&amp;gt; {
            while (running) {        // volatile로 항상 최신값을 메인 메모리에서 읽음
                // 작업 수행...
            }
            System.out.println(&quot;Worker 종료됨&quot;);
        });

        // Thread-2: 1초 후 종료 신호 전송
        Thread stopper = new Thread(() -&amp;gt; {
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            running = false;         // volatile이므로 메인 메모리에 즉시 반영
            System.out.println(&quot;종료 신호 전송&quot;);
        });

        worker.start();
        stopper.start();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주의: volatile &amp;ne; 동기화&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;volatile&lt;/b&gt;은 가시성만 보장하며, 원자성(atomicity)은 보장하지 않습니다. count++처럼 복합 연산은 여전히 Race Condition이 발생합니다. 복합 연산에는 &lt;b&gt;AtomicInteger&lt;/b&gt;나 &lt;b&gt;synchronized&lt;/b&gt;를 사용해야 합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;!-- 5. 비교 &amp; 선택 --&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;멀티프로세스 vs 멀티스레드 비교&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40%; text-align: center;&quot;&gt;&lt;b&gt;멀티프로세스&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40%; text-align: center;&quot;&gt;&lt;b&gt;멀티스레드&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;메모리 구조&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;독립된 메모리 공간&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;메모리 공유 (Heap, Code)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;생성 비용&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;높음 (메모리 복제)&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;낮음 (스택만 추가)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;컨텍스트 스위칭&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;무거움 (PCB 전체 교체)&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;가벼움 (스택/레지스터만)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;데이터 공유&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;IPC 필요 (소켓, 파이프)&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;직접 공유 &amp;mdash; 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;안정성&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;높음 (독립 &amp;rarr; 충돌 없음)&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;낮음 (하나 죽으면 전체 위험)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;동기화 이슈&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;있음 (Race Condition, Deadlock)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Java 구현&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;ProcessBuilder, Runtime.exec()&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;Thread, ExecutorService, @Async&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;대표 사례&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;Chrome 탭, Nginx worker, MSA&lt;/td&gt;
&lt;td style=&quot;width: 40%;&quot;&gt;웹 서버, DB 커넥션 풀, 채팅 서버&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;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;언제 무엇을 선택할까?&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티프로세스&lt;/b&gt;는 프로세스 하나가 죽어도 전체 서비스가 유지돼야 할 때, 보안상 완전한 격리가 필요할 때, 독립 배포와 스케일아웃이 필요한 MSA 구조에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티스레드&lt;/b&gt;는 I/O 대기 시간 동안 다른 작업을 처리해야 할 때, 빠른 데이터 공유가 필요할 때, 메모리 제약이 있고 가볍게 동시성을 구현해야 할 때 적합합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;!-- 마치며 --&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;멀티프로세스와 멀티스레드는 단순히 &quot;동시에 여러 개를 실행하는 것&quot;이 아니라, 메모리 구조와 자원 공유 방식이 근본적으로 다른 두 가지 접근 방식입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;특히 멀티스레드에서 발생하는 Race Condition, Deadlock, 가시성 문제는 Java 백엔드 면접에서도 자주 나오는 주제이니 각 문제의 원인과 해결책을 함께 이해해두면 좋습니다.&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;</description>
      <category>Java</category>
      <category>java</category>
      <category>스레드</category>
      <category>프로세스</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/115</guid>
      <comments>https://back-stead.tistory.com/115#entry115comment</comments>
      <pubDate>Mon, 30 Mar 2026 01:42:59 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 애너테이션(annotaion)이란?</title>
      <link>https://back-stead.tistory.com/114</link>
      <description>&lt;div style=&quot;position: relative; background-color: #ffffff; padding: 0px 25px 0px 60px; border: 1px solid #d9d9d9;&quot;&gt;
&lt;div style=&quot;position: absolute; top: -1px; left: 14px; width: 30px; height: 47px; background-color: #5c636a;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;position: absolute; top: 17px; left: 14px; width: 0; height: 0; border: 15px solid; border-color: transparent transparent #ffffff transparent;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;애너테이션(annotaion)이란?&lt;/h3&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;div style=&quot;padding: 15px 20px; border-radius: 20px 0px; border: 2px solid #d9d9d9; line-height: 1.8;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이나 NestJS로 개발을 하다 보면 @Controller, @Service, @Transactional 같은 코드를 자연스럽게 쓰게 됩니다. 그런데 문득 이런 생각이 든 적이 있을거라 생각합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 @ 기호는 도대체 뭐고, 어떻게 동작하는 걸까?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 그냥 &quot;붙이면 되는 것&quot;으로 넘어가기 쉽지만, 애노테이션이 내부적으로 어떻게 동작하는지 이해하면 프레임워크를 훨씬 자신 있게 다룰 수 있습니다. 이 글에서는 애노테이션이 무엇인지, 어떤 원리로 동작하는지 차근차근 살펴보겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;애너테이션이란?&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애너테이션은 코드에 &lt;b&gt;메타데이터(metadata)&lt;/b&gt; 를 부여하는 문법입니다. 쉽게 말해, &quot;이 클래스는 컨트롤러야&quot;, &quot;이 메서드는 트랜잭션 안에서 실행해줘&quot; 처럼 코드에 &lt;b&gt;추가적인 정보를 표시하는 라벨&lt;/b&gt; 이라고 생각하면 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 비즈니스 로직을 수행하지는 않지만, 컴파일러나 프레임워크가 이 라벨을 읽고 그에 맞는 동작을 대신 처리해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774542001203&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Controller        // &quot;나는 컨트롤러야&quot;
@RequestMapping(&quot;/users&quot;)
public class UserController {

    @GetMapping(&quot;/{id}&quot;)
    @Transactional   // &quot;나는 트랜잭션 안에서 실행돼야 해&quot;
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;표준 애너테이션&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;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;일반 애너테이션(java.lang)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 218px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 10.4651%; text-align: center; height: 21px;&quot;&gt;&lt;b&gt;에너테이션&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.5349%; text-align: center; height: 21px;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 10.4651%; height: 17px;&quot;&gt;&lt;b&gt; @Override &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.5349%; height: 17px;&quot;&gt;부모 클래스나 인터페이스의 메서드를 재정의할 때 사용. 컴파일러가 올바르게 오버라이딩됐는지 검증해줍니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 10.4651%; height: 17px;&quot;&gt;&lt;b&gt; @Deprecated &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.5349%; height: 17px;&quot;&gt;더 이상 사용을 권장하지 않는 메서드&amp;middot;클래스에 표시. 사용 시 컴파일러가 경고를 발생시킵니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 10.4651%; height: 17px;&quot;&gt;&lt;b&gt; @SuppressWarnings &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.5349%; height: 17px;&quot;&gt;컴파일러 경고를 억제할 때 사용. @SuppressWarnings(&quot;unchecked&quot;) 처럼 억제할 경고 종류를 지정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 10.4651%; height: 17px;&quot;&gt;&lt;b&gt; @SafeVarargs &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.5349%; height: 17px;&quot;&gt;가변인자(varargs)와 제네릭을 함께 사용할 때 타입 안전성을 보장한다고 선언. final 또는 static 메서드에만 사용 가능합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 10.4651%; height: 17px;&quot;&gt;&lt;b&gt; @FunctionalInterface &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.5349%; height: 17px;&quot;&gt;함수형 인터페이스임을 명시. 추상 메서드가 딱 하나여야 하며, 람다식과 함께 사용됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;메타 애너테이션 (java.lang.annotation) &lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 122px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; text-align: center; height: 21px;&quot;&gt;&lt;b&gt;애너테이션&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.3023%; text-align: center; height: 21px;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 21px;&quot;&gt;&lt;b&gt; @Target &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.3023%; height: 21px;&quot;&gt;애너테이션을 적용할 수 있는 위치를 지정. METHOD, FIELD, CLASS 등을 설정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 21px;&quot;&gt;&lt;b&gt; @Documented &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.3023%; height: 21px;&quot;&gt;JavaDoc 생성 시 해당 애너테이션 정보도 문서에 포함되도록 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 21px;&quot;&gt;&lt;b&gt; @Inherited &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.3023%; height: 21px;&quot;&gt;부모 클래스에 붙인 애너테이션을 자식 클래스도 자동으로 상속받도록 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 21px;&quot;&gt;&lt;b&gt; @Retention &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.3023%; height: 21px;&quot;&gt;애너테이션 정보를 어느 시점까지 유지할지 결정. SOURCE &amp;middot; CLASS &amp;middot; RUNTIME 세 가지가 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20.6977%; height: 17px;&quot;&gt;&lt;b&gt; @Repeatable &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.3023%; height: 17px;&quot;&gt;같은 애너테이션을 동일한 대상에 여러 번 반복 적용할 수 있도록 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;@Target&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애너테이션이 적용가능한 대상을 지정하는데 사용된다. 아래는 &lt;b&gt;'@UserAgentHeader'&lt;/b&gt;를 정의한 것인데, 이 애너테이션에 적용할수 있는 대상을 &lt;b&gt;'@Target'&lt;/b&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;404&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MSRk0/dJMcafzgn4O/ZRmgTHAEa0wOyMLGtGPZkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MSRk0/dJMcafzgn4O/ZRmgTHAEa0wOyMLGtGPZkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MSRk0/dJMcafzgn4O/ZRmgTHAEa0wOyMLGtGPZkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMSRk0%2FdJMcafzgn4O%2FZRmgTHAEa0wOyMLGtGPZkK%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;404&quot; height=&quot;131&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;131&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;'@Target'&lt;/b&gt; 으로 지정할수 있는 애너테이션 적용대상의 종류는 아래와 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 187px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;대상 타입&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;ANNOTATION_TYPE&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;애너테이션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;CONSTRUCTOR&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;생성자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;FIELD&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;필드(멤버변수, enum) 상수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;LOCAL_VARIABLE&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;지역변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;METHOD&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;메서드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;PACKAGE&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;패키지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;PARAMETER&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;매개변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;TYPE&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;타입(클래스, 인터페이스, enum)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;TYPE_PARAMETER&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;타입 매개변수(JDK1.8)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;TYPE_USE&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;타입이 사용되는 모든 곳(JDK1.8)&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;동작 원리 - 리플렉션 (Reflection)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애너테이션 자체는 단순한 표시에 불과합니다. 실제로 동작하게 만드는 건 &lt;b&gt;리플렉션(Reflection)&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플렉션이란 런타임 시점에 클래스, 메서드, 필드 등의 정보를 동적으로 읽고 조작할 수 있는 Java의 기능입니다. Spring은 애플리케이션이 시작될 때 리플렉션으로 클래스를 스캔하고, 애너테이션이 붙어 있으면 그에 맞는 처리를 자동으로 수행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774543212045&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Spring이 내부적으로 하는 일 (단순화된 예시)
Class&amp;lt;?&amp;gt; clazz = UserController.class;

if (clazz.isAnnotationPresent(Controller.class)) {
    // 컨트롤러로 등록
    registerAsController(clazz);
}

for (Method method : clazz.getDeclaredMethods()) {
    if (method.isAnnotationPresent(Transactional.class)) {
        // 트랜잭션 프록시 적용
        applyTransactionProxy(method);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;커스텀 애너테이션 만들기&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #ee2323;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주의 사항&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;애너테이션은 상속이 허용되지 않으므로 아래와 같이 명시적으로 Annotation을 조상으로 지정할 수 없다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774543498903&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@interface TestInfo extends Annotation {
    int    count();
    String testedBy();
}&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_1774543542287&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 애너테이션 정의
@Target(ElementType.METHOD)       // 메서드에만 붙일 수 있음
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 유지
public @interface LoginRequired {
}

// 2. AOP로 처리 로직 작성
@Aspect
@Component
public class LoginAspect {

    @Before(&quot;@annotation(LoginRequired)&quot;)
    public void checkLogin(JoinPoint joinPoint) {
        // 로그인 여부 확인 로직
        if (!isLoggedIn()) {
            throw new UnauthorizedException(&quot;로그인이 필요합니다.&quot;);
        }
    }
}

// 3. 사용
@GetMapping(&quot;/mypage&quot;)
@LoginRequired  // 이 메서드는 로그인한 사용자만 접근 가능
public MyPage getMyPage() {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;애너테이션 요소 규칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애너테이션의 요소를 선언할 때 반드시 지켜야 하는 규칙은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;- 요소의 타입은 기본형, String,enum, 애너테이션, Class만 허용된다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- ()안에 매개변수를 선언할 수 없다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- 예외를 선언할 수 없다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- 요소를 타입 매개변수로 정의할 수 없다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1774543735440&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@interface BadAnnotation {
    // 허용되지 않는 타입 (List는 사용 불가)
    List&amp;lt;String&amp;gt; tags();

    // 매개변수 선언 불가
    String name(String prefix);

    // 예외 선언 불가
    int count() throws Exception;

    // 타입 매개변수 사용 불가
    &amp;lt;T&amp;gt; Class&amp;lt;T&amp;gt; element();
}

@interface GoodAnnotation {
    // 기본형
    int count();

    // String
    String name();

    // enum
    RetentionPolicy policy();

    // 애너테이션
    Target target();

    // Class
    Class&amp;lt;?&amp;gt; element();
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;애너테이션은 단순히 &quot;붙이면 동작하는 것&quot;이 아니라, 리플렉션을 통해 프레임워크가 런타임에 읽고 처리하는 구조입니다. 이 원리를 이해하면 @Transactional이 왜 private 메서드에서 동작하지 않는지, @Autowired가 어떻게 자동으로 주입되는지 같은 질문에도 스스로 답을 찾을 수 있게 됩니다.&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;</description>
      <category>Java</category>
      <category>스프링</category>
      <category>애너테이션</category>
      <category>자바</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/114</guid>
      <comments>https://back-stead.tistory.com/114#entry114comment</comments>
      <pubDate>Fri, 27 Mar 2026 01:50:14 +0900</pubDate>
    </item>
    <item>
      <title>[성능 개선] Redis + RabbitMQ 이벤트 기반을 통핸 초대권 시스템 개선 By UMF 2025 초대권</title>
      <link>https://back-stead.tistory.com/113</link>
      <description>&lt;div style=&quot;position: relative; background-color: #ffffff; padding: 0px 25px 0px 60px; border: 1px solid #d9d9d9;&quot;&gt;
&lt;div style=&quot;position: absolute; top: -1px; left: 14px; width: 30px; height: 47px; background-color: #5c636a;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;position: absolute; top: 17px; left: 14px; width: 0; height: 0; border: 15px solid; border-color: transparent transparent #ffffff transparent;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;ldquo;Redis-Lua + RabbitMQ로 초대권 트랜잭션 병목을 끊다 &amp;mdash; 178% 성능 개선 사례&amp;rdquo;&lt;/h3&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;div style=&quot;padding: 15px 20px; border-radius: 20px 0px; border: 2px solid #d9d9d9; line-height: 1.8;&quot;&gt;
&lt;p data-end=&quot;327&quot; data-start=&quot;133&quot; data-ke-size=&quot;size16&quot;&gt;다가오는 &lt;b&gt;UMF 2025(EDM 페스티벌)&lt;/b&gt; 를 앞두고, 우리는 &amp;lsquo;&lt;b&gt;초대권(Invitation)&lt;/b&gt;&amp;rsquo; 기능을 새로 설계&amp;middot;개발했습니다. 행사 특성상 &lt;b&gt;수천 명 사용자가 특정 시점에 동시 입장&lt;/b&gt;하는 초고부하 상황이 예측되었고, 실제로 &lt;b&gt;3,000장 이상&lt;/b&gt;의 초대권이 발급되었습니다. 문제는 &lt;b&gt;입장 시점에 요청이 한꺼번에 몰린다&lt;/b&gt;는 점이었습니다.&lt;/p&gt;
&lt;p data-end=&quot;549&quot; data-start=&quot;329&quot; data-ke-size=&quot;size16&quot;&gt;기존 아키텍처는 이 트래픽을 감당하지 못했습니다. &lt;b&gt;동시 60건&lt;/b&gt;을 넘기면서부터 서비스가 불안정해졌고, &lt;b&gt;DB Connection Pool이 고갈&lt;/b&gt;되어 이후 요청이 처리되지 않는 상황이 반복되었습니다. &lt;b&gt;MSA 구조&lt;/b&gt;였음에도 내부적으로 &lt;b&gt;블록체인 기록 로직을 트랜잭션 안에서 동기적으로 실행&lt;/b&gt;하는 설계 때문에, &lt;b&gt;트랜잭션 경합과 DB 잠금이 누적&lt;/b&gt;된 것이 근본 원인이었습니다.&lt;/p&gt;
&lt;p data-end=&quot;743&quot; data-start=&quot;551&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해, 우리는 &lt;b&gt;Redis 이벤트 기반 트랜잭션 제어&lt;/b&gt;(원자 연산 + 대기열/디스패치)와 &lt;b&gt;(선택적으로) RabbitMQ를 활용한 후속 처리 비동기화&lt;/b&gt;를 도입했습니다. 핵심 아이디어는 단순합니다.&lt;br /&gt;&lt;b&gt;&amp;ldquo;선점/가용성 판단은 Redis의 원자 연산으로 초저지연 처리하고, 무거운 후속 로직은 트랜잭션 밖으로 분리한다.&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;819&quot; data-start=&quot;745&quot; data-ke-size=&quot;size16&quot;&gt;그 결과, &lt;b&gt;초당 약 70TPS 수준이던 처리량이 3,000TPS 이상 (약 42.9배) &lt;/b&gt;증가했고, &lt;b&gt;안정적인 처리율&lt;/b&gt;을 달성했습니다.&lt;/p&gt;
&lt;p data-end=&quot;819&quot; data-start=&quot;745&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;819&quot; data-start=&quot;745&quot; data-ke-size=&quot;size16&quot;&gt;이후 글에서는 Redis를 중심으로 트랜잭션 병목을 제거하고, 안정적인 초고부하 처리를 가능하게 만든 구조적 개선 과정을 상세히 다뤄보겠습니다&lt;/p&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;1. 블록체인 트랜잭션의 특성과 병목 원인&lt;/h3&gt;
&lt;p data-end=&quot;347&quot; data-start=&quot;180&quot; data-ke-size=&quot;size16&quot;&gt;블록체인 시스템의 트랜잭션은 우리가 흔히 사용하는 데이터베이스(DB) 트랜잭션과는 다릅니다.&lt;br /&gt;DB 트랜잭션은 하나의 커넥션 안에서 여러 작업을 묶어 원자적으로 처리하는 개념이지만, 블록체인의 트랜잭션은 &lt;b&gt;&amp;ldquo;계정(Account)&amp;rdquo;을 통해 네트워크에 전송되는 서명된 실행 단위&lt;/b&gt;를 의미합니다.&lt;/p&gt;
&lt;p data-end=&quot;440&quot; data-start=&quot;349&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 차이점은, &lt;b&gt;하나의 계정(EOA, Externally Owned Account)&lt;/b&gt; 은 &lt;b&gt;동시에 여러 트랜잭션을 보낼 수 없다는 점&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;617&quot; data-start=&quot;442&quot; data-ke-size=&quot;size16&quot;&gt;그 이유는 블록체인 네트워크가 &lt;b&gt;트랜잭션의 순서(Nonce)&lt;/b&gt; 를 기반으로 동작하기 때문입니다.&lt;br /&gt;각 계정은 트랜잭션을 보낼 때마다 nonce 값을 1씩 증가시키며, 이 값이 겹치거나 순서가 어긋나면 네트워크는 해당 요청을 &lt;b&gt;거부(revert)&lt;/b&gt; 하거나 &lt;b&gt;대기(pending)&lt;/b&gt; 상태로 둡니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;트랜잭션 실패.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P4jCy/dJMb9QyxTa5/IIIuAa9Unto442cLuUlzKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P4jCy/dJMb9QyxTa5/IIIuAa9Unto442cLuUlzKk/img.png&quot; data-alt=&quot;1-1 동시 요청 시 실패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P4jCy/dJMb9QyxTa5/IIIuAa9Unto442cLuUlzKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP4jCy%2FdJMb9QyxTa5%2FIIIuAa9Unto442cLuUlzKk%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;768&quot; height=&quot;273&quot; data-filename=&quot;트랜잭션 실패.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1-1 동시 요청 시 실패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;623&quot; data-start=&quot;619&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;794&quot; data-start=&quot;624&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;670&quot; data-start=&quot;624&quot;&gt;같은 계정에서 여러 트랜잭션을 동시에 보내면 &amp;rarr; &lt;b&gt;Nonce 충돌&lt;/b&gt; 발생&lt;/li&gt;
&lt;li data-end=&quot;719&quot; data-start=&quot;671&quot;&gt;Nonce 충돌이 나면 &amp;rarr; &lt;b&gt;이전 트랜잭션이 처리될 때까지 네트워크가 대기&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;794&quot; data-start=&quot;720&quot;&gt;결국 서버 입장에서는 여러 요청이 동시에 들어와도, &lt;b&gt;실제 블록체인 네트워크에서는 순차(Sequential) 처리&lt;/b&gt;가 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;트랜잭션실패.png&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RVC5A/dJMb9hW8FGk/VgIvCqG3Sn0jiJ94VNAggK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RVC5A/dJMb9hW8FGk/VgIvCqG3Sn0jiJ94VNAggK/img.png&quot; data-alt=&quot;1-2 트랜잭션 실패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RVC5A/dJMb9hW8FGk/VgIvCqG3Sn0jiJ94VNAggK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRVC5A%2FdJMb9hW8FGk%2FVgIvCqG3Sn0jiJ94VNAggK%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;487&quot; height=&quot;485&quot; data-filename=&quot;트랜잭션실패.png&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1-2 트랜잭션 실패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;925&quot; data-start=&quot;796&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이더리움 메인넷은 한 트랜잭션이 블록에 포함되기까지 평균 &lt;b&gt;약 8초&lt;/b&gt;, 빠른 체인에서도 &lt;b&gt;1~2초&lt;/b&gt; 정도가 소요됩니다.&lt;br /&gt;이 시간 동안은 동일한 계정에서 새로운 트랜잭션을 전송할 수 없는 상태가 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;992&quot; data-start=&quot;927&quot; 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-end=&quot;1168&quot; data-start=&quot;994&quot; data-ke-size=&quot;size16&quot;&gt;특히 우리 시스템은 &lt;b&gt;모든 발급 및 입장 트랜잭션을 단일 Pool 계정&lt;/b&gt;을 통해 처리하고 있었기 때문에,&lt;br /&gt;블록체인 측에서는 트랜잭션 대기로 인한 지연이 발생하고, 서버 측에서는 그동안 &lt;b&gt;DB 트랜잭션이 점유되어 Connection Pool이 고갈&lt;/b&gt;되는 &lt;b&gt;연쇄적인 병목 현상&lt;/b&gt;이 발생했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;2. 기존 아키텍처 구조 와 문제점&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-removebg-preview.png&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/payK5/dJMb9dAq7I9/suDFSIkLcQT0VaEZ6KUxZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/payK5/dJMb9dAq7I9/suDFSIkLcQT0VaEZ6KUxZk/img.png&quot; data-alt=&quot;1.1 기존 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/payK5/dJMb9dAq7I9/suDFSIkLcQT0VaEZ6KUxZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpayK5%2FdJMb9dAq7I9%2FsuDFSIkLcQT0VaEZ6KUxZk%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;648&quot; height=&quot;365&quot; data-filename=&quot;image-removebg-preview.png&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1.1 기존 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;286&quot; data-start=&quot;174&quot; data-ke-size=&quot;size16&quot;&gt;기존 아키텍처에서 관리자가 초대권 발행 요청을 70개 보냈다고 가정해보겠습니다.&lt;br /&gt;서비스는 데이터베이스에 저장된 단 &lt;b&gt;2개의 EOA(Ethereum 계정)&lt;/b&gt; 를 사용해 트랜잭션을 처리했습니다.&lt;/p&gt;
&lt;p data-end=&quot;320&quot; data-start=&quot;288&quot; data-ke-size=&quot;size16&quot;&gt;요청이 들어오면, 서버는 다음과 같은 순서로 동작했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;509&quot; data-start=&quot;322&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;345&quot; data-start=&quot;322&quot;&gt;DB에서 사용 가능한 계정을 조회&lt;/li&gt;
&lt;li data-end=&quot;397&quot; data-start=&quot;346&quot;&gt;&lt;b&gt;비관적 락(SELECT ... FOR UPDATE)&lt;/b&gt; 을 걸어 계정을 선점&lt;/li&gt;
&lt;li data-end=&quot;434&quot; data-start=&quot;398&quot;&gt;선점에 실패한 요청은 재시도하며 동일한 쿼리를 반복 실행&lt;/li&gt;
&lt;li data-end=&quot;509&quot; data-start=&quot;435&quot;&gt;선점이 완료된 요청은 해당 계정으로 트랜잭션 오브젝트를 생성하고 이더리움 네트워크(EVM)에 전송하여 초대권을 발행&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;587&quot; data-start=&quot;511&quot; data-ke-size=&quot;size16&quot;&gt;이 구조는 동시 요청이 적은 환경에서는 큰 문제가 없었습니다.&lt;br /&gt;하지만 요청이 동시에 몰릴 경우, 심각한 성능 저하를 일으켰습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;610&quot; data-start=&quot;594&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;비관적 락의 문제&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;764&quot; data-start=&quot;612&quot; data-ke-size=&quot;size16&quot;&gt;SELECT ... FOR UPDATE 쿼리는 &lt;b&gt;DB 자원을 가장 많이 사용하는 쿼리 중 하나&lt;/b&gt;입니다.&lt;br /&gt;락을 걸어 다른 트랜잭션의 접근을 막기 때문에, 락이 해제되기 전까지는 동일한 행(row)에 접근하는 모든 요청이 &lt;b&gt;대기 상태&lt;/b&gt;로 머무르게 됩니다. 위 예시에서 70개의 요청 중 2개만 계정을 선점하고, 나머지 &lt;b&gt;68개 요청은 선점을 위해 반복적으로 재시도&lt;/b&gt;합니다.&lt;br /&gt;이 과정에서 DB는 락 대기 상태를 유지하며 Connection Pool의 커넥션을 점점 소모하게 됩니다. 결국 일정 수 이상의 요청이 쌓이면 Connection Pool이 가득 차서 &lt;b&gt;그 이후의 요청은 더 이상 처리되지 않는 병목 현상&lt;/b&gt;이 발생합니다.&lt;/p&gt;
&lt;h3 data-end=&quot;1012&quot; data-start=&quot;993&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;체결 지연과 누적 부하&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1097&quot; data-start=&quot;1014&quot; data-ke-size=&quot;size16&quot;&gt;블록체인 트랜잭션은 평균적으로 약 &lt;b&gt;8초(이더리움 기준)&lt;/b&gt; 가 소요되며, 이 시간 동안 해당 계정은 새로운 트랜잭션을 처리할 수 없다고 앞에서 설명한 바있습니다. 즉, 트랜잭션이 체결되기 전까지 락이 유지되며, 남은 68개의 요청은 계속해서 &lt;b&gt;&amp;ldquo;계정 선점&amp;rdquo; 쿼리를 재시도&lt;/b&gt;하게 됩니다. 만약 요청이 초당 5회씩 재시도된다면, 68 * 5 = 340 회의 DB 쿼리가 매초 발생하게 됩니다.이는 단일 서비스 인스턴스 수준에서도 DB 부하를 폭발적으로 증가시키는 수준입니다.&lt;/p&gt;
&lt;p data-end=&quot;1097&quot; data-start=&quot;1014&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1317&quot; data-start=&quot;1292&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;단순한 확장으로는 해결되지 않는다&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1384&quot; data-start=&quot;1319&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 피하기 위한 단순한 방법 으로는 &lt;b&gt;계정을 10개 .. 200개 그 이상으로 늘리는 것&lt;/b&gt;입니다. 하지만 이 방식은 요청이 적을 때 &lt;b&gt;불필요한 리소스 낭비&lt;/b&gt;를 초래합니다. 또한 계정이 많아질수록 관리 비용이 증가하고, 보안&amp;middot;트랜잭션 관리의 복잡도도 함께 커집니다.&lt;/p&gt;
&lt;p data-end=&quot;1384&quot; data-start=&quot;1319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1508&quot; data-start=&quot;1491&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;구조적 한계&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1585&quot; data-start=&quot;1510&quot; data-ke-size=&quot;size16&quot;&gt;결국 이 구조는 &lt;b&gt;동시에 많은 사용자가 초대권을 발급받는 상황(예: 대규모 행사 입장)&lt;/b&gt; 에서는확실히 한계를 드러냈습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1625&quot; data-start=&quot;1586&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1597&quot; data-start=&quot;1586&quot;&gt;DB 락 경합&lt;/li&gt;
&lt;li data-end=&quot;1610&quot; data-start=&quot;1598&quot;&gt;커넥션 풀 고갈&lt;/li&gt;
&lt;li data-end=&quot;1625&quot; data-start=&quot;1611&quot;&gt;트랜잭션 지연 누적&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1688&quot; data-start=&quot;1627&quot; data-ke-size=&quot;size16&quot;&gt;이 세 가지 문제는 시스템 전반의 처리 속도를 떨어뜨리고, 서비스 중단으로 이어질 가능성이 높았습니다. 이러한 이유로, 이번 UMF 행사를 대비하여 우리는 기존 구조를 &lt;b&gt;이벤트 기반의 트랜잭션 처리 구조&lt;/b&gt;로 개선하기로 결정했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;3. 개선 방향 및 적용한 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1491&quot; data-end=&quot;1508&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. DLQ + Back-Off 적용 - 사다리형 구조&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-removebg-preview (2).png&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eHLr15/dJMb9NaKkna/WELismsBabpY1Ec63PkH3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eHLr15/dJMb9NaKkna/WELismsBabpY1Ec63PkH3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eHLr15/dJMb9NaKkna/WELismsBabpY1Ec63PkH3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeHLr15%2FdJMb9NaKkna%2FWELismsBabpY1Ec63PkH3K%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;869&quot; height=&quot;287&quot; data-filename=&quot;image-removebg-preview (2).png&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;467&quot; data-start=&quot;311&quot; data-ke-size=&quot;size16&quot;&gt;기존 시스템에서는 요청이 실패하면 단순히 &amp;ldquo;재시도&amp;rdquo;만 반복했습니다. 하지만 트랜잭션이 블록체인 체결을 기다리는 동안(평균 8초 이상), 서버는 여전히 Connection Pool을 점유하고 있었죠. 결국 &amp;ldquo;일시적 실패&amp;rdquo;조차 시스템 전체의 병목으로 번지게 되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;483&quot; data-start=&quot;469&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하려면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;554&quot; data-start=&quot;484&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;517&quot; data-start=&quot;484&quot;&gt;&lt;b&gt;실패를 즉시 재시도하지 않고 지연시켜야 하고&lt;/b&gt;,&lt;/li&gt;
&lt;li data-end=&quot;554&quot; data-start=&quot;518&quot;&gt;&lt;b&gt;계속 실패하는 요청은 다른 큐로 분리&lt;/b&gt;해야 했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;634&quot; data-start=&quot;556&quot; data-ke-size=&quot;size16&quot;&gt;이 두 가지 조건을 동시에 만족시키는 구조가 바로 &lt;b&gt;DLQ(Dead Letter Queue)&lt;/b&gt; 와 &lt;b&gt;Back-Off&lt;/b&gt; 의 조합입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;397&quot; data-start=&quot;366&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;u&gt;&lt;b&gt;DLQ(Dead Letter Queue)란?&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;464&quot; data-start=&quot;399&quot; data-ke-size=&quot;size16&quot;&gt;DLQ는 메시지 큐 시스템에서 &lt;b&gt;정상적으로 처리되지 못한 메시지를 별도로 보관하는 큐&lt;/b&gt;를 의미합니다. 즉,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;536&quot; data-start=&quot;465&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;505&quot; data-start=&quot;465&quot;&gt;컨슈머(Consumer)가 메시지를 처리하다가 예외가 발생하거나,&lt;/li&gt;
&lt;li data-end=&quot;536&quot; data-start=&quot;506&quot;&gt;재시도 횟수를 초과해도 성공하지 못한 메시지들을&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;612&quot; data-start=&quot;538&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉시 폐기하지 않고 DLQ에 보관&lt;/b&gt;함으로써,이후 운영자가 해당 메시지를 수동으로 확인하거나 재처리할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-end=&quot;716&quot; data-start=&quot;614&quot; data-ke-size=&quot;size16&quot;&gt;이 방식은 단순히 실패를 무시하지 않고, &amp;ldquo;실패한 이벤트도 추적 가능한 데이터로 관리&amp;rdquo;할 수 있게 해주므로 &lt;b&gt;대규모 비동기 처리 시스템의 안정성을 높이는 핵심 요소&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;716&quot; data-start=&quot;614&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;744&quot; data-start=&quot;723&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Back-Off 재시도란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;881&quot; data-start=&quot;746&quot; data-ke-size=&quot;size16&quot;&gt;Back-Off은 요청이 실패했을 때 &lt;b&gt;일정한 지연 시간을 두고 재시도하는 전략&lt;/b&gt;입니다.&lt;br /&gt;단순히 즉시 재시도하는 대신, 실패가 반복될수록 &lt;b&gt;재시도 간격을 점차 늘려주는(Exponential Back-Off)&lt;/b&gt; 방식을 적용합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 80.4651%; height: 137px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px; text-align: center;&quot;&gt;시도횟수&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px; text-align: center;&quot;&gt;대기 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;1 회차&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;1초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;2 회차&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;2초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 16px; text-align: center;&quot;&gt;3 회차&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 16px; text-align: center;&quot;&gt;5초&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-end=&quot;178&quot; data-start=&quot;153&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;이벤트 기반 재시도 구조 적용 및 결과&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;238&quot; data-start=&quot;180&quot; data-ke-size=&quot;size16&quot;&gt;이러한 DLQ + Back-Off 방식을 실제 시스템에 적용하자, 요청 흐름이 명확히 구분되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;443&quot; data-start=&quot;240&quot; data-ke-size=&quot;size16&quot;&gt;요청이 처음 들어왔을 때 &lt;b&gt;계정을 성공적으로 선점한 요청&lt;/b&gt;은 정상적으로 초대권 발행이 진행됩니다. 반면, &lt;b&gt;계정을 선점하지 못한 나머지 68개의 요청&lt;/b&gt;은 이전처럼 무한히 반복 재시도를 하지 않습니다. 대신, 이 요청들은 &lt;b&gt;다음 큐(Queue)&lt;/b&gt; 로 메시지를 전달하며,&lt;br /&gt;각 메시지에는 &lt;b&gt;랜덤한 Back-Off 지연 시간&lt;/b&gt;이 함께 설정됩니다. 이 지연 시간 동안 요청은 잠시 대기하고, 대기 후 다시 &amp;ldquo;계정 선점 실행 큐&amp;rdquo;로 메시지를 재발행하여 &lt;b&gt;쿼리 부하를 분산&lt;/b&gt;시켰습니다. 즉, 모든 요청이 동시에 DB를 두드리는 대신, &lt;b&gt;시간 차를 두어 안정적인 재시도&lt;/b&gt;가 가능해졌습니다. 또한, &lt;b&gt;지정된 재시도 횟수를 초과한 메시지&lt;/b&gt;는 무한 루프에 빠지지 않도록 &lt;b&gt;DLQ(Dead Letter Queue)&lt;/b&gt; 로 이동시켰습니다. 이 DLQ에 적재된 메시지는 추후 운영자가 &lt;b&gt;실패한 이벤트를 트래킹하고 재처리&lt;/b&gt;할 수 있도록 설계했습니다.&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;758&quot; data-start=&quot;742&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여전히 남은 문제&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;822&quot; data-start=&quot;760&quot; data-ke-size=&quot;size16&quot;&gt;물론 완벽한 해결책은 아니었습니다. 이 구조는 안정성을 크게 높였지만, 여전히 몇 가지 한계가 존재했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1028&quot; data-start=&quot;824&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;926&quot; data-start=&quot;824&quot;&gt;Back-Off가 적용되면서 요청들이 &lt;b&gt;일정 시간 대기 상태&lt;/b&gt;에 머물게 되었고, 서버 내부적으로는 &lt;b&gt;대기 중인 인터벌(Interval) 처리 부담&lt;/b&gt;이 남았습니다.&lt;/li&gt;
&lt;li data-end=&quot;1028&quot; data-start=&quot;927&quot;&gt;트래픽이 매우 많을 경우, 랜덤한 Back-Off를 적용하더라도 &lt;b&gt;대기 시간이 겹치는 구간&lt;/b&gt;이 발생해, 순간적인 &lt;b&gt;동시성 스파이크&lt;/b&gt;가 완전히 사라지지는 않았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1134&quot; data-start=&quot;1030&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로, 이 개선을 통해 &lt;b&gt;동시 70건의 요청을 약 120건까지 확장&lt;/b&gt;할 수 있었지만, 여전히 우리가 목표로 한 &lt;b&gt;수천 TPS 수준의 처리량&lt;/b&gt;에는 미치지 못했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-end=&quot;1134&quot; data-start=&quot;1030&quot; data-ke-size=&quot;size26&quot;&gt;2. Redis + Lua 스크립트를 활용한 이벤트 기반 확장&lt;/h2&gt;
&lt;p data-end=&quot;466&quot; data-start=&quot;259&quot; data-ke-size=&quot;size16&quot;&gt;DLQ와 Back-Off 구조를 통해 재시도 안정성은 확보했지만, 여전히 &lt;b&gt;DB 락(SELECT ... FOR UPDATE)&lt;/b&gt; 이 전체 성능의 병목으로 남아 있었습니다. 트랜잭션이 블록체인에 체결되는 동안(평균 8초 이상) DB 커넥션이 점유되고 있었고, 이는 Connection Pool을 빠르게 소진시키며 TPS 확장을 가로막는 주요 원인이었습니다.&lt;/p&gt;
&lt;p data-end=&quot;630&quot; data-start=&quot;468&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해, DLQ의 메시지 흐름 구조를 그대로 유지하면서 **Redis를 활용한 &amp;ldquo;사다리 타기형 구조&amp;rdquo;**를 구상했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;동기화-removebg-preview (1).png&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/piYlJ/dJMcahWZauz/FlaHPpOXFJsYNeELMOWH2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/piYlJ/dJMcahWZauz/FlaHPpOXFJsYNeELMOWH2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/piYlJ/dJMcahWZauz/FlaHPpOXFJsYNeELMOWH2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpiYlJ%2FdJMcahWZauz%2FFlaHPpOXFJsYNeELMOWH2K%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;562&quot; height=&quot;233&quot; data-filename=&quot;동기화-removebg-preview (1).png&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;233&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;630&quot; data-start=&quot;468&quot; data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 단순합니다. &lt;b&gt;DB의 계정 선점을 Redis와 동기화하고&lt;/b&gt;, 계정 가용 상태를 &lt;b&gt;캐시 기반으로 제어&lt;/b&gt;하는 것입니다.&lt;/p&gt;
&lt;h3 data-end=&quot;663&quot; data-start=&quot;637&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Redis를 활용한 캐시 기반 계정 선점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;813&quot; data-start=&quot;665&quot; data-ke-size=&quot;size16&quot;&gt;Redis를 사용하면 매번 DB에 접근하지 않고도 계정의 가용 여부를 즉시 확인할 수 있습니다. 예를 들어, 계정을 선점할 때는 Redis에 +1을 수행하고, 사용을 완료하면 -1을 수행하여 DB의 상태와 Redis의 캐시를 동기화할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;833&quot; data-start=&quot;815&quot; data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은 명확합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;928&quot; data-start=&quot;834&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;856&quot; data-start=&quot;834&quot;&gt;불필요한 DB 락 경쟁이 사라지고&lt;/li&gt;
&lt;li data-end=&quot;893&quot; data-start=&quot;857&quot;&gt;계정 선점 및 해제가 &lt;b&gt;메모리 기반 연산으로 전환&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;계정 선점.png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX4uDE/dJMcaaDyDLE/sg2Wq9Wek1TkvfaV5s0KC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX4uDE/dJMcaaDyDLE/sg2Wq9Wek1TkvfaV5s0KC0/img.png&quot; data-alt=&quot;계정 선점 프로세스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX4uDE/dJMcaaDyDLE/sg2Wq9Wek1TkvfaV5s0KC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX4uDE%2FdJMcaaDyDLE%2Fsg2Wq9Wek1TkvfaV5s0KC0%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;878&quot; height=&quot;486&quot; data-filename=&quot;계정 선점.png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;계정 선점 프로세스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;958&quot; data-start=&quot;935&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Lua 스크립트를 통한 원자적 제어&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1073&quot; data-start=&quot;960&quot; data-ke-size=&quot;size16&quot;&gt;Redis의 Lua 스크립트를 활용하면, &amp;ldquo;가용 슬롯 증가 &amp;rarr; 대기열 확인 &amp;rarr; 대기 중 메시지를 깨워 실행 큐로 이동&amp;rdquo; 같은 일련의 과정을 &lt;b&gt;단일 트랜잭션처럼 원자적으로 처리&lt;/b&gt;할 수 있습니다. 즉, 여러 요청이 동시에 동일한 키를 조작하더라도&lt;br /&gt;레이스 컨디션(race condition)이 발생하지 않습니다. 이 구조는 단순한 캐시 제어를 넘어, &lt;b&gt;Redis가 분산 환경에서의 락 관리자 역할&lt;/b&gt;을 수행하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1236&quot; data-start=&quot;1211&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;세마포어(Semaphore) 방식 적용&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1381&quot; data-start=&quot;1238&quot; data-ke-size=&quot;size16&quot;&gt;여기에 &lt;b&gt;세마포어(Semaphore)&lt;/b&gt; 개념을 결합했습니다. 세마포어란 한정된 자원의 접근 허용 개수를 제어하는 동기화 기법으로,&lt;br /&gt;자원 접근 시 카운트를 감소시키고, 해제 시 다시 증가시켜 &lt;b&gt;동시에 접근 가능한 프로세스 수를 제어&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-end=&quot;1492&quot; data-start=&quot;1383&quot; data-ke-size=&quot;size16&quot;&gt;Redis를 기반으로 세마포어를 구현하면, 여러 서버 인스턴스가 동시에 Redis를 공유하더라도&lt;br /&gt;&lt;b&gt;전역 단위의 동시성 제어(Distributed Semaphore)&lt;/b&gt; 가 가능합니다. 이를 통해 특정 계정 풀(pool)에 허용된 동시 요청 개수를 실시간으로 관리할 수 있으며, 모든 요청이 동일 시점에 몰리는 스파이크 현상도 자연스럽게 완화됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;테스트 및 결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EBk6r/dJMcagqeUKJ/UmNsZksSYS96e6YRtt5Owk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EBk6r/dJMcagqeUKJ/UmNsZksSYS96e6YRtt5Owk/img.png&quot; data-alt=&quot;테스트 성능표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EBk6r/dJMcagqeUKJ/UmNsZksSYS96e6YRtt5Owk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEBk6r%2FdJMcagqeUKJ%2FUmNsZksSYS96e6YRtt5Owk%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;894&quot; height=&quot;213&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트 성능표&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1605&quot; data-end=&quot;1691&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 시나리오&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;233&quot; data-start=&quot;131&quot; data-ke-size=&quot;size16&quot;&gt;이번 성능 테스트는 &lt;b&gt;Redis + Lua + 세마포어 기반 구조&lt;/b&gt;가 실제로 병목을 얼마나 줄이는지를 검증하기 위해 진행했습니다.&lt;br /&gt;테스트는 아래와 같은 조건으로 구성되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;392&quot; data-start=&quot;235&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;290&quot; data-start=&quot;235&quot;&gt;&lt;b&gt;요청 수(Requests):&lt;/b&gt; 10, 20, 30, 50, 70, 100개의 동시 요청&lt;/li&gt;
&lt;li data-end=&quot;338&quot; data-start=&quot;291&quot;&gt;&lt;b&gt;계정 수(Accounts):&lt;/b&gt; 5개, 7개, 10개로 나누어 단계별 실험&lt;/li&gt;
&lt;li data-end=&quot;392&quot; data-start=&quot;339&quot;&gt;&lt;b&gt;측정 항목:&lt;/b&gt; 처리 시간(Time)과 TPS(Transaction Per Second)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;473&quot; data-start=&quot;394&quot; data-ke-size=&quot;size16&quot;&gt;즉, 계정 풀 크기를 점진적으로 늘려가며 동일한 요청 부하를 반복 적용하여&lt;br /&gt;시스템의 응답 속도와 처리 효율(TPS)을 동시에 측정했습니다.&lt;/p&gt;
&lt;hr data-end=&quot;478&quot; data-start=&quot;475&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;492&quot; data-start=&quot;480&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&amp;nbsp;테스트 방법&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;799&quot; data-start=&quot;494&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;612&quot; data-start=&quot;494&quot;&gt;&lt;b&gt;기존 방식 (DB Lock 기반)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;612&quot; data-start=&quot;525&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;612&quot; data-start=&quot;525&quot;&gt;각 요청은 SELECT ... FOR UPDATE로 DB에서 직접 계정을 잠그며,&lt;br /&gt;트랜잭션이 끝날 때까지 커넥션을 점유하는 구조였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;799&quot; data-start=&quot;614&quot;&gt;&lt;b&gt;개선 방식 (Redis + Lua + 세마포어 기반)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;799&quot; data-start=&quot;656&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;728&quot; data-start=&quot;656&quot;&gt;계정 선점과 해제를 Redis로 처리하고,&lt;br /&gt;Lua 스크립트를 통해 원자적 증감 연산 및 큐 관리가 수행됩니다.&lt;/li&gt;
&lt;li data-end=&quot;799&quot; data-start=&quot;732&quot;&gt;DB는 단순 상태 동기화 역할만 수행하며,&lt;br /&gt;Redis가 실질적인 병렬 제어(세마포어 역할)를 담당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-end=&quot;804&quot; data-start=&quot;801&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;817&quot; data-start=&quot;806&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;테스트 결과&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;구분평균 개선 효과
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;989&quot; data-start=&quot;819&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;989&quot; data-start=&quot;863&quot;&gt;
&lt;tr data-end=&quot;916&quot; data-start=&quot;863&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;878&quot; data-start=&quot;863&quot;&gt;&lt;b&gt;시간(Time)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;916&quot; data-start=&quot;878&quot; data-col-size=&quot;sm&quot;&gt;최대 &lt;b&gt;69.9% 단축&lt;/b&gt;, 평균 약 &lt;b&gt;46.9% 단축&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;989&quot; data-start=&quot;917&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;951&quot; data-start=&quot;917&quot;&gt;&lt;b&gt;TPS(Transaction Per Second)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;989&quot; data-start=&quot;951&quot; data-col-size=&quot;sm&quot;&gt;최대 &lt;b&gt;170% 향상&lt;/b&gt;, 평균 약 &lt;b&gt;107.9% 향상&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1075&quot; data-start=&quot;991&quot; data-ke-size=&quot;size16&quot;&gt;요청 수가 많을수록(특히 70건 이상) 성능 향상이 두드러졌으며,Redis 기반 구조로 전환한 후 &lt;b&gt;처리 안정성과 속도 모두 향상&lt;/b&gt;되었습니다. 특히 100개 동시 요청 시,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1191&quot; data-start=&quot;1096&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1127&quot; data-start=&quot;1096&quot;&gt;&lt;b&gt;개선 전:&lt;/b&gt; 119.5초 / 0.82 TPS&lt;/li&gt;
&lt;li data-end=&quot;1191&quot; data-start=&quot;1128&quot;&gt;&lt;b&gt;개선 후:&lt;/b&gt; 45.5초 / 2.2 TPS&lt;br /&gt;로, 약 &lt;b&gt;170% 이상의 TPS 향상&lt;/b&gt;을 달성했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis + Lua + 세마포어 구조를 적용한 결과, &lt;b&gt;단순 DLQ + Back-Off 단계에서 약 170% 추가적인 성능 향상&lt;/b&gt;을 확인했습니다.이제 시스템은 재시도 안정성뿐만 아니라, &lt;b&gt;DB 부하 분산, 트랜잭션 병목 제거, 처리량 확장성&lt;/b&gt;까지 확보하게 되었습니다.&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-end=&quot;354&quot; data-start=&quot;134&quot; data-ke-size=&quot;size16&quot;&gt;이번 UMF 2025 초대권 시스템은 단순한 성능 개선을 넘어, &lt;b&gt;구조적 병목을 제거하고 안정성을 확보한 실전 사례&lt;/b&gt;였습니다.&lt;br /&gt;Redis-Lua와 RabbitMQ 기반의 비동기 트랜잭션 제어를 적용한 결과, 수천 건의 요청이 동시에 몰리는 상황에서도&lt;br /&gt;시스템은 한 번도 중단되지 않았으며, 실제 행사 현장에서도 &lt;b&gt;단 한 건의 오류 없이 안정적으로 초대권 발급이 완료&lt;/b&gt;되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;565&quot; data-start=&quot;356&quot; data-ke-size=&quot;size16&quot;&gt;무엇보다, 이번 개선을 통해 단순히 수치를 올리는 것을 넘어 &amp;ldquo;&lt;b&gt;대규모 실사용 환경에서도 견고하게 버틸 수 있는 구조&lt;/b&gt;&amp;rdquo;를 직접 설계하고 검증할 수 있었다는 점에서 큰 의미가 있었습니다. 행사 기간 동안 사용자가 직접 초대권을 발급받고 입장 과정을 원활히 진행하는 모습을 보며, 그동안의 수많은 테스트와 튜닝 과정이 결실을 맺은 것 같아 정말 뿌듯했습니다. 앞으로도 이번 경험을 바탕으로,&lt;br /&gt;더 많은 트래픽을 안정적으로 처리하고, 블록체인 기반 서비스의 신뢰성과 확장성을 강화할 수 있는 구조적 개선을 지속해 나갈 예정입니다. 감사합니다.&lt;/p&gt;</description>
      <category>성능 개선</category>
      <category>개발자</category>
      <category>대규모</category>
      <category>백엔드</category>
      <category>성능 개선</category>
      <category>시스템</category>
      <category>초대권</category>
      <category>최적화</category>
      <category>행사</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/113</guid>
      <comments>https://back-stead.tistory.com/113#entry113comment</comments>
      <pubDate>Mon, 27 Oct 2025 23:48:52 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] QueryDSL 에서 FROM절에 SubQuery 및 ROW_NUMBER() 사용 방법</title>
      <link>https://back-stead.tistory.com/112</link>
      <description>&lt;div style=&quot;position: relative; background-color: #ffffff; padding: 0px 25px 0px 60px; border: 1px solid #d9d9d9;&quot;&gt;
&lt;div style=&quot;position: absolute; top: -1px; left: 14px; width: 30px; height: 47px; background-color: #5c636a;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;position: absolute; top: 17px; left: 14px; width: 0; height: 0; border: 15px solid; border-color: transparent transparent #ffffff transparent;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;FROM 절에 서브쿼리를 사용해 보자&lt;/b&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;div style=&quot;padding: 15px 20px; border-radius: 20px 0px; border: 2px solid #d9d9d9; line-height: 1.8;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하면서, 각 카테고리별로 좋아요 수를 기준으로 상위 2개의 게시물을 조회하는 쿼리가 필요하게 되었다. 하지만 일반 SQL문을 사용하는 것이 아닌 QueryDSL을 사용할 때 FROM 절에 서브쿼리를 어떻게 적용할 수 있는지 한번 알아보도록 하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;SQL 조회 예시&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ERD.png&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7uQ13/btsJFFcuEz4/gZNBKlJ2sqVrP11zPqxHKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7uQ13/btsJFFcuEz4/gZNBKlJ2sqVrP11zPqxHKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7uQ13/btsJFFcuEz4/gZNBKlJ2sqVrP11zPqxHKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7uQ13%2FbtsJFFcuEz4%2FgZNBKlJ2sqVrP11zPqxHKk%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;336&quot; data-filename=&quot;ERD.png&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이, Article 테이블에서 카테고리별로 좋아요 순으로 상위 2개의 게시물을 조회하는 방법에 대해 설명드리겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;카테고리별 좋아요 순 쿼리&lt;/b&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;- 먼저 각 게시물에 ROW_NUMBER()를 사용하여, 카테고리별로 좋아요 순위를 매깁니다. SQL 쿼리는 다음과 같습니다:&lt;/p&gt;
&lt;pre id=&quot;code_1726597516809&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; SELECT a.*, ROW_NUMBER() OVER (PARTITION BY a.article_category ORDER BY a.like_count DESC) AS rn
 FROM article a;&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;- 이 쿼리를 통해, 각 카테고리별로 게시물의 좋아요 순위가 매겨집니다. 이제 각 카테고리에서 상위 2개의 게시물을 조회해야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;FROM 절에 카테고리별 좋아요 서브쿼리&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ROW_NUMBER()를 통해 계산된 순위를 이용해, 상위 2개의 게시물만 필터링하기 위해서는 서브쿼리를 FROM 절에 넣어야 합니다. 이를 적용한 SQL은 다음과 같습니다:&lt;/p&gt;
&lt;pre id=&quot;code_1726597702734&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ra.*
FROM(
	SELECT a.*, ROW_NUMBER() OVER (PARTITION BY a.article_category ORDER BY a.like_count DESC) AS rn
	FROM article a) as ra
where ra.rn&amp;lt;=2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같이 카테고리별로 좋아요가 많은 게시글을 2개씩 조회된 것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;sql조회.png&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWAviR/btsJDUbatht/Csjn0CkfGrIs7gEolwCqvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWAviR/btsJDUbatht/Csjn0CkfGrIs7gEolwCqvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWAviR/btsJDUbatht/Csjn0CkfGrIs7gEolwCqvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWAviR%2FbtsJDUbatht%2FCsjn0CkfGrIs7gEolwCqvK%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;282&quot; height=&quot;128&quot; data-filename=&quot;sql조회.png&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;QueryDSL에서 윈도우 함수 사용&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;기본적으로 JPA와 QueryDSL을 어느 정도 사용해 본 경험이 있다고 간주하고 Q클래스 및 설정 방법 등은 생략하고 작성하도록 하겠습니다.!! ( &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;text-align: center;&quot;&gt;스프링부트 3.x 이상버전 사용 중이며,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: center;&quot;&gt;Hibernate 7.0 이상 기준입니다)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 QueryDSL에서의 ROW_OVER() 윈도우 함수 사용방법에 대해서 알아봅시다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;1. Expressions 사용하기&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Expressions 사용법&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1726600649040&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; JPAQuery&amp;lt;Tuple&amp;gt; rowNumber = jpaQueryFactory.select(
                        article,
                        Expressions.numberTemplate(
                                Long.class,
                                &quot;row_number() over (partition by {0} order by {1})&quot;,
                                article.articleCategory,
                                article.likeCount
                        ).as(&quot;rowNumber&quot;))
                .from(article);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 QueryDsl에서 서브쿼리르 사용할 때 사용하는 Expressions를 사용하여 직접 template를 작성해 주면 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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-filename=&quot;쿼리 실행.png&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biVOcJ/btsJFIfZWww/EfqqrhiMHBe3eY9DF0CU90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biVOcJ/btsJFIfZWww/EfqqrhiMHBe3eY9DF0CU90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biVOcJ/btsJFIfZWww/EfqqrhiMHBe3eY9DF0CU90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiVOcJ%2FbtsJFIfZWww%2FEfqqrhiMHBe3eY9DF0CU90%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;806&quot; height=&quot;135&quot; data-filename=&quot;쿼리 실행.png&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;2.&lt;span&gt; SQLExpressions&lt;/span&gt;사용하기&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;QueryDSL에서 지원하는 &lt;b&gt;SQLExpressions&lt;/b&gt;을 사용하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;지원.png&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgpAnR/btsJFsEtLN7/VREmsOlCiK2lldlETyRfrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgpAnR/btsJFsEtLN7/VREmsOlCiK2lldlETyRfrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgpAnR/btsJFsEtLN7/VREmsOlCiK2lldlETyRfrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgpAnR%2FbtsJFsEtLN7%2FVREmsOlCiK2lldlETyRfrk%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;947&quot; height=&quot;50&quot; data-filename=&quot;지원.png&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;rowNumber을 지원하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;SQLExpressions을 사용하기 위해서는 다음 의존성을 추가해줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1726600981625&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'com.querydsl:querydsl-sql:5.0.0'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;SQLExpressions 사용법&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1726601094344&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;JPAQuery&amp;lt;Tuple&amp;gt; rowNumber = jpaQueryFactory.select(article, SQLExpressions.rowNumber().over().partitionBy(article.articleCategory).as(&quot;rn&quot;))
                .from(article)
                .orderBy(Expressions.stringPath(&quot;rn&quot;).desc());&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;쿼리를 작성하고 실행하게 되면 다음과 같은 오류 메시지를 볼수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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-filename=&quot;오류메시지.png&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mZFED/btsJD8mEQKN/mnRpspl8xkZKLHgtQoTkdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mZFED/btsJD8mEQKN/mnRpspl8xkZKLHgtQoTkdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mZFED/btsJD8mEQKN/mnRpspl8xkZKLHgtQoTkdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmZFED%2FbtsJD8mEQKN%2FmnRpspl8xkZKLHgtQoTkdK%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;1220&quot; height=&quot;179&quot; data-filename=&quot;오류메시지.png&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;발생 원인&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;JPQL 쿼리 언어는 SQL 함수인 ROW_NUMBER()와 같은 고급 SQL 표현식을 직접 지원하지 않는다. 따라서, 이를 사용하기 위해서는 JPQL 템플릿을 사용자 정의하여 ORM에 이러한 함수가 사용될 수 있도록 설정해야 하는 오류가 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;해결 방법&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jpaQueryFactory를 EntityManger을 통해 빈으로 등록 시에 row_number을 사용하도록 JPQLTemplates의 Enum타입으로&amp;nbsp; 정의된 SQLOps을 등록하여 사용가능하도록 하면 해결할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1726601602481&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; @Bean
 public JPAQueryFactory jpaQueryFactory(){
      JPQLTemplates templates = new JPQLTemplates() {
          {
              add(SQLOps.ROWNUMBER, &quot;row_number()&quot;);
          }
      };
      return new JPAQueryFactory(templates,entityManager);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; 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-filename=&quot;사용쿼리.png&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb7nOQ/btsJD6PS5Fz/0mESloloCOceZU9yoiQT40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb7nOQ/btsJD6PS5Fz/0mESloloCOceZU9yoiQT40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb7nOQ/btsJD6PS5Fz/0mESloloCOceZU9yoiQT40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb7nOQ%2FbtsJD6PS5Fz%2F0mESloloCOceZU9yoiQT40%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;920&quot; height=&quot;134&quot; data-filename=&quot;사용쿼리.png&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위의 주입을 해주게 된다면 정삭적으로 쿼리가 실행되어 조회되는 것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;QueryDsl에서 From 절에 서브쿼리 사용하기&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;From 절에 서브쿼리를 사용하려고 여러 자료를 조사해 본 결과, From 절에서 서브쿼리를 사용하는 것이 가능한지에 대한 정보가 엇갈리고 있었습니다. 어떤 자료에서는 From 절에서 서브쿼리를 사용할 수 없다고 하고, 다른 자료에서는 사용이 가능하다고 설명하고 있었습니다. 여러 시도를 통해 완벽하지는 않지만, 정적인 조회에서는 From 절에 서브쿼리를 사용할 수 있는 방법을 찾게 되었습니다. 이제 이 방법에 대해 자세히 설명드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;From 절에 서브쿼리 사용 시&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 row_number을 from절에 쿼리로 사용하게 되면 다음과 같은 에러 메시지와 함께 조회의 실패하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726602118569&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.ClassCastException: class com.querydsl.jpa.impl.JPAQuery cannot be cast to class com.querydsl.core.types.EntityPath (com.querydsl.jpa.impl.JPAQuery and com.querydsl.core.types.EntityPath are in unnamed module of loader 'app')] with root cause

java.lang.ClassCastException: class com.querydsl.jpa.impl.JPAQuery cannot be cast to class com.querydsl.core.types.EntityPath (com.querydsl.jpa.impl.JPAQuery and com.querydsl.core.types.EntityPath are in unnamed module of loader 'app')&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;발생 원인&lt;/b&gt;&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JPQL에서는 서브쿼리를 직접 FROM절에 사용할 수 없기때문에 EntityPath으로 캐스팅을 하더라고 메타데이터인 QAticle로 조회되지 못해 사용할수 없는거 같은 생각이 들었습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;해결 방법&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서브 조회 클래스를 만들어 조회된 Qsub 클래스를 from절에 넣는 방법&lt;/li&gt;
&lt;li&gt;네이티브 쿼리를 사용하여 조회하는 방법&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위에 해결 방법에서는 서브 조회 클래스를 만들어 from절에 사용하는 방법을 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 본 클래스와 똑같이 클래스를 하나 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1726602479980&quot; class=&quot;bash&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Subselect(
        &quot;SELECT &quot; +
                &quot;a.*, &quot; +
                &quot;ROW_NUMBER() OVER (PARTITION BY a.article_category ORDER BY a.like_count DESC) AS rn &quot; +
                &quot;FROM article a&quot;
)
@Immutable
@Synchronize(&quot;article&quot;)
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ArticleRowSub extends BaseTime {

    @Id
    @Column(name = &quot;article_id&quot;) // assuming this is the primary key
    private Long id;

    private String title;               // 제목

    @Column(length = 5000)
    private String content;             // 내용

    private Integer likeCount;          // 좋아요 수
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt; @Subselect&lt;/b&gt; &lt;b&gt;:&lt;/b&gt;&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;서브쿼리의 결과 매핑&lt;/b&gt; : 서브쿼리의 결과를 JPA엔티티로 매핑이 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쿼리와 엔티티 분리&lt;/b&gt; : 복잡한 쿼리를 엔티티를 통해 매핑하여 복잡하거나 사용할수 없을 때 분리하여 유지보수성 향상&lt;/li&gt;
&lt;li&gt;&lt;b&gt; 복잡한 결과 집합 처리&lt;/b&gt; : @Subselect를 사용하면, 복잡한 집계 함수나 조인 결과를 엔티티로 매핑할 수 있습니다. 이로 인해 복잡한 데이터 집합을 간편하게 처리할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt; @Immutable&lt;/b&gt; &lt;b&gt;:&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;불변(immutable)&lt;/b&gt;임을 나타냅니다. 즉, 엔티티의 데이터가 변경되지 않으며, 읽기 전용으로 다루어야 함을 명시합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt; @Synchronize&lt;/b&gt; &lt;b&gt;:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;특정 엔티티나 컬렉션에 대한 동시성 문제를 제어하는 데 사용됩니다. article의 데이터의 일관성을 유지합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;QueryDsl 쿼리&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1726603165620&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; JPAQuery&amp;lt;ArticleRowSub&amp;gt; from = jpaQueryFactory.select(articleRowSub)
                .from(articleRowSub);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;article를 사용하여 조회하는 방식이 아닌 ArticleRowSub을 통해 조회하면 from절에 서브쿼리로 사용하는 것과 같은 기능을 동작하게 됩니다.&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-filename=&quot;완료.png&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDjT6m/btsJD5wGU34/HGPJLNxvNy8tyUWI17PBc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDjT6m/btsJD5wGU34/HGPJLNxvNy8tyUWI17PBc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDjT6m/btsJD5wGU34/HGPJLNxvNy8tyUWI17PBc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDjT6m%2FbtsJD5wGU34%2FHGPJLNxvNy8tyUWI17PBc0%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;926&quot; height=&quot;130&quot; data-filename=&quot;완료.png&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;querydsl 이 만든 쿼리와 직접 실행되는 쿼리가 다른 것을 볼 수 있습니다. from 절에 서브쿼리가 사용되어 조회된것을 볼수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;QueryDSL을 사용하면 동적인 쿼리를 쉽게 생성할 수 있지만, @Subselect를 사용하여 FROM 절에 서브쿼리를 포함할 경우 동적인 조회가 어려울 수 있습니다. 특히, 카테고리별로 조회할 때 차단된 사용자의 게시글을 제외해야 하는 제약이 있는 경우, QueryDSL의 동적 쿼리 기능만으로는 충분하지 않을 수 있습니다. 이 경우 비즈니스 로직에 조건문을 추가하여 네이티브 쿼리를 사용하는 것이 현실적인 해결책이 될 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;figure id=&quot;og_1726603400306&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SQLExpressions (Querydsl 4.4.0 API)&quot; data-og-description=&quot;cumeDist public static&amp;nbsp;WithinGroup &amp;nbsp;cumeDist(Object...&amp;nbsp;args) As an aggregate function, CUME_DIST calculates, for a hypothetical row r identified by the arguments of the function and a corresponding sort specification, the relative position of row r amon&quot; data-og-host=&quot;querydsl.com&quot; data-og-source-url=&quot;http://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/sql/SQLExpressions.html&quot; data-og-url=&quot;http://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/sql/SQLExpressions.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;http://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/sql/SQLExpressions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/sql/SQLExpressions.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;SQLExpressions (Querydsl 4.4.0 API)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;cumeDist public static&amp;nbsp;WithinGroup &amp;nbsp;cumeDist(Object...&amp;nbsp;args) As an aggregate function, CUME_DIST calculates, for a hypothetical row r identified by the arguments of the function and a corresponding sort specification, the relative position of row r amon&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;querydsl.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1726603441834&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;JPA Querydsl from절에 subquery 사용하기(@Subselect 이용)&quot; data-og-description=&quot;이슈사항 이번 내용도 배치로 통계로 구축하다가 생긴 이슈이다. 통계 쿼리를 mybatis로 작성하는 것은 쉬운데 검색해보니 querydsl은 subquery가 select 절이나 where 절에만 사용가능하고 from절에는 불&quot; data-og-host=&quot;wms0603.tistory.com&quot; data-og-source-url=&quot;https://wms0603.tistory.com/58&quot; data-og-url=&quot;https://wms0603.tistory.com/58&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oovNF/hyW2WfvaQl/rNmfsa7RkW0AOehlsjRNO0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cnTdmI/hyW6Is8avE/GAvWpcErlkUyZh9ksqUSt1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bfA30d/hyW6K5xCMt/LoR59KUprJAlZvGnwnJUC0/img.jpg?width=6000&amp;amp;height=3375&amp;amp;face=0_0_6000_3375&quot;&gt;&lt;a href=&quot;https://wms0603.tistory.com/58&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wms0603.tistory.com/58&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oovNF/hyW2WfvaQl/rNmfsa7RkW0AOehlsjRNO0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cnTdmI/hyW6Is8avE/GAvWpcErlkUyZh9ksqUSt1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bfA30d/hyW6K5xCMt/LoR59KUprJAlZvGnwnJUC0/img.jpg?width=6000&amp;amp;height=3375&amp;amp;face=0_0_6000_3375');&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;JPA Querydsl from절에 subquery 사용하기(@Subselect 이용)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이슈사항 이번 내용도 배치로 통계로 구축하다가 생긴 이슈이다. 통계 쿼리를 mybatis로 작성하는 것은 쉬운데 검색해보니 querydsl은 subquery가 select 절이나 where 절에만 사용가능하고 from절에는 불&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wms0603.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;</description>
      <category>JPA</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/112</guid>
      <comments>https://back-stead.tistory.com/112#entry112comment</comments>
      <pubDate>Wed, 18 Sep 2024 05:11:27 +0900</pubDate>
    </item>
    <item>
      <title>[Jenkins] Nginx+Docker+Jenkins 활용한 배포</title>
      <link>https://back-stead.tistory.com/111</link>
      <description>&lt;div style=&quot;position: relative; background-color: #ffffff; padding: 0px 25px 0px 60px; border: 1px solid #d9d9d9;&quot;&gt;
&lt;div style=&quot;position: absolute; top: -1px; left: 14px; width: 30px; height: 47px; background-color: #5c636a;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;position: absolute; top: 17px; left: 14px; width: 0; height: 0; border: 15px solid; border-color: transparent transparent #ffffff transparent;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;무중단 배포를 한번 해보자!&lt;/b&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;div style=&quot;padding: 15px 20px; border-radius: 20px 0px; border: 2px solid #d9d9d9; line-height: 1.8;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;새로운 프로젝트를 시작하면서 CI/CD를 구성하게 되었습니다. 이전 프로젝트에서는 새로운 기능이 담긴 JAR 파일을 배포할 때 기존 서버를 종료하고 새로운 서버를 띄우는 과정에서 다운타임이 발생하는 문제가 있었습니다. 이로 인해 짧게는 30초, 길게는 1분 이상 서비스가 중단되어 사용자에게 불편을 초래했습니다. 이번 프로젝트에서는 이러한 문제를 해결하기 위해 무중단 배포를 구현하려고 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;다운 타임&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&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;계획된 다운 타임 :&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시스템 유지 보수&lt;/b&gt; : 시스템의 성능을 올리거나 유지보수를 하는 동안 사용할 수 없는 시간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배포 및 릴리스&lt;/b&gt; : 새로운 기능이느 버그가 발생해 새로운 버전을 배포 시 발생 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계획되지 않은 다운 타임:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시스템 오류&lt;/b&gt; : 시스템이 다운됐거나, 오류가 발생해 사용할 수 없을 때 발생하는 시간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안&lt;/b&gt; : 블랙해커의 의해 시스템이 해킹되어 사용 할 수 없는 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;만약 새로운 기능일 개발해 CI/CD를 진행한다면 위에서 설명한 계횐된 다운타임-배포 및 릴리스의 해당되어 새 버가 새롭게 배포되기 전까지의 과정에서 다운타임이 발생하게 됩니다. 그래서 이를 해결하기 위해서는 무중단 배포(Zero Downtime Deployment) 기술을 활용하면 서비스 중단 없이 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;무중단 배포 방식&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;무중단 배포방식의 여러가지 방식이 있습니다, 그중 이번 포스팅에서는 &lt;b&gt;블루 그린&lt;/b&gt; 방식에 대해서만 설명하겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;블루-그린 무중단 배포란?&lt;/h4&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;85ed514d-86bd-43a2-ba4f-09829db9355e&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;블루-그린 배포(Blue-Green Deployment)는 두 개의 서버를 활용한 배포 방식입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;동작 과정&lt;/h4&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;현재상태.PNG&quot; data-origin-width=&quot;459&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EjDI9/btsIV6QKl45/ZWbKsad4240MK9WcyTrRHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EjDI9/btsIV6QKl45/ZWbKsad4240MK9WcyTrRHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EjDI9/btsIV6QKl45/ZWbKsad4240MK9WcyTrRHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEjDI9%2FbtsIV6QKl45%2FZWbKsad4240MK9WcyTrRHK%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;459&quot; height=&quot;340&quot; data-filename=&quot;현재상태.PNG&quot; data-origin-width=&quot;459&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;text-align: center; letter-spacing: 0px;&quot;&gt;현재 서버는 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;GREEN&lt;/b&gt; &lt;/span&gt;서버가 동작 중입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;새로운 버전을 개발하여 새로운 배포 서버를 띄우려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;먼저, 현재 사용 중인 서버가 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;BLUE&lt;/b&gt; &lt;/span&gt;서버인지 &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;GREEN&lt;/span&gt; &lt;/b&gt;서버인지 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;확인 결과, 현재 GREEN 서버가 동작 중임을 확인했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;블루실행.PNG&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cY51Nx/btsIVLFZC8R/RSTN3PUfr0E1lr8SXnikqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cY51Nx/btsIVLFZC8R/RSTN3PUfr0E1lr8SXnikqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cY51Nx/btsIVLFZC8R/RSTN3PUfr0E1lr8SXnikqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcY51Nx%2FbtsIVLFZC8R%2FRSTN3PUfr0E1lr8SXnikqK%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;319&quot; data-filename=&quot;블루실행.PNG&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그럼 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;BLUE&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;서버 포트를 사용해 새롭게 서버를 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;아직 그대로 데이터 전송은 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;GREEN&lt;/b&gt; &lt;/span&gt;서버로 전달합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그린다운.PNG&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DccRY/btsIWdPFwfW/GCG6fWb7vlWNnfXutV28iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DccRY/btsIWdPFwfW/GCG6fWb7vlWNnfXutV28iK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DccRY/btsIWdPFwfW/GCG6fWb7vlWNnfXutV28iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDccRY%2FbtsIWdPFwfW%2FGCG6fWb7vlWNnfXutV28iK%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;496&quot; height=&quot;331&quot; data-filename=&quot;그린다운.PNG&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;기존에 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;GREEN&lt;/b&gt;&lt;/span&gt;서버를 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;DOWN&lt;/span&gt; &lt;/b&gt;시키고 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;BLUE&lt;/b&gt; &lt;/span&gt;서버로 전달이 되도록 변경합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이런식의 과정으로 &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;GREEN&lt;/span&gt;-&lt;span style=&quot;color: #006dd7;&quot;&gt;BLUE&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: center;&quot;&gt; 방식이 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;단점&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;1. &amp;nbsp;&lt;b&gt;비용 증가&lt;/b&gt;: 두 개의 환경을 동시에 운영하기 때문에 인프라 비용이 증가할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;복잡성 증가&lt;/b&gt;: 환경 관리와 전환 작업이 추가되므로 설정과 운영의 복잡성이 높아질 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;사전 준비&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt; 이번 구현 방법에서는 Nginx, Docker, AWS, Jenkins, Spring를 기본적으로 숙지한 상태라고 가정하며, 자세한 설치 과정 등은 생략하도록 하겠습니다. &lt;u&gt;&lt;b&gt; &lt;/b&gt;&lt;/u&gt; &lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;사전 준비&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring-boot&lt;/b&gt; : actuator, web 준비&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS-EC2&lt;/b&gt; : docker, docker-compose, nginx, jenkins 준비&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker&lt;/b&gt; : docker-hub 에 빌드된 image&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Nginx 설정&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Docker-compose를통해 nginx를 실행할 수 있게 작성해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722974513541&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.9'

services:
  nginx:
    image: nginx:latest
    volumes:
      - ./conf/nginx.conf:/etc/nginx/nginx.conf
    restart: always
    ports:
      - 80:80
    networks:
      - backend_network
    depends_on:
      - api

networks:
  backend_network:
    external: true&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단하게 작성해 줍니다. 서버는 다른 docker-compose 작성해 실행해 줄 예정이니 depends_on을 통해 의존해 줍니다.&lt;br /&gt;또한 사용할 수 있게 네트워크도 연결해줘야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1722974585841&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; docker network create backend_network&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; 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-filename=&quot;네트웤으.PNG&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zuywS/btsIWgZWdUO/4KB2nmz79bkUorRKuNSjj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zuywS/btsIWgZWdUO/4KB2nmz79bkUorRKuNSjj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zuywS/btsIWgZWdUO/4KB2nmz79bkUorRKuNSjj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzuywS%2FbtsIWgZWdUO%2F4KB2nmz79bkUorRKuNSjj0%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;508&quot; height=&quot;353&quot; data-filename=&quot;네트웤으.PNG&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 두 개의 blue, green의 yml파일도 작성해 줍니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;docker-compose.blue.yml&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1722979001062&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3.9&quot;

networks:
  backend_network:
     external: true

services:
  api:
    image: [이미지명]
    container_name: [컨테이너명]
    restart: always
    ports:
      - &quot;8443:8443&quot;
    networks:
      - backend_network&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;docker-compose.green.yml&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1722979019734&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3.9&quot;

networks:
  backend_network:
     external: true

services:
  api:
    image: [이미지명]
    container_name: [컨테이너명]
    restart: always
    ports:
      - &quot;8444:8443&quot;
    networks:
      - backend_network&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;nginx.conf&lt;/blockquote&gt;
&lt;pre id=&quot;code_1722981687036&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;events {
    worker_connections 1024;
}

http {

    server {
        listen 80;

        server_name 52.78.225.238;

        location /.well-known/acme-challenge/ {
            allow all;
        }
        location /actuator {
            add_header X-Served-By $host;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-Scheme $scheme;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722979137293&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose -f [docker-compose.[색].yml] -p [별명] -f docker-compose.yml up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 명령어를 입력하여 실행해 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실행 중인 Niginx의 접속하여 VIM을 설치해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722981712991&quot; class=&quot;awk&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;vim /etc/nginx/include/service_url.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아래 내용을 작성해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722981749518&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;set $service_url :8081;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;현재 실행 중인 Green 서버의 포트번호입니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 shell작성하면 현재 실행중인 포트번호로 자동으로 바뀌게 할 예정입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;deploy.sh&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1722982442705&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXIST_BLUE=$(sudo docker-compose -p blog-blue -f /blog/docker-compose.blue.yml ps | grep blog-blue)
echo &quot;${EXIST_BLUE}&quot;

#1
if [ -z &quot;$EXIST_BLUE&quot; ]; then
  echo &quot;blue 컨테이너는 실행 중이지 않습니다.&quot;
  sudo docker-compose -p blog-blue -f /blog/docker-compose.blue.yml up -d
  BEFORE_COLOR=&quot;green&quot;
  AFTER_COLOR=&quot;blue&quot;
  BEFORE_PORT=8081
  AFTER_PORT=8080
else
  echo &quot;blue 컨테이너는 실행 중입니다.&quot;
  sudo docker-compose -p blog-green -f /blog/docker-compose.green.yml up -d
  BEFORE_COLOR=&quot;blue&quot;
  AFTER_COLOR=&quot;green&quot;
  BEFORE_PORT=8080
  AFTER_PORT=8081
fi

echo &quot;${AFTER_COLOR} server up(port:${AFTER_PORT})&quot;


#2
for cnt in {1..10}
do
  echo &quot;서버 응답 확인중(${cnt}/10)&quot;
  UP=$(curl -s http://52.78.225.238/:${AFTER_PORT}/actuator/health)
  if [ -z &quot;$UP&quot; ]; then
    sleep 10
    continue
  else
    break
  fi
done

if [ $cnt -eq 10 ]; then
  echo &quot;서버가 정상적으로 구동되지 않았습니다.&quot;
  exit 1
fi


#3
# 설정 파일을 호스트에서 수정
echo &quot;Modifying Nginx Configuration on Host...&quot;
sudo docker exec blog-green_nginx_1 sed -i &quot;s/:${BEFORE_PORT}/:${AFTER_PORT}/&quot; /etc/nginx/include/service_url.conf


# Nginx 서버 재로드
echo &quot;Reloading Nginx...&quot;
sudo docker exec blog-green_nginx_1 nginx -s reload

echo &quot;Deploy Completed!!&quot;

#4
echo &quot;$BEFORE_COLOR server down(port:${BEFORE_PORT})&quot;
pwd
sudo docker stop blog-${BEFORE_COLOR}
sudo docker rm blog-${BEFORE_COLOR}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;디렉터리 구조&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;blog&lt;/b&gt;&lt;br /&gt;├──&amp;nbsp;deploy.sh &lt;br /&gt;├──&amp;nbsp;docker-compose.blue.yml &lt;br /&gt;├──&amp;nbsp;docker-compose.green.yml &lt;br /&gt;├──&amp;nbsp;docker-compose.yml &lt;br /&gt;├──&amp;nbsp;nginx.conf &lt;br /&gt;└──&amp;nbsp;service_url.conf&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;젠킨스 설정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운.PNG&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VQraT/btsIValSOrQ/Hyqe6PsSuVNSE6ToYDa73k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VQraT/btsIValSOrQ/Hyqe6PsSuVNSE6ToYDa73k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VQraT/btsIValSOrQ/Hyqe6PsSuVNSE6ToYDa73k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVQraT%2FbtsIValSOrQ%2FHyqe6PsSuVNSE6ToYDa73k%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;628&quot; height=&quot;100&quot; data-filename=&quot;다운.PNG&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;100&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;원격으로 ssh를 접속하기 위해서 SSH 플러그인을 다운로드하여줍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 후 EC2에 접속할 때 사용한. pem키의 내용을 복사해 줍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;credenials를 추가해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;키.PNG&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;763&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNgwTN/btsIViRBfp9/4Ooa6sw4Kr90p6RGhxC40k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNgwTN/btsIViRBfp9/4Ooa6sw4Kr90p6RGhxC40k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNgwTN/btsIViRBfp9/4Ooa6sw4Kr90p6RGhxC40k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNgwTN%2FbtsIViRBfp9%2F4Ooa6sw4Kr90p6RGhxC40k%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;1097&quot; height=&quot;763&quot; data-filename=&quot;키.PNG&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;763&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ID&lt;/b&gt; : 사용할 커스텀 아이디(마음대로 지정가능합니다.)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Description&lt;/b&gt; : 부가설명을 작성합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Username&lt;/b&gt; : EC2의 접속할 때 사용한 명을 사용합니다.(우분투 : ubuntu, 아마존리눅스면 : ec2-user)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Key&lt;/b&gt; : ec2 접속 시 사용했던 키의 내용을 복사&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;파이프라인 작성&lt;/h4&gt;
&lt;pre id=&quot;code_1722983542558&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pipeline{
    agent any
    
    stages{
        
       stage('Deploy') {
            steps {
                sshagent (credentials: ['ec2_blog']) {
                        sh &quot;&quot;&quot;
                            ssh -o StrictHostKeyChecking=no ubuntu@52.78.225.238 '
                            cd /blog
                            ls -a
                            sudo ./deploy.sh
                            '
                        &quot;&quot;&quot;
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;추가적인 필요한 것이 있으면 stage를 통해 추가하시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;현재는 ip주소가 그대로 노출되지만 블로그 작성 시에만 사용했지만 꼭 환경변수로 사용해야 합니다!!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 쉘스크립트를 실행하게 되면 이전에 작성했던 service_url.conf 이 동적으로 바뀌어 새롭게 연결해 주는 것을 기대할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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-filename=&quot;파이프라인.PNG&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eQgIeL/btsIW5XGSHf/sJb9lwjJlZbKiyNckzBn71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eQgIeL/btsIW5XGSHf/sJb9lwjJlZbKiyNckzBn71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eQgIeL/btsIW5XGSHf/sJb9lwjJlZbKiyNckzBn71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeQgIeL%2FbtsIW5XGSHf%2FsJb9lwjJlZbKiyNckzBn71%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;521&quot; data-filename=&quot;파이프라인.PNG&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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-filename=&quot;성공.PNG&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcVL1G/btsIVLzf8C0/Ju7IbcBFpo4krkfBts6RX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcVL1G/btsIVLzf8C0/Ju7IbcBFpo4krkfBts6RX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcVL1G/btsIVLzf8C0/Ju7IbcBFpo4krkfBts6RX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcVL1G%2FbtsIVLzf8C0%2FJu7IbcBFpo4krkfBts6RX1%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;352&quot; height=&quot;180&quot; data-filename=&quot;성공.PNG&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;결과 학인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8080.PNG&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp0MgL/btsIXoJvaz9/6VE7pTfqNMkuVA7JWCrkZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp0MgL/btsIXoJvaz9/6VE7pTfqNMkuVA7JWCrkZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp0MgL/btsIXoJvaz9/6VE7pTfqNMkuVA7JWCrkZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp0MgL%2FbtsIXoJvaz9%2F6VE7pTfqNMkuVA7JWCrkZ1%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;1059&quot; height=&quot;141&quot; data-filename=&quot;8080.PNG&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;141&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;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;시퍂.PNG&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UBvqx/btsIVcRw63P/UJl22aXzyMXKUHfr2Tqv80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UBvqx/btsIVcRw63P/UJl22aXzyMXKUHfr2Tqv80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UBvqx/btsIVcRw63P/UJl22aXzyMXKUHfr2Tqv80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUBvqx%2FbtsIVcRw63P%2FUJl22aXzyMXKUHfr2Tqv80%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;926&quot; height=&quot;579&quot; data-filename=&quot;시퍂.PNG&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;579&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;위 로그에서 blue가 실행 중이지 않아서 blue가 실행되고 green서버가 다운된 것을 확인할 수 있다.&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 우여곡절 끝에 green-blue 무중단배포를 구현하게 됐습니다. 어려운 점이 많이 있었지만.. 역시.. 삽질하면서 하다 보면 언젠가는 구현하는 것 같습니다.!! 궁금하거나 틀린 부분이 있으면 말씀해 주세요!! 감사합니다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;b&gt;Reference&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1722983914608&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;Jenkins, Docker를 이용한 무중단 배포 시작하기&quot; data-og-description=&quot;시작하기 예전에는 배포하는 날이면 접속하는 사용자가 적은 새벽시간에 했다고 본적이 있습니다. 배포를 할 때에는 새로운 버전의 jar 파일을 배포할 서버로 복사시키고, 직접 SSH로 접속하여 ja&quot; data-og-host=&quot;iseunghan.tistory.com&quot; data-og-source-url=&quot;https://iseunghan.tistory.com/452&quot; data-og-url=&quot;https://iseunghan.tistory.com/452&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/be8PA6/hyWKGKiWT6/IrjYD652AwlULOjG0blIGk/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/dPNKOB/hyWKDNyyMp/ZCdom0oHCqAB1nkfF4uWy0/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/DCpZ2/hyWKv24i7I/AEF3Z1wpwKd4KKstklPUY0/img.png?width=2000&amp;amp;height=980&amp;amp;face=0_0_2000_980&quot;&gt;&lt;a href=&quot;https://iseunghan.tistory.com/452&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://iseunghan.tistory.com/452&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/be8PA6/hyWKGKiWT6/IrjYD652AwlULOjG0blIGk/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/dPNKOB/hyWKDNyyMp/ZCdom0oHCqAB1nkfF4uWy0/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/DCpZ2/hyWKv24i7I/AEF3Z1wpwKd4KKstklPUY0/img.png?width=2000&amp;amp;height=980&amp;amp;face=0_0_2000_980');&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;Jenkins, Docker를 이용한 무중단 배포 시작하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;시작하기 예전에는 배포하는 날이면 접속하는 사용자가 적은 새벽시간에 했다고 본적이 있습니다. 배포를 할 때에는 새로운 버전의 jar 파일을 배포할 서버로 복사시키고, 직접 SSH로 접속하여 ja&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;iseunghan.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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Devops/도커</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/111</guid>
      <comments>https://back-stead.tistory.com/111#entry111comment</comments>
      <pubDate>Wed, 7 Aug 2024 07:40:08 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL]FULLTEXT with N-GRAM 전문 검색 을 사용한 최적화</title>
      <link>https://back-stead.tistory.com/110</link>
      <description>&lt;div style=&quot;position: relative; background-color: #ffffff; padding: 0px 25px 0px 60px; border: 1px solid #d9d9d9;&quot;&gt;
&lt;div style=&quot;position: absolute; top: -1px; left: 14px; width: 30px; height: 47px; background-color: #5c636a;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;position: absolute; top: 17px; left: 14px; width: 0; height: 0; border: 15px solid; border-color: transparent transparent #ffffff transparent;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;FULLTEXT with N-GRAM 전문 검색을 사용해 보자&lt;/b&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;div style=&quot;padding: 15px 20px; border-radius: 20px 0px; border: 2px solid #d9d9d9; line-height: 1.8;&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;웹사이트를 사용하다 보면 검색 기능을 쉽게 접할 수 있습니다. 기본적으로 많은 웹사이트는 엘라스틱서치(Elasticsearch)를 사용하여 손쉽게 검색 기능을 구현하고 개선합니다. 그러나 MySQL에서도 FULLTEXT와 N-GRAM을 지원하여 검색 기능을 최적화할 수 있습니다. 이번 포스팅에서는 MySQL을 사용하여 검색 기능을 최적화하는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;FullText Index&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;테긋트.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RUAZF/btsIy12cd65/nCort8Sep6tLqOhcTitatk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RUAZF/btsIy12cd65/nCort8Sep6tLqOhcTitatk/img.png&quot; data-alt=&quot;동작 방식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RUAZF/btsIy12cd65/nCort8Sep6tLqOhcTitatk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRUAZF%2FbtsIy12cd65%2FnCort8Sep6tLqOhcTitatk%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;2000&quot; height=&quot;1561&quot; data-filename=&quot;테긋트.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1561&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;동작 방식&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;a5ca7acd-e8ec-491e-a708-d13a969c862d&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;FULLTEXT 검색은 관계형 데이터베이스에서 텍스트 기반의 검색 기능을 제공하는 중요한 기술입니다. 특히 MySQL과 같은 데이터베이스에서는 FULLTEXT 인덱스를 통해 이 기능을 구현할 수 있습니다. 이 인덱스는 텍스트 필드 내의 단어들을 색인화하여 빠르게 검색할 수 있도록 돕습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 데이터베이스에서 LIKE 문을 사용하여 검색할 때는 데이터가 적을 때는 빠를 수 있지만, 데이터가 많아지면 성능 문제가 발생할 수 있습니다. 특히 LIKE 문에서 %_, _%, %_%과 같은 와일드카드 패턴을 사용할 경우 인덱스를 효과적으로 활용하기 어렵습니다. 이때 FULLTEXT 인덱스는 이러한 패턴 검색을 지원하며 빠른 속도로 데이터를 검색할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, FULLTEXT 인덱스를 사용하여 다음과 같은 검색을 수행할 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LIKE 'apple%': FULLTEXT 인덱스는 이 패턴을 지원하여 빠르게 결과를 반환할 수 있습니다.&lt;/li&gt;
&lt;li&gt;LIKE '%orange%': FULLTEXT 인덱스 역시 이러한 패턴을 효율적으로 처리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;FULLTEXT 검색은 단순히 텍스트 매칭을 넘어서, 단어의 의미론적인 관계나 가까운 거리에 있는 단어들을 검색할 수 있는 기능도 제공합니다. 이는 사용자가 원하는 검색 결과를 더 정확하게 찾을 수 있도록 도와줍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;FULLTEXT 검색&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;FULLTEXT검색은 일반적으로 다음과 같은 필드 유형에만 적용됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CHAR&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;VARCHAR&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TEXT&lt;/b&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;FULLTEXT 인덱스&amp;nbsp; 생성&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;인덱스 생성 방법&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1720770423191&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE FULLTEXT INDEX idx_name ON table_name(column1, column2, ...);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;nbsp;&lt;b&gt;사용 방법&lt;/b&gt;&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- IN NATURAL LANGUAGE MODE&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720770617912&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM TABLE
WHERE MATCH(column) AGAINST ('keyword' IN NATURAL LANUGUAGE MODE);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;키워드 토큰화&lt;/b&gt;: 검색할 키워드를 공백을 기준으로 토큰화하여 각각의 단어로 분리합니다. 예를 들어, &quot;apple pie&quot;라는 검색어는 &quot;apple&quot;과 &quot;pie&quot;로 분리됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단어 포함 검색&lt;/b&gt;: 분리된 각 단어 중 하나라도 테이블의 해당 컬럼에 포함되어 있으면 해당 레코드를 검색 결과로 반환&lt;br /&gt;합니다. 즉, OR 조건과 비슷하게 동작하며, 하나 이상의 단어가 포함된 레코드를 검색합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- IN BOLEAN MODE &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720770625822&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM TABLE
WHERE MATCH(column) AGAINST ('keyword' IN BOOLEAN MODE);&lt;/code&gt;&lt;/pre&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;b&gt;키워드 분리&lt;/b&gt;: 검색할 키워드를 공백을 기준으로 토큰화하여 각각의 단어로 분리합니다. 예를 들어, &quot;apple pie&quot;라는 검색어는 &quot;apple&quot;과 &quot;pie&quot;로 분리됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부울 연산자 사용&lt;/b&gt;: BOOLEAN MODE에서는 부울 연산자(+, -, *, &quot; 등)를 사용하여 검색 조건을 명확히 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 101.047%; height: 153px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0967%; height: 17px; text-align: center;&quot;&gt;Operate&lt;/td&gt;
&lt;td style=&quot;width: 87.6503%; height: 17px; text-align: center;&quot;&gt;Description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0967%; height: 17px; text-align: center;&quot;&gt;+&lt;/td&gt;
&lt;td style=&quot;width: 87.6503%; height: 17px; text-align: center;&quot;&gt;반드시 포함하는 단어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0967%; height: 17px; text-align: center;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;width: 87.6503%; height: 17px; text-align: center;&quot;&gt;반드시 제외하는 단어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0967%; height: 17px; text-align: center;&quot;&gt;&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 87.6503%; height: 17px; text-align: center;&quot;&gt;포함하면서 검색 순위를 높일 단어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0967%; height: 17px; text-align: center;&quot;&gt;&amp;lt;&lt;/td&gt;
&lt;td style=&quot;width: 87.6503%; height: 17px; text-align: center;&quot;&gt;포함하지만 검색 순위를 낮출 단어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0967%; height: 17px; text-align: center;&quot;&gt;()&lt;/td&gt;
&lt;td style=&quot;width: 87.6503%; height: 17px; text-align: center;&quot;&gt;하위 표현식으로 그룹화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0967%; height: 17px; text-align: center;&quot;&gt;~&lt;/td&gt;
&lt;td style=&quot;width: 87.6503%; height: 17px; text-align: center;&quot;&gt;'_' 연산자와 비슷하나 제외 시키지 않고 검색 조건 낮춤&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0967%; height: 17px; text-align: center;&quot;&gt;*&lt;/td&gt;
&lt;td style=&quot;width: 87.6503%; height: 17px; text-align: center;&quot;&gt;와일드 카드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0967%; height: 17px; text-align: center;&quot;&gt;&quot;&quot;&lt;/td&gt;
&lt;td style=&quot;width: 87.6503%; height: 17px; text-align: center;&quot;&gt;구문 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;FULLTEXT 인덱스&amp;nbsp; 설정&lt;/h4&gt;
&lt;pre id=&quot;code_1720771145071&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;show variables like '%ft_min%';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기볹설정.PNG&quot; data-origin-width=&quot;213&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Mxc4/btsIyxG2AUK/4X1XbzwQ3ipK3ERcz0hbBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Mxc4/btsIyxG2AUK/4X1XbzwQ3ipK3ERcz0hbBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Mxc4/btsIyxG2AUK/4X1XbzwQ3ipK3ERcz0hbBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Mxc4%2FbtsIyxG2AUK%2F4X1XbzwQ3ipK3ERcz0hbBk%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;272&quot; height=&quot;83&quot; data-filename=&quot;기볹설정.PNG&quot; data-origin-width=&quot;213&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;단어를 2글자로 검색하게 된다면 검색이 되지 않습니다. 왜냐면 기본적으로 검색되는 단어의 길이가 4로 되어있는걸을 볼 수 있습니다. 만약 2 글자도 검색이 가능하게 하고 싶으면 설정을 변경하시면 됩니다.(my.ini, cnf의 파일에서 &lt;span&gt;ft_min_word_len&lt;/span&gt;=&quot;바꿀 길이수&quot; 변경하시면 됩니다.)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;일반 검색&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xwJOC/btsIyyMKWL8/Onya6H1AdRgKLYxFH6Txu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xwJOC/btsIyyMKWL8/Onya6H1AdRgKLYxFH6Txu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xwJOC/btsIyyMKWL8/Onya6H1AdRgKLYxFH6Txu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxwJOC%2FbtsIyyMKWL8%2FOnya6H1AdRgKLYxFH6Txu1%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;1033&quot; height=&quot;299&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;FullText 검색&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;일반2.PNG&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0K1K9/btsIwQO5GBN/Wdzk3AjHzSPcOQ6AdKuEt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0K1K9/btsIwQO5GBN/Wdzk3AjHzSPcOQ6AdKuEt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0K1K9/btsIwQO5GBN/Wdzk3AjHzSPcOQ6AdKuEt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0K1K9%2FbtsIwQO5GBN%2FWdzk3AjHzSPcOQ6AdKuEt1%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;1012&quot; height=&quot;216&quot; data-filename=&quot;일반2.PNG&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;일반 like문을 사용 시에 테이블 풀스캔을 통해 모든 데이터를 조회했지만, 인덱스를 생성 후 match() - against()을 사용하게 되면 fulltext인덱스를 사용하면서 성능적인 우의를 찾는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;551795aa-2bbd-46f9-85fc-b12fde0f0ecb&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 조회되는 데이터의 수가 다른 것을 볼 수 있습니다. 문제의 원인은 FULLTEXT 인덱스에서 사용되는 토큰화 방식에 있습니다. FULLTEXT 검색에서는 기본적으로 공백을 기준으로 단어를 분리하여 토큰화합니다. 따라서 &quot;돼지고기&quot;와 &quot;돼지고기&quot;는 다르게 인식될 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;N-GRAM&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ngram.png&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtIcAg/btsIxDafZ8H/NgO3AAwyGWwXv3um34RFRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtIcAg/btsIxDafZ8H/NgO3AAwyGWwXv3um34RFRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtIcAg/btsIxDafZ8H/NgO3AAwyGWwXv3um34RFRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtIcAg%2FbtsIxDafZ8H%2FNgO3AAwyGWwXv3um34RFRk%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;695&quot; height=&quot;286&quot; data-filename=&quot;ngram.png&quot; data-origin-width=&quot;695&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;p data-ke-size=&quot;size16&quot;&gt;N-GRAM은 텍스트를 N개의 연속된 문자 또는 단어로 분할하는 방법입니다. 이를 통해 텍스트의 부분 문자열을 검색할 수 있게 합니다. 예를 들어, &quot;hello&quot;라는 단어를 2-그램(바이그램)으로 분할하면 다음과 같은 조각들이 생성됩니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;he&quot; , &quot;el&quot;, &quot;ll&quot; , &quot;lo&quot;로 토큰을 만들게 됩니다. 띄어 씌기가 있을 경우, 띄어쓰기를 제거 후 하나의 단어로만 존재하게 됩니다.&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;위에서 FullText만 사용했을 때 발생한 문제점을 해결하려면 N-gram을 함께 사용한 인덱스를 생성하면 됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;FULLTEXT&amp;nbsp; With N-gram 인덱스&amp;nbsp; 생성&lt;/h4&gt;
&lt;pre id=&quot;code_1720773124496&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE example ADD FULLTEXT INDEX ngram_idx (content) WITH PARSER ngram;&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;877&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OHUiV/btsIwMTwGty/kLPkBkQvYAYCqQ96lQtaH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OHUiV/btsIwMTwGty/kLPkBkQvYAYCqQ96lQtaH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OHUiV/btsIwMTwGty/kLPkBkQvYAYCqQ96lQtaH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOHUiV%2FbtsIwMTwGty%2FkLPkBkQvYAYCqQ96lQtaH0%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;877&quot; height=&quot;222&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성 후 실행하 보면 FullText 인덱스를 사용하는 것을 볼 수있다. 하지만 13개가이난 무려 10705개가 조회된것을 볼수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce5fzq/btsIygr8T9Q/qlrkwi6WrgK8By8cgT0x3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce5fzq/btsIygr8T9Q/qlrkwi6WrgK8By8cgT0x3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce5fzq/btsIygr8T9Q/qlrkwi6WrgK8By8cgT0x3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fce5fzq%2FbtsIygr8T9Q%2Fqlrkwi6WrgK8By8cgT0x3k%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;265&quot; height=&quot;383&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회된 데이터를 보면 &quot;돼지&quot;, &quot;고기&quot; 단어가 들어간 단어가 조회되면서 데이터가 많은 데이터를 조회되는 것을 볼 수 있다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;FULLTEXT&amp;nbsp; With N-gram 인덱스&amp;nbsp; 단점&lt;/h4&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;b&gt;너무 많은 데이터 조회&lt;/b&gt;: N-gram을 사용하면 특정 단어의 일부만으로도 해당 단어를 포함한 많은 데이터가 조회될 수 있습니다. 예를 들어, &quot;돼지&quot;와 &quot;고기&quot;라는 두 단어가 각각 포함된 모든 데이터가 조회될 수 있습니다. 이는 사용자가 원하는 검색 결과와 실제로 조회된 결과가 다를 수 있는 원인이 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정확도 저하&lt;/b&gt;: 특히 단어의 일부만으로 조회되는 경우, 원하는 결과와 다른 데이터가 섞일 수 있어 검색 결과의 정확도가 저하될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 저하&lt;/b&gt;: N-gram을 사용하여 데이터베이스를 쿼리 할 때는 추가적인 연산이 필요하며, 많은 데이터가 조회될 경우 성능이 저하될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문맥 파악의 어려움&lt;/b&gt;: N-gram은 단어의 일부를 기반으로 검색을 수행하기 때문에 문맥을 완전히 이해하지 못할 수 있습니다. 예를 들어, &quot;돼지고기&quot;라는 단어가 포함된 데이터는 &quot;돼지&quot;와 &quot;고기&quot;라는 단어가 각각 포함된 데이터와 다를 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 기본적인 MySQL에서 지원해 주는 것만으로도 많은 성능 개선되는 것을 볼 수 있습니다. 아래와 같이 같은 돼지고기를 검색하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;속도.PNG&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCRfIG/btsIxKOedoa/0SAU1rcWVvDqJAt8KHNQa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCRfIG/btsIxKOedoa/0SAU1rcWVvDqJAt8KHNQa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCRfIG/btsIxKOedoa/0SAU1rcWVvDqJAt8KHNQa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCRfIG%2FbtsIxKOedoa%2F0SAU1rcWVvDqJAt8KHNQa0%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;789&quot; height=&quot;40&quot; data-filename=&quot;속도.PNG&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2476건의 데이터를 조회하는 데 0.093sec 걸린반면 10705건을 조회하는데 걸린 시간은 0.032 sec가 걸린 것을 보면&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;엄청 빠른 속도로 검색되는 것을 확인할 수 있습니다. 검색조건을 추가하거나 위에 작성한&lt;span&gt;&amp;nbsp;&lt;/span&gt;부울 검색을 활용해서 잘만 적용한다면 MySQL에서도 충분히 빠른 속도로 검색이 가능할 것 같습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;figure id=&quot;og_1720774321203&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: InnoDB 전문 검색 : N-gram Parser&quot; data-og-description=&quot;기본 InnoDB 전문 검색(Full Text) 파서는 공백이나 단어 분리자가 토큰인 라틴 기반 언어들에서는 이상적이지만 개별 단어에 대한 고정된 구분자가 없는 중국어, 일본어, 한국어(CJK)같은 언어들에서&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/blog-archive/innodb-full-text-n-gram-parser-ko/&quot; data-og-url=&quot;https://dev.mysql.com/blog-archive/innodb-full-text-n-gram-parser-ko/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/blog-archive/innodb-full-text-n-gram-parser-ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/blog-archive/innodb-full-text-n-gram-parser-ko/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;MySQL :: InnoDB 전문 검색 : N-gram Parser&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;기본 InnoDB 전문 검색(Full Text) 파서는 공백이나 단어 분리자가 토큰인 라틴 기반 언어들에서는 이상적이지만 개별 단어에 대한 고정된 구분자가 없는 중국어, 일본어, 한국어(CJK)같은 언어들에서&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>MySQL</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/110</guid>
      <comments>https://back-stead.tistory.com/110#entry110comment</comments>
      <pubDate>Fri, 12 Jul 2024 17:52:52 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] Let's encrypt - 스프링 부트  SSL/TLS 인증서 발급 받기</title>
      <link>https://back-stead.tistory.com/109</link>
      <description>&lt;div style=&quot;position: relative; background-color: #ffffff; padding: 0px 25px 0px 60px; border: 1px solid #d9d9d9;&quot;&gt;
&lt;div style=&quot;position: absolute; top: -1px; left: 14px; width: 30px; height: 47px; background-color: #5c636a;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;position: absolute; top: 17px; left: 14px; width: 0; height: 0; border: 15px solid; border-color: transparent transparent #ffffff transparent;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SSL/TLS인증서를 발급받기&lt;/b&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;div style=&quot;padding: 15px 20px; border-radius: 20px 0px; border: 2px solid #d9d9d9; line-height: 1.8;&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 스프링 부트를 사용하는 애플리케이션이 Let`s encrypt를 이용한 SSL/TLS 인증서를 발급받고 설정하는 방법을 알아보도록 하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;SSL/TLS란&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;SSL(Secure Sockets Layer)와 TLS(Transport Layer Secuity)는 인터넷상에서 데이터의 기밀성과 무결성을 보장하는 보안 프로토콜입니다. 이 프로토콜은 주로 웹 서버 간의 통신을 암호화하여 데이터를 안전하게 전송하기 위해 사용됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Let's Encrypt&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;et's Encrypt는 무료, 자동, 개방형, 인증 기관(CA)입니다. 인터넷 보안을 개선하고 HTTPS 사용을 장려하기 위해서 비영리 단체로 설립되었습니다. 그래서 Let's Encrypt는 누구나 무료로 SSL/TLS 인증서를 발급받아 웹사이트에 HTTPS를 적용 가능합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;특징&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;가격&lt;/b&gt; : AWS나 다른 상용 인증서와 달리 &lt;span style=&quot;background-color: #fdfdfd; color: #000000; text-align: start;&quot;&gt;Let&amp;rsquo;s Encrypt 무료로 인증서를 발급받을 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000; text-align: start;&quot;&gt;&lt;b&gt;자동화&lt;/b&gt; : 인증서를 발급받을 때 설치, 갱신, 발급 과정을 명령어를 사용해 자동화하여 편의성을 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000; text-align: start;&quot;&gt;&lt;b&gt;개방형&lt;/b&gt; : 모든 사용자가 공개적인 API의 인증서를 발급 가능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000; text-align: start;&quot;&gt;&lt;b&gt;투명성&lt;/b&gt; : 발급받은 인증서는 공개되므로 누가 발급받았는지 알 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;작동 방식&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;도메인&lt;/b&gt; 소유권 확인 : 해당 도메인이 존재하는지 실제로 소유하고 있는지 확인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증서 발급&lt;/b&gt; : 소유권이 확인되면 해동 도메인에 대해서 SSL/TLS를 발급&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증서 설치 및 갱신&lt;/b&gt; : 발급된 인증서는 자동으로 웹서버에 설치되며, 인증서의 유효 기간(보통 90)이 만료되기 전에 자동 갱신&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;인증서 발급 방법&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;필자는 Windows10 pro, aws Linux 기준으로 작성되었습니다.&lt;/u&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;1. SSH 서버로 접속합니다.&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저는 Mobaxterm를 사용 중이니 다운 방법이 궁금하시면 다음 글을 통해 설치해주시면 될 것 같습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1720767006840&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;MobaXterm 다운 방법&quot; data-og-description=&quot;MobaXterm 다운하기 MobaXterm란? MobaXterm은 Windows 운영 체제에서 사용할 수 있는 다목적 원격 접속 및 시스템 관리 도구이다. 이 프로그램은 SSH, RDP, X11, SFTP 등 다양한 프로토콜을 지원하여 원격 서버&quot; data-og-host=&quot;back-stead.tistory.com&quot; data-og-source-url=&quot;https://back-stead.tistory.com/61&quot; data-og-url=&quot;https://back-stead.tistory.com/61&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b3L3E6/hyWzsdVdvE/2V0BWKK2R56qdGDKfiXYqK/img.png?width=793&amp;amp;height=461&amp;amp;face=0_0_793_461,https://scrap.kakaocdn.net/dn/r8MeN/hyWzBaQRng/fqJJ1lQLAkKazoDZV76GVK/img.png?width=793&amp;amp;height=461&amp;amp;face=0_0_793_461,https://scrap.kakaocdn.net/dn/dQYZ8C/hyWzzRDMRW/VzVb1pKwJLnWT6h9KBRegK/img.png?width=750&amp;amp;height=436&amp;amp;face=0_0_750_436&quot;&gt;&lt;a href=&quot;https://back-stead.tistory.com/61&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://back-stead.tistory.com/61&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b3L3E6/hyWzsdVdvE/2V0BWKK2R56qdGDKfiXYqK/img.png?width=793&amp;amp;height=461&amp;amp;face=0_0_793_461,https://scrap.kakaocdn.net/dn/r8MeN/hyWzBaQRng/fqJJ1lQLAkKazoDZV76GVK/img.png?width=793&amp;amp;height=461&amp;amp;face=0_0_793_461,https://scrap.kakaocdn.net/dn/dQYZ8C/hyWzzRDMRW/VzVb1pKwJLnWT6h9KBRegK/img.png?width=750&amp;amp;height=436&amp;amp;face=0_0_750_436');&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;MobaXterm 다운 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MobaXterm 다운하기 MobaXterm란? MobaXterm은 Windows 운영 체제에서 사용할 수 있는 다목적 원격 접속 및 시스템 관리 도구이다. 이 프로그램은 SSH, RDP, X11, SFTP 등 다양한 프로토콜을 지원하여 원격 서버&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;back-stead.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;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;2. Cerbot을 설치합니다.&lt;/h4&gt;
&lt;pre id=&quot;code_1720767127110&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo yum install certbot&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;886&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJsu1n/btsIytq8BiZ/99o5WUhcatEvG6X2oIf1S0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJsu1n/btsIytq8BiZ/99o5WUhcatEvG6X2oIf1S0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJsu1n/btsIytq8BiZ/99o5WUhcatEvG6X2oIf1S0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJsu1n%2FbtsIytq8BiZ%2F99o5WUhcatEvG6X2oIf1S0%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;886&quot; height=&quot;135&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;3. 인증 방법 선택&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;인증서를 발급받기 옵션에는 여러 가지가 존재합니다, --standalone , --webroot ,--apache 등등 여러 가지가 존재하지만, 스프링 서버에서 적용할 인증서를 발급받는 거기 때문에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;--standalone&lt;span&gt; 을 사용해 독립 실행을 하여 웹서버의 소유권을 확인해 인증서를 발급받을 것입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720767483851&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo certbot certonly --standalone&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 후 발급받은 도메인과, 이메일 주소를 입력하고 나면 정상적은 도메인을 가진 경우라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&amp;nbsp;/etc/letsencrypt/live/도메인/ 경로에 fullcahin.pem과 privkey.pem 가 발급되었다고 안내합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cL4wEM/btsIwWVDi3q/2fTQ1tuKtQOPoTNxKCcKV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cL4wEM/btsIwWVDi3q/2fTQ1tuKtQOPoTNxKCcKV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cL4wEM/btsIwWVDi3q/2fTQ1tuKtQOPoTNxKCcKV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcL4wEM%2FbtsIwWVDi3q%2F2fTQ1tuKtQOPoTNxKCcKV1%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;963&quot; height=&quot;82&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;82&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;4. keyStore 발급&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;스프링 부트에서 https를 사용하려면 이전에 발급받았던 pem 방식이 아닌 keySotre 방식으로 사용해야 합니다. 그래서 아래 명령어를 통해 keySotre 파일을 생성해야 합니다. 인증서가 발급된 디렉터리로 이동해 아래 명령어를 실행해 줍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 비밀번호를 입력하고 나면 keystore.12 파일이 생성된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1720767731072&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out keystore.p12 -name ttp -CAfile chain.pem -caname root&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;1441&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/24dxk/btsIyCOTtLg/un7dmVzxTHsZJmDnD55jy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/24dxk/btsIyCOTtLg/un7dmVzxTHsZJmDnD55jy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/24dxk/btsIyCOTtLg/un7dmVzxTHsZJmDnD55jy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F24dxk%2FbtsIyCOTtLg%2Fun7dmVzxTHsZJmDnD55jy0%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;1441&quot; height=&quot;84&quot; data-origin-width=&quot;1441&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;5. keystore 로컬에 다운로드&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 발급받은 keystore.12 인증서를 로컬에 다운로드하아야 합니다. 필자는 윈도를 사용하고 있어 아래 명령어를 통해 ssh 서버의 데이터를 다운로드하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;PowerShell을 실행하여 아래 명령어를 통해 파일을 다운로드하여줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1720768255941&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;scp -i [AWS PEM 경로] [사용자명]@[EC2_IP주소]:[keystore.12가 저장된 파일경로] [로컬에 다운 받을 경로]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;6. 스프링 부트 적용&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;resources의 파일을 저장하고, yaml파일아 다음과 같이 작성해 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;237&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFmqlY/btsIxhrFo78/BfM6aJATkGLY9c2uQzUaTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFmqlY/btsIxhrFo78/BfM6aJATkGLY9c2uQzUaTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFmqlY/btsIxhrFo78/BfM6aJATkGLY9c2uQzUaTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFmqlY%2FbtsIxhrFo78%2FBfM6aJATkGLY9c2uQzUaTK%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;64&quot; data-origin-width=&quot;237&quot; data-origin-height=&quot;64&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;pre id=&quot;code_1720768841133&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
	ssl:
    	key-store: classpath:ssl/keystore.p12
    	key-store-type: PKCS12
    	key-store-password: 변환후입력헀던비밀번호
    port: 8443&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;인증서.PNG&quot; data-origin-width=&quot;388&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8cTrk/btsIyuDAKC0/xN2NIN5t2kvyrT66ACPgM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8cTrk/btsIyuDAKC0/xN2NIN5t2kvyrT66ACPgM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8cTrk/btsIyuDAKC0/xN2NIN5t2kvyrT66ACPgM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8cTrk%2FbtsIyuDAKC0%2FxN2NIN5t2kvyrT66ACPgM0%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;388&quot; height=&quot;119&quot; data-filename=&quot;인증서.PNG&quot; data-origin-width=&quot;388&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 후 AWS에서 Spring 서버를 실행해 줍니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;인증섬ㄴㅇ.PNG&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC4rZA/btsIx6pIHxK/IhbwQPJWZgUVLaCK4GOkNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC4rZA/btsIx6pIHxK/IhbwQPJWZgUVLaCK4GOkNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC4rZA/btsIx6pIHxK/IhbwQPJWZgUVLaCK4GOkNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC4rZA%2FbtsIx6pIHxK%2FIhbwQPJWZgUVLaCK4GOkNK%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;1094&quot; height=&quot;575&quot; data-filename=&quot;인증섬ㄴㅇ.PNG&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;인증서가 올바르게 발급되어 실행되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;figure id=&quot;og_1720769497997&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Let's Encrypt&quot; data-og-description=&quot;Jun 24, 2024 NTP is critical to how TLS works, and now it&amp;rsquo;s memory safe at Let&amp;rsquo;s Encrypt. Read more May 30, 2024 Increasing defense against BGP attacks thanks to support from the Open Technology Fund. Read more May 1, 2024 Takeaways from Tailscale&amp;rsquo;s &quot; data-og-host=&quot;letsencrypt.org&quot; data-og-source-url=&quot;https://letsencrypt.org/&quot; data-og-url=&quot;https://letsencrypt.org/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://letsencrypt.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://letsencrypt.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Let's Encrypt&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jun 24, 2024 NTP is critical to how TLS works, and now it&amp;rsquo;s memory safe at Let&amp;rsquo;s Encrypt. Read more May 30, 2024 Increasing defense against BGP attacks thanks to support from the Open Technology Fund. Read more May 1, 2024 Takeaways from Tailscale&amp;rsquo;s&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;letsencrypt.org&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;figure id=&quot;og_1720769547482&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;company&quot; data-og-title=&quot;SSL 인증서란 무엇인가요? - SSL/TLS 인증서 설명 - AWS&quot; data-og-description=&quot;SSL/TLS 인증서란 무엇입니까? SSL/TLS 인증서는 시스템에서 ID를 확인하고 이후에 Secure Sockets Layer/전송 계층 보안(SSL/TLS) 프로토콜을 사용하여 다른 시스템에 대한 암호화된 네트워크 연결을 설정할&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/what-is/ssl-certificate/&quot; data-og-url=&quot;https://aws.amazon.com/ko/what-is/ssl-certificate/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SHI9b/hyWzsSvAy8/DvxxGaAWSTZyIyzi1LMls1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/di8N2n/hyWzzqzkzz/r8pleWHPNqe6IULY6NV4s1/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109,https://scrap.kakaocdn.net/dn/2epT6/hyWzxM33qb/wUmPa7onOtxDd5X5stWVl1/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/ssl-certificate/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/what-is/ssl-certificate/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SHI9b/hyWzsSvAy8/DvxxGaAWSTZyIyzi1LMls1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/di8N2n/hyWzzqzkzz/r8pleWHPNqe6IULY6NV4s1/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109,https://scrap.kakaocdn.net/dn/2epT6/hyWzxM33qb/wUmPa7onOtxDd5X5stWVl1/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350');&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;SSL 인증서란 무엇인가요? - SSL/TLS 인증서 설명 - AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SSL/TLS 인증서란 무엇입니까? SSL/TLS 인증서는 시스템에서 ID를 확인하고 이후에 Secure Sockets Layer/전송 계층 보안(SSL/TLS) 프로토콜을 사용하여 다른 시스템에 대한 암호화된 네트워크 연결을 설정할&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.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;</description>
      <category>Spring/SpringBoot</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/109</guid>
      <comments>https://back-stead.tistory.com/109#entry109comment</comments>
      <pubDate>Fri, 12 Jul 2024 16:29:21 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] @Qualifier 사용하기</title>
      <link>https://back-stead.tistory.com/108</link>
      <description>&lt;div style=&quot;position: relative; background-color: #ffffff; padding: 0px 25px 0px 60px; border: 1px solid #d9d9d9;&quot;&gt;
&lt;div style=&quot;position: absolute; top: -1px; left: 14px; width: 30px; height: 47px; background-color: #5c636a;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;position: absolute; top: 17px; left: 14px; width: 0; height: 0; border: 15px solid; border-color: transparent transparent #ffffff transparent;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Qualifier 사용해 보자!&lt;/h3&gt;
&lt;/div&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;div style=&quot;padding: 15px 20px; border-radius: 20px 0px; border: 2px solid #d9d9d9; line-height: 1.8;&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 종종 여러 구현체가 동일한 인터페이스를 구현하게 됩니다. 이럴 때 어떤 구현체를 사용할지에 대해서 지정해 주는 에노테이션입니다. 이번 포스팅에서는 사용방법에 대해서 알아보도록 하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;@Qualifier이란&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;@Qualifier 어노테이션은 DI할때 어떤 빈을 등록할 것인지를 명시해 줄 때 사용됩니다. 하지만&amp;nbsp; 기본적으로는 @Autowired을 통해서 DI를 하게 되지만, 동인 한 인터페이스를 가진 구현체가 있을대 @Autowired를 사용하게 되면 빈이 유일하지 않다는 NoUniqueBeanDefinitionException 예외를 발생하게 됩니다.&amp;nbsp; 이럴 때에 구현체를 지정해 주기 때문에 예외를 피할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉&amp;nbsp;   동일한&amp;nbsp;Interface를&amp;nbsp;가진&amp;nbsp;구현체의&amp;nbsp;대해서&amp;nbsp;명시할 때&amp;nbsp;사용됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;@ Qualifier 예시&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같이 이메일 전송에 관한 서비스의 두 개의 이메일 로직을 구현했다고 가정해 봅시다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718655322431&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class findEmail implements EmailService {
    public void send() {
        // 찾기 이메일 로직
    }
}


public class joinEmail implements EmailService {
   public void send() {
        // 가입 이메일 로직
    }
}

public interface EmailService {
  
  void send();
  
}&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;만약 이 상태에서 DI(의존성 주입)을 하게 되면 위에 설명했던 것처럼 예외가 발생합니다. 스프링 컨테이너에서 두 개의 빈이 등록되어 있으니 Spring빈은 스프링 컨테이너에서 어떤 빈이 해당 로직에서 어떤 구현체를 주입해줘야 하는지 모르기 때문에 NoUniqueBeanDefinitionException 예외가 발생하게 됩니다. 이때 명시적으로 'joinEmail' 구현체를 사용할 거야!라고 명시해 주면 Spring이 아! 이걸 사용하는구나라고 알 수 있어서 올바르게 빈을 등록해 줍니다.&amp;nbsp; @Qualifier 에노테이션은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718655598724&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Qualifier(&quot;JoinEmail&quot;)
public class joinEmail implements EmailService {
   public void send() {
        // 가입 이메일 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;클래스상단에 작성해 주면 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;DI 주입 방법&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;생성자 주입 시&lt;/b&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;b&gt;Lombook&lt;/b&gt;을 사용하고 있다면 final 필드에 붙여 작성해 주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1718655715100&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Qualifier(&quot;JoinEmail&quot;)
private final MailService mailService;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1718657461536&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;[오류] @RequiredArgsConstructor과 @Qualifier 이슈&quot; data-og-description=&quot;오류 발생롬복의 &amp;nbsp;@RequiredArgsConstructor을 통해 생성자 주입을 아주 편리하게 사용하고 있는 도중에동일한 인터페에스를 사용하는 여러 구현체가 있어서 &amp;nbsp;@Qualifier오류가 뜬것이 아닌가.......(┬┬&quot; data-og-host=&quot;back-stead.tistory.com&quot; data-og-source-url=&quot;https://back-stead.tistory.com/107&quot; data-og-url=&quot;https://back-stead.tistory.com/107&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bM5DBE/hyWloD4MWS/1IfR3XmOA6XeTWVVqXA9B1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/bAbHtl/hyWoLqRXpn/GpHrHbxiX4xKOLsjvIxkhK/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/bCKvhf/hyWloc1IVT/KqX2KxLl962sDkc4Lu5Xd0/img.png?width=1611&amp;amp;height=436&amp;amp;face=0_0_1611_436&quot;&gt;&lt;a href=&quot;https://back-stead.tistory.com/107&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://back-stead.tistory.com/107&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bM5DBE/hyWloD4MWS/1IfR3XmOA6XeTWVVqXA9B1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/bAbHtl/hyWoLqRXpn/GpHrHbxiX4xKOLsjvIxkhK/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/bCKvhf/hyWloc1IVT/KqX2KxLl962sDkc4Lu5Xd0/img.png?width=1611&amp;amp;height=436&amp;amp;face=0_0_1611_436');&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;[오류] @RequiredArgsConstructor과 @Qualifier 이슈&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오류 발생롬복의 &amp;nbsp;@RequiredArgsConstructor을 통해 생성자 주입을 아주 편리하게 사용하고 있는 도중에동일한 인터페에스를 사용하는 여러 구현체가 있어서 &amp;nbsp;@Qualifier오류가 뜬것이 아닌가.......(┬┬&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;back-stead.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;u&gt;(롬복 생성자 주입 오류시 해당 포스팅 참고 바랍니다!!)&lt;/u&gt;&lt;/i&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;기존 생성자 주입 시&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터에 에노테이션을 작성해 주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1718655912936&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public EmailController(@Qualifier(&quot;JoinEmail&quot;) MailService mailService) {
	this.mailService = mailService;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/spring-qualifier-annotation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Badldung&lt;/a&gt;&lt;/p&gt;</description>
      <category>Spring/SpringBoot</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/108</guid>
      <comments>https://back-stead.tistory.com/108#entry108comment</comments>
      <pubDate>Tue, 18 Jun 2024 05:52:24 +0900</pubDate>
    </item>
    <item>
      <title>[오류] @RequiredArgsConstructor과 @Qualifier 이슈</title>
      <link>https://back-stead.tistory.com/107</link>
      <description>&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;오류 발생&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;롬복의 &amp;nbsp;@RequiredArgsConstructor을 통해 생성자 주입을 아주 편리하게 사용하고 있는 도중에&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;동일한 인터페에스를 사용하는 여러 구현체가 있어서 &amp;nbsp;@Qualifier&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;오류밠갱.PNG&quot; data-origin-width=&quot;1611&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qhpPn/btsH1mAC0Ur/kD7hkQWS5MGxdKtGxeYRgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qhpPn/btsH1mAC0Ur/kD7hkQWS5MGxdKtGxeYRgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qhpPn/btsH1mAC0Ur/kD7hkQWS5MGxdKtGxeYRgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqhpPn%2FbtsH1mAC0Ur%2FkD7hkQWS5MGxdKtGxeYRgk%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;1384&quot; height=&quot;375&quot; data-filename=&quot;오류밠갱.PNG&quot; data-origin-width=&quot;1611&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오류가 뜬것이 아닌가.......(┬┬﹏┬┬)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 찾아보니.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;@RequiredArgsConstructor 어노테이션은 final이거나 @NonNull이 붙은 필드에 대해 생성자를 자동으로 만들어 주기 때문에 @Qualifier 어노테이션이 붙은 필드에 대해서는 @Qualifier 어노테이션을 생성자의 매개변수에 복사하지 않기 때문에 생성자를 주입할 수 없는 오류가 발생했던 것이다..&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;해결책&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Lombock.config 파일의 다음과 같은 설정을 추가해 주고 Rebuild Project를 해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1718657037983&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;해당 명령어를 작성해 주면 Lombok은 @Qualifier 어노테이션을 생성자의 매개변수에 복사 하게게됩니다.!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;실행.PNG&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPJWrK/btsH02vGTJa/L77lsehdSM1pbzKOs3KKfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPJWrK/btsH02vGTJa/L77lsehdSM1pbzKOs3KKfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPJWrK/btsH02vGTJa/L77lsehdSM1pbzKOs3KKfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPJWrK%2FbtsH02vGTJa%2FL77lsehdSM1pbzKOs3KKfk%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;1001&quot; height=&quot;42&quot; data-filename=&quot;실행.PNG&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정상적으로 실행된 것을 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;figure id=&quot;og_1718657306391&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;podo-dev : @RequiredArgsConstructor과 @Qualifier 같이 사용 시 이슈 해결법&quot; data-og-description=&quot;문제 Lombok의 @RequiredArgsConstructor는 final인 필수 멤버변수에 대해서, 자동으로 생성자를 만들어주는 어노테이션입니다. 문제는 다음과 같은 상황일때, 발생합니다. &amp;#96;&amp;#96;&amp;#96; java @RequiredArgsConstructor public c&quot; data-og-host=&quot;www.podo-dev.com&quot; data-og-source-url=&quot;https://www.podo-dev.com/blogs/224&quot; data-og-url=&quot;https://www.podo-dev.com/blogs/224&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.podo-dev.com/blogs/224&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.podo-dev.com/blogs/224&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;podo-dev : @RequiredArgsConstructor과 @Qualifier 같이 사용 시 이슈 해결법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문제 Lombok의 @RequiredArgsConstructor는 final인 필수 멤버변수에 대해서, 자동으로 생성자를 만들어주는 어노테이션입니다. 문제는 다음과 같은 상황일때, 발생합니다. ``` java @RequiredArgsConstructor public c&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.podo-dev.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;figure id=&quot;og_1718657335513&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Springboot dependency: two dependencies found and Qualifier ignored&quot; data-og-description=&quot;This is my class: @Repository @RequiredArgsConstructor @Slf4j public class ServeiTerritorialCatalegsClientRepositoryImpl implements ServeiTerritorialCatalegsClientRepository { @Qualifier(&amp;quot;&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/68379090/springboot-dependency-two-dependencies-found-and-qualifier-ignored&quot; data-og-url=&quot;https://stackoverflow.com/questions/68379090/springboot-dependency-two-dependencies-found-and-qualifier-ignored&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/X5o3g/hyWlkBGhWq/BkrL9cYHph5EPYorGy2DN1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/68379090/springboot-dependency-two-dependencies-found-and-qualifier-ignored&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/68379090/springboot-dependency-two-dependencies-found-and-qualifier-ignored&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/X5o3g/hyWlkBGhWq/BkrL9cYHph5EPYorGy2DN1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;Springboot dependency: two dependencies found and Qualifier ignored&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This is my class: @Repository @RequiredArgsConstructor @Slf4j public class ServeiTerritorialCatalegsClientRepositoryImpl implements ServeiTerritorialCatalegsClientRepository { @Qualifier(&quot;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>오류관련</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/107</guid>
      <comments>https://back-stead.tistory.com/107#entry107comment</comments>
      <pubDate>Tue, 18 Jun 2024 05:50:22 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] CSV 파일 Import 및 Export: 간편 가이드</title>
      <link>https://back-stead.tistory.com/106</link>
      <description>&lt;div style=&quot;position: relative; background-color: #ffffff; padding: 0px 25px 0px 60px; border: 1px solid #d9d9d9;&quot;&gt;
&lt;div style=&quot;position: absolute; top: -1px; left: 14px; width: 30px; height: 47px; background-color: #5c636a;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;position: absolute; top: 17px; left: 14px; width: 0; height: 0; border: 15px solid; border-color: transparent transparent #ffffff transparent;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MySQL에서 데이터를 Import 및 Export 해보자!&lt;/b&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;div style=&quot;padding: 15px 20px; border-radius: 20px 0px; border: 2px solid #d9d9d9; line-height: 1.8;&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;로컬 환경에서 작업한 데이터를 AWS 또는 다른 데이터베이스(DB)로 효율적으로 이관하는 방법을 LOAD DATA INFILE을 사용하는 방법이 있습니다. 이 방법은 데이터를 빠르게 로드할 수 있어 사용하는 방법을 한번 알아보도록 하겠습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;설정 확인하기&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;LOAD DATA INFILE을 사용하기 전에 먼저 환경 설정을 확인해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1718226685376&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SHOW VARIABLES LIKE 'local_infile';&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 명령어를 통해 해당 Mysql의 설정 정보를 확인해 줍니다. 만약&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Value가 Off로 되어있다면 아래 명령어를 입력해 On으로 변경해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1718226727440&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET GLOBAL local_infile=1;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; 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-filename=&quot;설정.PNG&quot; data-origin-width=&quot;156&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mY92F/btsHVVXzWS3/EYPokC5Myuveb7VdzPVcck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mY92F/btsHVVXzWS3/EYPokC5Myuveb7VdzPVcck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mY92F/btsHVVXzWS3/EYPokC5Myuveb7VdzPVcck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmY92F%2FbtsHVVXzWS3%2FEYPokC5Myuveb7VdzPVcck%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;215&quot; height=&quot;66&quot; data-filename=&quot;설정.PNG&quot; data-origin-width=&quot;156&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;설정파일 변경 시(Windows 기준)&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C:\ProgramData\MySQL\MySQL Server 8.0\my.ini&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;설정 파일에서 직접 '&lt;b&gt; &lt;span&gt;local_infile&lt;/span&gt;=&lt;/b&gt;&lt;span&gt;&lt;b&gt;1 &lt;/b&gt;' 명령어를 작성해 줄수도 있습니다. 위와 동일합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;WorkBench 설정&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ujnej/btsH3n5J2oJ/5PHU7qnCtiuwsQ3LKSDyVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ujnej/btsH3n5J2oJ/5PHU7qnCtiuwsQ3LKSDyVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ujnej/btsH3n5J2oJ/5PHU7qnCtiuwsQ3LKSDyVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fujnej%2FbtsH3n5J2oJ%2F5PHU7qnCtiuwsQ3LKSDyVK%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;523&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;workbench에서 접속할 connection의 edit connection에 들어갑니다.&lt;/li&gt;
&lt;li&gt;Advanced탭의 Others에 OPT_LOCAL_INFILE=1 입력합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;ini&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;OPT_LOCAL_INFILE=1&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Export&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Export란&lt;/b&gt; MySQL 데이터베이스에서 데이터를 외부 파일로 내보내는 작업을 말합니다. csv와 같은 데이터파을을 외부로 추출해 내는 것을 말합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1718226979988&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT column1, column2, ...
INTO OUTFILE '\경로\저장할 파일명.csv'
FIELDS TERMINATED BY ',' 
OPTIONALLY ENCLOSED BY '&quot;'
LINES TERMINATED BY '\n'
FROM 테이블명
WHERE condition;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; 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;b&gt;INTO OUTFILE :&lt;/b&gt; 결과를 지정된 파일로 Export 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FIELDS TERMINATED BY ', ' :&lt;/b&gt; 각 열을 쉼표로 구분합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OPTIONALLY ENCLOSED BY '&quot;' :&lt;/b&gt; 필드 값이 문자열일 경우 따옴표로 묶습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LINES TERMINATED BY '\n' :&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;파일 경로에서 (Windos 기준) 경로를 '&lt;b&gt;C:/ProgramData/MySQL/MySQL Server 8.0/Uploads/'&lt;/b&gt; 다음 경로를 작성 후 파일명을 작성해줘야 한다. 안 그러면 1209 에러가 뜨면서 사용할 수 없게 된다. &lt;span style=&quot;background-color: #ffffff; color: #5c5962; text-align: start;&quot;&gt;이 에러는 파일의 입출력 경로가 다를 경우에 발생하는 에러이다.&lt;span&gt; 기본적으로 mysql의 구성파일을 보면 다음과 같이 기본 경로가 설정된 것을 볼 수 있다. (수정하면 수정한 경로로 저장이 된다.)&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #5c5962; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #5c5962; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tFN8e/btsHXujVr0Z/KaPEkiYzSt1Yzp5zLb5pf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tFN8e/btsHXujVr0Z/KaPEkiYzSt1Yzp5zLb5pf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tFN8e/btsHXujVr0Z/KaPEkiYzSt1Yzp5zLb5pf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtFN8e%2FbtsHXujVr0Z%2FKaPEkiYzSt1Yzp5zLb5pf0%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;1008&quot; height=&quot;123&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;123&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;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Export 성공 시&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;데이터.PNG&quot; data-origin-width=&quot;1053&quot; data-origin-height=&quot;24&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJibRp/btsHWfuHdDT/w4AmSQckYXK7stsWxmHiLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJibRp/btsHWfuHdDT/w4AmSQckYXK7stsWxmHiLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJibRp/btsHWfuHdDT/w4AmSQckYXK7stsWxmHiLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJibRp%2FbtsHWfuHdDT%2Fw4AmSQckYXK7stsWxmHiLK%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;1053&quot; height=&quot;24&quot; data-filename=&quot;데이터.PNG&quot; data-origin-width=&quot;1053&quot; data-origin-height=&quot;24&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;765&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MxTfp/btsHVKIHi94/zxzaOAbkuNAFklhi6p8rqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MxTfp/btsHVKIHi94/zxzaOAbkuNAFklhi6p8rqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MxTfp/btsHVKIHi94/zxzaOAbkuNAFklhi6p8rqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMxTfp%2FbtsHVKIHi94%2FzxzaOAbkuNAFklhi6p8rqk%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;765&quot; height=&quot;573&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;573&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;정상적으로 약 18만 개의 데이터가 0.5초 만에 csv파일로 만들어진 것을 볼 수 있다. csv파일 안에 내용은 인코딩 형식이 맞지 않아 정상적으로 출력 도지 않지만 db에 바로 Import 할 거 기 때문에 굳이 설정까지 안 해도 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Import&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Import&lt;/b&gt;란 외부 파일에서 데이터를 MySQL 데이터베이스로 가져오는 작업을 말합니다. 지금 csv로 뽑은 데이터를 다시 다른 db로 넣는 행위라고 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Import 하는 방법이 두 가지가 있습니다. 워크벤치에서 Table Data Export 하는 방법과 LOAD DATA INFILE 명령어를 사용해서 하는 방법이 존재합니다. Table Data Export로 데이터를 Import를 하게 되면 outfile의 배해 속도가 엄청 차이 납니다. 그래서 여기서는 LOAD DATA INFILE을 사용해 Import를 해보도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Table Data Export&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;첫번쨰.PNG&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k1bKn/btsHV8vCvM6/LqpxKGf4zGoUC1km2BOptk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k1bKn/btsHV8vCvM6/LqpxKGf4zGoUC1km2BOptk/img.png&quot; data-alt=&quot;Table Data Export&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k1bKn/btsHV8vCvM6/LqpxKGf4zGoUC1km2BOptk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk1bKn%2FbtsHV8vCvM6%2FLqpxKGf4zGoUC1km2BOptk%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;751&quot; height=&quot;569&quot; data-filename=&quot;첫번쨰.PNG&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Table Data Export&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Table Data Export는 간단하게 버튼 클릭으로 데이터를 Import 할 수 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;LOAD DATA INFILE&lt;/h4&gt;
&lt;pre id=&quot;code_1718228673495&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LOAD DATA INFILE '/경로/저장한 파일명.csv'
INTO TABLE table_name
FIELDS TERMINATED BY ',' 
OPTIONALLY ENCLOSED BY '&quot;'
LINES TERMINATED BY '\n'
IGNORE 1 LINES;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; 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;b&gt;INTO TABLE :&lt;/b&gt; 데이터를 지정된 테이블로 Import 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FIELDS TERMINATED BY ',' :&lt;/b&gt; 각 열을 쉼표로 구분합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OPTIONALLY ENCLOSED BY '&quot;' :&lt;/b&gt; 필드 값이 문자열일 경우 따옴표로 묶습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LINES TERMINATED BY '\n' :&lt;/b&gt; 각 행을 개행 문자로 구분합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IGNORE 1 LINES :&lt;/b&gt; CSV 파일의 첫 번째 줄은 보통 헤더이므로 Import 시 무시할 수 있습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2초.PNG&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;32&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9VbAG/btsHVS0P1QD/3GKFMK8kxQsMpxiaT2U6B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9VbAG/btsHVS0P1QD/3GKFMK8kxQsMpxiaT2U6B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9VbAG/btsHVS0P1QD/3GKFMK8kxQsMpxiaT2U6B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9VbAG%2FbtsHVS0P1QD%2F3GKFMK8kxQsMpxiaT2U6B1%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;1152&quot; height=&quot;32&quot; data-filename=&quot;2초.PNG&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;32&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Import시에 약 18만 개의 데이터가 2초도 안돼서 삽입되는 것을 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;데이텀.PNG&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CWNIW/btsHWQnHN0U/DzxnvvvFQaIsnHKl6iucGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CWNIW/btsHWQnHN0U/DzxnvvvFQaIsnHKl6iucGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CWNIW/btsHWQnHN0U/DzxnvvvFQaIsnHKl6iucGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCWNIW%2FbtsHWQnHN0U%2FDzxnvvvFQaIsnHKl6iucGk%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;495&quot; height=&quot;179&quot; data-filename=&quot;데이텀.PNG&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;데이터도 db에 정상적으로 출력되는 것을 볼 수 있다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;aws rds에 로드 시!!&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;LOAD&amp;nbsp;DATA&amp;nbsp;LOCAL&amp;nbsp;INFILE&amp;nbsp;'C:/ProgramData/MySQL/MySQL&amp;nbsp;Server&amp;nbsp;8.0/Uploads/blog.csv'&lt;br /&gt;INTO&amp;nbsp;TABLE&amp;nbsp;recipe&lt;br /&gt;FIELDS&amp;nbsp;TERMINATED&amp;nbsp;BY&amp;nbsp;','&amp;nbsp;ENCLOSED&amp;nbsp;BY&amp;nbsp;'&quot;'&lt;br /&gt;LINES&amp;nbsp;TERMINATED&amp;nbsp;BY&amp;nbsp;'\n';&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;INFILE 종류&lt;/b&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;b&gt;LOAD DATA LOCAL INFILE:&lt;/b&gt; 클라이언트 로컬 파일 시스템에서 업로드.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LOAD DATA INFILE:&lt;/b&gt; 서버 파일 시스템에서 업로드.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;border-bottom: 3px solid  #5c636a; width: 100px; margin: 0 auto; position: relative; bottom: -15px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;RDS IMPORT&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 RDS에서 Import를 하기 위해서는 파라미터 그룹을 직접 변경해줘야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;파라미터 그룹을 생성하고 사용 중인 RDS와 연결을 한상태에서&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;파라미터그룹.PNG&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ynUkH/btsH2I3NUwn/5JevWSslk9kdsykK2c2Q21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ynUkH/btsH2I3NUwn/5JevWSslk9kdsykK2c2Q21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ynUkH/btsH2I3NUwn/5JevWSslk9kdsykK2c2Q21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FynUkH%2FbtsH2I3NUwn%2F5JevWSslk9kdsykK2c2Q21%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;1056&quot; height=&quot;539&quot; data-filename=&quot;파라미터그룹.PNG&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파라티터 그룹 &amp;rarr; 파라미터 편집 &amp;rarr; local_infile 검색 &amp;rarr; 값을 1로 변경&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;변경사항을 저장을 하고 RDS를 재부팅하고 나면 Infile 명령어를 통해서 import를 할 수 있을 것입니다.!&lt;/p&gt;</description>
      <category>MySQL</category>
      <category>E</category>
      <author>코드기록사</author>
      <guid isPermaLink="true">https://back-stead.tistory.com/106</guid>
      <comments>https://back-stead.tistory.com/106#entry106comment</comments>
      <pubDate>Tue, 18 Jun 2024 04:52:13 +0900</pubDate>
    </item>
  </channel>
</rss>