<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발 하는 묘</title>
    <link>https://myojun.tistory.com/</link>
    <description>want to be a Backend Software Developer</description>
    <language>ko</language>
    <pubDate>Sun, 12 Jul 2026 09:56:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>myo-dev</managingEditor>
    <image>
      <title>개발 하는 묘</title>
      <url>https://tistory1.daumcdn.net/tistory/7545723/attach/60f28a5b0f0049679934c6e73ec4ea67</url>
      <link>https://myojun.tistory.com</link>
    </image>
    <item>
      <title>[Data Structures] Stack(스택) 파해치기</title>
      <link>https://myojun.tistory.com/entry/Data-Structures-Stack%EC%8A%A4%ED%83%9D-%ED%8C%8C%ED%95%B4%EC%B9%98%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Stack(스택)이란?&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Stack이란 무엇인가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Stack(스택)&lt;/b&gt;은 가장 &lt;b&gt;기본적인 선형 자료구조&lt;/b&gt; 중 하나입니다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프링글스 통이나 접시를 쌓아 놓은 모습을 떠올리면 이해하기 쉽습니다.&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;span&gt;Stack은 데이터를 &lt;b&gt;한쪽 끝에서만 삽입하고 삭제&lt;/b&gt;할 수 있으며&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;가장 나중에 들어온 데이터가 가장 먼저 나가는 &lt;/span&gt;&lt;/b&gt;&lt;b&gt;LIFO(Last In First Out) 구조&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Stack의 특징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Stack은 &lt;b&gt;하나의 입구&lt;/b&gt;만 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;데이터를 넣을 때도 같은 위치, &lt;/span&gt;&lt;span&gt;꺼낼 때도 같은 위치를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;항상 가장 위(Top)의 데이터만 접근&lt;/b&gt;할 수 있으며&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;중간 데이터를 직접 접근하는 것은 지원하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Stack 시간 복잡도&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 46.3949%; height: 73px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 15.458%;&quot;&gt;&lt;b&gt;연산&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 20.011%;&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;text-align: center; height: 17px; width: 15.458%;&quot;&gt;&lt;span&gt;Push&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 20.011%;&quot;&gt;&lt;span&gt;O(1)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 15.458%;&quot;&gt;&lt;span&gt;Pop&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 20.011%;&quot;&gt;&lt;span&gt;O(1)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 15.458%;&quot;&gt;&lt;span&gt;Peek&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 20.011%;&quot;&gt;&lt;span&gt;O(1)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Stack은 &lt;b&gt;Top 위치만 관리하면 되기 때문에 대부분의 연산이 O(1)&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Java에서 Stack 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Java에서는 Stack이 인터페이스로 제공됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그 구현체로는 다음을 많이 사용합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 54.1845%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.0284%; text-align: center;&quot;&gt;&lt;b&gt;구현체&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.5059%; 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: 18.0284%; text-align: center;&quot;&gt;LinkedList&lt;/td&gt;
&lt;td style=&quot;width: 43.5059%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot;&gt;가장 기본적인 Stack구현&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.0284%; text-align: center;&quot;&gt;ArrayDeque&lt;/td&gt;
&lt;td style=&quot;width: 43.5059%; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot;&gt;가장 많이 사용하는 Stack 구현, 성능 우수&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적인 Stack이라면 성능이 가장 우수한 ArrayDeque를 이용하는 것이&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가장 권장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- ArrayDeque 권장 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Stack 구현체로 LinkedList도 사용할 수 있지만&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적인 Queue에서는 ArrayDeque가 더 권장&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이유는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;양방향 순환 배열(Circular Array) 기반&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;노드 생성 X&amp;nbsp;&amp;rarr; 메모리 효율 높음&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;객체 연결 비용 X&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;캐시(Locality of Reference) 효율 높음&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Queue와 Deque 연산 모두 O(1)에 가깝게 수행&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;LinkedList는 노드 객체를 계속 생성하고 연결해야 하므로&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메모리 사용량과 참고 비용&lt;/b&gt;이 증가합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 특별한 이유가 없다면 Queue는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;ArrayDeque를 사용하는 것이 가장 좋은 선택&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;h3 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Stack 주요 메서&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ push()&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터를 삽입&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;stack.push(10);
stack.push(20);
stack.push(30);

/*
TOP

30
20
10
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ pop()&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 위 데이터를 제거하면서 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782893007481&quot; class=&quot;java&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;stack.pop();

//30&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 Stack이 비어있다면 NoSuchElementException을 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ peek()&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 앞의 데이터를 제거하지 않고 조회&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782893007483&quot; class=&quot;java&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;stack.peek();

//30&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;데이터는 제거되지 않습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;만약&lt;b&gt;&lt;span&gt; Stack이&lt;/span&gt;&amp;nbsp;비어있다면 null을 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ size()&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현재 데이터 개수를 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782893208258&quot; class=&quot;java&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;stack.size();

//3&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5️⃣ isEmpty()&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;queue가 비어있는지 확인&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782893208258&quot; class=&quot;java&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;stack.isEmpty();

//true / false&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Deque에 대해서&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Deque(Double Ended Queue)&lt;/b&gt;는&lt;b&gt; 양쪽 끝에서 삽입과 삭제가 가능한 자료구조입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Stack처럼 사용할 수도, Queue처럼 사용할 수도 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782893965685&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Deque&amp;lt;Integer&amp;gt; stack = new ArrayDeque&amp;lt;&amp;gt;(); 

stack.push(10); 
stack.pop();&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1782893994816&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Deque&amp;lt;Integer&amp;gt; queue = new ArrayDeque&amp;lt;&amp;gt;(); 

queue.offer(10); 
queue.poll();&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;하나의 자료구조로 두 가지를 모두 구현할 수 있기 때문에 Java에서는 Deque 사용이 일반적입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 재귀와 Stack&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Stack&lt;/b&gt;은 &lt;b&gt;재귀 함수의 동작 원리&lt;/b&gt;와도 밀접하게 연결되어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;함수가 호출될 때마다 JVM은 함수의 실행 정보를 &lt;/span&gt;&lt;b&gt;&lt;span&gt;Call Stack에&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 저장합니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;함수 호출 시작&lt;/b&gt;&lt;br /&gt;main()&amp;nbsp;&amp;rarr; func(1)&amp;nbsp;&amp;rarr; func(2)&amp;nbsp;&amp;rarr; func(3)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;재귀 호출 종료 시&lt;/b&gt;&lt;br /&gt;&amp;nbsp;func(3) &amp;rarr; func(2) &amp;rarr; func(1) &amp;rarr; main()&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 마지막에 호출된 함수부터 차례대로 종료됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 재귀 함수 역시 Stack 구조를 기반으로 동작합니다.&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; 재귀 호출이 너무 깊어지면 Stack 공간을 모두 사용&lt;/b&gt;하게 되고 &lt;b&gt;StackOveflowError&lt;/b&gt;가 발생합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 id=&quot;%C2%B7%20Queue%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0%20%EC%A2%8B%EC%9D%80%20%EC%9C%A0%ED%98%95%20%EB%B0%8F%20%EC%83%81%ED%99%A9-1-8&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;middot; Stack을 사용하기 좋은 유형 및 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Stack은 &lt;/span&gt;&lt;b&gt;&lt;span&gt;가장 최근에 저장한 데이터를 먼저 처리해야 하는 상황&lt;/span&gt;&lt;/b&gt;&lt;span&gt;에서 가장 적합한 자료구조입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음과 같은 상황이라면 Stack 사용을 고려하는 게 좋습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;최근에 저장한 데이터를 먼저 처리해야 하는 경우&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;이전 상태로 되돌아가야 하는 경우&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;재귀 호출 구조를 직접 구현해야 하는 경우&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;깊이 우선 탐색을 수행하는 경우&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;짝을 이루는 데이터를 검사하는 경우&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;대표적인 코딩 테스트 문제 유형은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;괄호 검사&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;문자열 폭발&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;같은 문자 제거&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;DFS&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;Undo / Redo&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;브라우저 뒤로 가기&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;함수 호출 관리&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;후위 표기식 계산&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이처럼 Stack은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;최근 데이터 중심의 처리&lt;/span&gt;&lt;/b&gt;&lt;span&gt;, &lt;/span&gt;&lt;b&gt;&lt;span&gt;재귀적인 구조&lt;/span&gt;&lt;/b&gt;&lt;span&gt;, &lt;/span&gt;&lt;b&gt;&lt;span&gt;깊이 우선 탐색&lt;/span&gt;&lt;/b&gt;&lt;span&gt;에서 매우 강력한 자료구조입니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Algorithm &amp;amp; Data Structures/Data Structures</category>
      <category>Java</category>
      <category>Stack</category>
      <category>스택</category>
      <category>자료 구조</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/67</guid>
      <comments>https://myojun.tistory.com/entry/Data-Structures-Stack%EC%8A%A4%ED%83%9D-%ED%8C%8C%ED%95%B4%EC%B9%98%EA%B8%B0#entry67comment</comments>
      <pubDate>Wed, 1 Jul 2026 17:28:10 +0900</pubDate>
    </item>
    <item>
      <title>[Data Structures] Queue(큐) 파해치기</title>
      <link>https://myojun.tistory.com/entry/Data-Structures-Queue%ED%81%90-%ED%8C%8C%ED%95%B4%EC%B9%98%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Queue(큐) 란?&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Queue란 무엇인가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Queue(큐)&lt;/b&gt;는 가장 &lt;b&gt;기본적인 선형 자료구조&lt;/b&gt; 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맛집 줄을 서는 모습을 떠올리면 이해하기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 줄을 선 사람이 먼저 서비스를 받고, 나중에 온사람은 뒤에서 기다리는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;Queue&lt;/b&gt;는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;먼저 들어온 데이터가 먼저 나가는 FIFO(First-In-First-Out)&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Queue의 특징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue는 양쪽 끝에서 서로 다른 작업이 이뤄집니다.&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;앞쪽(front)&lt;/b&gt;: 데이터를 꺼내는 위치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;뒤쪽(rear)&lt;/b&gt;: 데이터를 삽입하는 위치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue에서 데이터는 항상 뒤에서 삽입되고 앞에서만 삭제됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구조덕에 입력 순서를 그대로 유지해야 하는 상황에서 많이 사용됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Queue 시간 복잡도&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 42.4402%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.6744%; text-align: center;&quot;&gt;&lt;b&gt;연산&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.1411%; 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: 22.6744%; text-align: center;&quot;&gt;삽입(Enqueue)&lt;/td&gt;
&lt;td style=&quot;width: 16.1411%; text-align: center;&quot;&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.6744%; text-align: center;&quot;&gt;삭제(Dequeue)&lt;/td&gt;
&lt;td style=&quot;width: 16.1411%; text-align: center;&quot;&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.6744%; text-align: center;&quot;&gt;가장 앞 조회(Peek)&lt;/td&gt;
&lt;td style=&quot;width: 16.1411%; text-align: center;&quot;&gt;O(1)&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;p data-ke-size=&quot;size16&quot;&gt;Queue는 맨 앞과 맨 뒤만 접근하기 때문에 대부분의 연산이 O(1)로 끝납니다.&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;중간 데이터를 탐색하거나 임의 접근(Random Access)은 지원하지 않습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Java에서 Queue 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서는 Queue가 인터페이스로 제공됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 구현체로는 다음을 많이 사용합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 66.1625%; height: 73px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 23.8683%; text-align: center; height: 22px;&quot;&gt;&lt;b&gt;구현체&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 38.8999%; text-align: center; height: 22px;&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: 23.8683%; text-align: center; height: 17px;&quot;&gt;LinkedList&lt;/td&gt;
&lt;td style=&quot;width: 38.8999%; text-align: center; height: 17px;&quot;&gt;가장 기본적인 Queue 구현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.8683%; text-align: center; height: 17px;&quot;&gt;ArrayDeque&lt;/td&gt;
&lt;td style=&quot;width: 38.8999%; text-align: center; height: 17px;&quot;&gt;가장 많이 사용하는 Queue 구현, 성능 우수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.8683%; text-align: center; height: 17px;&quot;&gt;PriorityQueue&lt;/td&gt;
&lt;td style=&quot;width: 38.8999%; text-align: center; height: 17px;&quot;&gt;우선순위 Queue&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적인 Queue라면 성능이 가장 우수한 ArrayDeque를 이용하는 것이&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- ArrayDeque 권장 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue 구현체로 LinkedList도 사용할 수 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적인 Queue에서는 ArrayDeque가 더 권장&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;양방향 순환 배열(Circular Array) 기반&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;노드 생성 X&amp;nbsp;&amp;rarr; 메모리 효율 높음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;객체 연결 비용 X&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐시(Locality of Reference) 효율 높음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Queue와 Deque 연산 모두 O(1)에 가깝게 수행&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LinkedList는 노드 객체를 계속 생성하고 연결해야 하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메모리 사용량과 참고 비용&lt;/b&gt;이 증가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 특별한 이유가 없다면 Queue는 &lt;b&gt;ArrayDeque를 사용하는 것이 가장 좋은 선택&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Queue 주요 메서드&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ offer()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터를 삽입&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;queue.offer(10);
queue.offer(20);
queue.offer(30);

//10 20 30&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;offer()는 데이터를 삽입하고 &lt;b&gt;boolean으로 삽입 성공 여부를 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ poll()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 앞의 데이터를 제거하면서 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782890275256&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queue.poll();

//10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 Queue가 비어있다면 null을 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ peek()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 앞의 데이터를 제거하지 않고 조회&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782890344193&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queue.peek();

//10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터는 제거되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;b&gt;Queue가 비어있다면 null을 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ size()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현재 데이터 개수를 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782890394279&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queue.size();

//3&lt;/code&gt;&lt;/pre&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;5️⃣ isEmpty()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;queue가 비어있는지 확인&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782890439167&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queue.isEmpty();

//true / false&lt;/code&gt;&lt;/pre&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;6️⃣ clear()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 데이터를 제거&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782890467439&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queue.clear();&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 비슷한 메서드 비교&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ offer와 add&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;둘 다 &lt;b&gt;데이터를 삽입&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;queue.offer(10);
queue.add(10);&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;span&gt;하지만 실패했을 때 차이가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt; offer()&lt;/b&gt;: false 반환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt; add()&lt;/b&gt;: Exception 발생&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일반적으로 &lt;b&gt;Queue에서는 offer() 사용을 권장&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;2️⃣ poll과 remove&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;둘 다&lt;b&gt; 데이터를 제거&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;queue.poll();
queue.remove();&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;하지만 Queue가 비어있을 때 차이가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;poll()&lt;/b&gt;: null 반환&lt;/li&gt;
&lt;li&gt;&lt;b&gt;remove()&lt;/b&gt;: NoSuchElementException&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서, &lt;b&gt;예외 처리를 하지 않아도 되는 poll()을 사용&lt;/b&gt;하는 경우가 대부분입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;3️⃣ peek와 element&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;둘 다&lt;b&gt; 가장 앞 데이터를 조회&lt;/b&gt;합니다.&lt;/span&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;peek()&lt;/b&gt;: null 반환&lt;/li&gt;
&lt;li&gt;&lt;b&gt;element()&lt;/b&gt;: NoShuchElementException&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;poll()과 마찬가지로 &lt;b&gt;peek() 사용&lt;/b&gt;이 일반적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Queue를 사용하기 좋은 유형 및 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue는 데이터가 들어온 순서를 유지하면서 처리해야 하는 상황에서 매우 적합한 구조입니다.&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;Queue&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;FIFO 구조가 필요한 경우&lt;/b&gt;&lt;/li&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;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;p data-ke-size=&quot;size16&quot;&gt;대표적인 코딩 테스트 문제 유형은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;BFS&lt;/b&gt;&lt;/li&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;li&gt;&lt;b&gt;확산, 번짐 등 시뮬레이션&lt;/b&gt;&lt;/li&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;li&gt;&lt;b&gt;카드 게임, 풍선 터뜨리기 등 순서 유지&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 Queue는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순서를 유지&lt;/b&gt;하면서 처리해야 하는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;단계적으로 확장&lt;/b&gt;되는 &lt;b&gt;탐색이나 시뮬레이션 문제&lt;/b&gt;에서 &lt;b&gt;아주 강력한 도구&lt;/b&gt;로 활용됩니다.&lt;/p&gt;</description>
      <category>Algorithm &amp;amp; Data Structures/Data Structures</category>
      <category>arraydeque</category>
      <category>Java</category>
      <category>Queue</category>
      <category>자료 구조</category>
      <category>큐</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/66</guid>
      <comments>https://myojun.tistory.com/entry/Data-Structures-Queue%ED%81%90-%ED%8C%8C%ED%95%B4%EC%B9%98%EA%B8%B0#entry66comment</comments>
      <pubDate>Wed, 1 Jul 2026 16:40:20 +0900</pubDate>
    </item>
    <item>
      <title>[Data Structures] Hash 파해치기 (Hash Table, HashMap, HashSet)</title>
      <link>https://myojun.tistory.com/entry/Data-Structures-Hash-%ED%8C%8C%ED%95%B4%EC%B9%98%EA%B8%B0-Hash-Table-HashMap-HashSet</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트에서 가장 많이 사용하는 자료구조 중 하나가 바로 Hash입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 다음 같은 상황에서 거의 필수적으로 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;420&quot; data-start=&quot;394&quot;&gt;&lt;b&gt;특정 값이 존재하는지 빠르게 확인해야 할 때&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;443&quot; data-start=&quot;421&quot;&gt;&lt;b&gt;문자열이나 숫자의 개수를 세야 할 때&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;458&quot; data-start=&quot;444&quot;&gt;&lt;b&gt;중복을 제거해야 할 때&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;476&quot; data-start=&quot;459&quot;&gt;&lt;b&gt;방문 여부를 저장해야 할 때&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서는 이러한 기능을 위해 HashMap과 HashSet을 주로 사용합니다.&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;그렇다면 HashMap은 왜 빠를까요?&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;이를 이해하려면 먼저 Direct Address Table이라는 개념부터 살펴봐야 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Direct Address Table&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Direct Address Table(직접 주소화 테이블)은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Key를 그대로 배열의 Index로 사용하는 자료구조&lt;/span&gt;&lt;/b&gt;&lt;span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 출석번호와 이름을 저장한다고 해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 40.2326%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.2464%; text-align: center;&quot;&gt;&lt;b&gt;출석 번호&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 26.9862%; 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: 13.2464%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 26.9862%; text-align: center;&quot;&gt;Kim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.2464%; text-align: center;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 26.9862%; text-align: center;&quot;&gt;Lim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.2464%; text-align: center;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 26.9862%; text-align: center;&quot;&gt;Lee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.2464%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 26.9862%; text-align: center;&quot;&gt;Park&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;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;blockquote data-ke-style=&quot;style3&quot;&gt;0&amp;nbsp;&lt;br /&gt;1 &amp;rarr; Park&lt;br /&gt;2&amp;nbsp;&amp;rarr; Kim&lt;br /&gt;3&amp;nbsp;&amp;rarr; Lim&lt;br /&gt;4&lt;br /&gt;5&amp;nbsp;&amp;rarr; Lee&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;찾고 싶은 학생이 있다면&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;students[5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처럼 바로 접근할 수 있으므로 접근 속도는 &lt;b&gt;O(1)&lt;/b&gt; 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 현실에서는 사용할 수 없는 문제점들이 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;- Direct Address Table의 문제점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;1️⃣ 공간 낭비&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;만약 학번을 Key로 사용한다면&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 40.2326%;&quot; border=&quot;1&quot; data-ke-style=&quot;style1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;학번&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;이름&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;202020&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;Kim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;202022&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;Lim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;202026&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;Lee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;202029&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;Park&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 크기는 최소 202030이 되어야 합니다.&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; 데이터는 4개뿐이지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 양의 공간이 비어 있게 되므로 메모리를 매우 비효율적으로 사용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 문자열을 Key로 사용할 수 없다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 Index는 정수만 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kim123&lt;/li&gt;
&lt;li&gt;park123&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 문자열 들은 배열의 index가 될 수 없습니다.&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;이러한 문제들은 Hash Table을 이용해 해결 가능합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Hash Table&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Hash Table이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Hash Table은 &lt;/span&gt;&lt;b&gt;&lt;span&gt;Key를 Hash Function을 이용하여 Index로 변환한 뒤 데이터를 저장하는 자료구조&lt;/span&gt;&lt;/b&gt;&lt;span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Key&amp;nbsp;&amp;rarr; Hash Function&amp;nbsp;&amp;rarr; Hash Value(Index)&amp;nbsp;&amp;rarr; Bucket&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;과정을 거쳐 데이터를 저장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;202020 &amp;rarr; hash() &amp;rarr; 7&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이라면 &lt;b&gt;Bucket[7]&lt;/b&gt;에 저장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, Direct Address Table처럼 Key를 그대로 사용하는 것이 아니라&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Hash Function이 계산한 결과를 Index로 사용합니다.&lt;/span&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Hash Function&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Hash Function은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;서로 다른 Key를 가능한 한 균등하게 분포된 값으로 변환하는 함수&lt;/span&gt;&lt;/b&gt;&lt;span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Key를 &lt;b&gt;정수(Hash Value)로 변환&lt;/b&gt;하는 역할을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;좋은 Hash Function의 조건은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;계산이 빠를 것&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;충돌(Collision)이 적을 것&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;결과 값이 고르게 분포될 것&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Java에서는 내부적으로 &lt;/span&gt;&lt;span&gt;hashCode()&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;b&gt;를 기반으로 Hash Value를 계산&lt;/b&gt;하며, 이를 통해 Bucket의 위치를 결정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Bucket&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Hash Table에서 데이터를 저장하는 공간을 &lt;/span&gt;&lt;b&gt;&lt;span&gt;Bucket(또는 Slot)이라고&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 부릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Bucket[0]&amp;nbsp;&amp;rarr; (202020, Kim)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처럼 하나의 Bucket에 Key-Value 쌍이 저장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 Key값에 의한 서로 다른 Value들끼리의 충돌을 막기 위해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Bucket에는 하나 이상의 데이터만 들어갈 수 있도록 설계합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Collision (충돌)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash Function은 항상 서로 다른 Index를 만들어주지는 않습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&quot;ABC&quot;&amp;nbsp;&amp;rarr; 3&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&quot;XYZ&quot;&amp;nbsp;&amp;rarr; 3&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 처럼 &lt;b&gt;서로 다른 Key가 동일한 Hash Value를 가지는 경우&lt;/b&gt;도 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상황을 &lt;b&gt;Collision(충돌)이라고&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash Table에서는 Collistion을 완전히 피할 수 없기 때문에 이를 처리하는 방법이 중요합니다.&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;Collision을 해결하는 방법은 두 가지가 있습니다.&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;Separate Chaining&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Open Addressing&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;Separate Chaining&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각&lt;b&gt; Bucket&lt;/b&gt;에 &lt;b&gt;Linked List(또는 Tree)&lt;/b&gt;를 두고, &lt;b&gt;여러 데이터를 저장하는 방식&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Java&lt;/b&gt;의 &lt;b&gt;HashMap은 기본적으로 이 방식을 사용&lt;/b&gt;하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 많아지면 트리 구조로 변환되기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ Open Addressing&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌이 발생하면 &lt;b&gt;다른 빈 Bucket을 찾아 데이터를 저장하는 방식&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로는 Linear Probing과 Quadratic Probing이 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 시간 복잡도&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Hash Table의 평균 시간복잡도&lt;/b&gt;는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 49.186%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 25.3488%;&quot;&gt;&lt;b&gt;&lt;span&gt;연산&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 23.7209%;&quot;&gt;&lt;b&gt;&lt;span&gt;시간복잡도&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 25.3488%;&quot;&gt;&lt;span&gt;검색&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 23.7209%;&quot;&gt;&lt;span&gt;O(1)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 25.3488%;&quot;&gt;&lt;span&gt;삽입&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 23.7209%;&quot;&gt;&lt;span&gt;O(1)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 25.3488%;&quot;&gt;&lt;span&gt;삭제&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 23.7209%;&quot;&gt;&lt;span&gt;O(1)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Hash Function을 이용해 &lt;/span&gt;&lt;span&gt;원하는 Bucket으로 바로 접근할 수 있기 때문에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;대부분의 연산을 매우 빠르게 수행&lt;/b&gt;할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;middot; Java의 Hash 자료 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서는 Hash Table을 직접 구현하지 않습니다.&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;HashMap&lt;/b&gt;과&lt;b&gt; HashSet&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- HashMap&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;HashMap이란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HashMap은 &lt;/span&gt;&lt;b&gt;&lt;span&gt;Key와 Value를 저장하는 자료구조&lt;/span&gt;&lt;/b&gt;&lt;span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Key &amp;rarr; Value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;형태로 데이터를 저장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;apple &amp;rarr; 3
banana &amp;rarr; 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처럼 저장할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;선언&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Map&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;추가&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;map.put(&quot;apple&quot;, 3);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;조회&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;map.get(&quot;apple&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;수정&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;map.put(&quot;apple&quot;, 5);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;같은 Key로 put 하면 기존 값이 덮어써집니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;삭제&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;map.remove(&quot;apple&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;Key 존재 여부 확인&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;map.containsKey(&quot;apple&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;평균 시간복잡도는 모두 O(1)입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- HashMap 코딩 테스트 빈출 유형&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 빈도수 계산&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1782750994211&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();

for (String s : arr) {
	map.put(s, map.getOrDefault(s, 0) + 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2️⃣ Key를 기준으로 데이터 저장&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;학생번호 &amp;rarr; 이름&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;장르 &amp;rarr; 총 재생 수&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;사용자 ID &amp;rarr; User 객체&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처럼 특정 Key를 기준으로 데이터를 저장할 때 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&amp;nbsp;3️⃣ Value에 여러 데이터를 저장하는 경우&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Value가 여러 정보를 가져야 한다면 클래스를 만들어 저장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class Edge {
    int dest;
    int weight;
}

Map&amp;lt;Integer, Edge&amp;gt; graph = new HashMap&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처럼 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;4️⃣ 커스텀 객체를 Key로 사용하는 경우&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HashMap의 Key로 객체를 사용할 경우 반드시&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;equals()&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;hashCode()&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1782751275418&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public boolean equals(Object obj) {
	if (obj instanceof Point) {
		Point other = (Point)obj;
		return other.x == this.x &amp;amp;&amp;amp; other.y == this.y;
	}
	return false;
}

@Override
public int hashCode() {
	return Objects.hash(this.x, this.y);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;를 오버라이드해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그렇지 않으면 같은 값을 가진 객체라도 서로 다른 Key로 인식하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;코딩테스트에서는 좌표(Point), 상태(State) 등을 Key로 사용할 때 자주 등장하는 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- HashSet&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;HashSet이란?&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HashSet은 &lt;/span&gt;&lt;b&gt;&lt;span&gt;중복을 허용하지 않는 자료구조&lt;/span&gt;&lt;/b&gt;&lt;span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Value만 저장하며 내부적으로 Hash Table을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;선언&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Set&amp;lt;Integer&amp;gt; set = new HashSet&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;추가&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;set.add(10);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;삭제&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;set.remove(10);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;포함 여부 확인&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;set.contains(10);&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;- HashSet 코딩테스트 빈출 유형&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;1️⃣ 중복 제거&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Set&amp;lt;Integer&amp;gt; set = new HashSet&amp;lt;&amp;gt;();

for (int n : arr) {
    set.add(n);
}&lt;/code&gt;&lt;/pre&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;&lt;span&gt;2️⃣ 방문 체크&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Set&amp;lt;Integer&amp;gt; visited = new HashSet&amp;lt;&amp;gt;();

visited.add(node);

visited.contains(node);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;DFS, BFS 문제에서도 자주 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;커스텀 객체를 저장하는 경우에도 HashMap과 마찬가지로 equals()와 hashCode()를 반드시 오버라이드해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; HashMap과 ArrayList 비교&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;HashMap&lt;/b&gt;이 항상 좋은 것은 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;ArrayList가 더 적합한 경우&lt;/b&gt;도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 55.814%; height: 105px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 26.6424%;&quot;&gt;&lt;b&gt;&lt;span&gt;ArrayList&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 29.0553%;&quot;&gt;&lt;b&gt;&lt;span&gt;HashMap&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 26.6424%;&quot;&gt;&lt;span&gt;인덱스로 접근&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 29.0553%;&quot;&gt;&lt;span&gt;Key로 접근&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 26.6424%;&quot;&gt;&lt;span&gt;순서 유지&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 29.0553%;&quot;&gt;&lt;span&gt;순서 없음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 26.6424%;&quot;&gt;&lt;span&gt;정렬 가능&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 29.0553%;&quot;&gt;&lt;span&gt;정렬 불가&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 26.6424%;&quot;&gt;&lt;span&gt;메모리 사용량이 적음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 29.0553%;&quot;&gt;&lt;span&gt;추가 메모리 사용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Key가 0부터 시작하는 연속된 정수&lt;/b&gt;라면 &lt;b&gt;배열&lt;/b&gt;이나 &lt;b&gt;ArrayList&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;문자열이나 임의의 Key&lt;/b&gt;라면 &lt;b&gt;HashMap&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;을 사용하는 것이 더 &lt;b&gt;효율적&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Algorithm &amp;amp; Data Structures/Data Structures</category>
      <category>hash</category>
      <category>HashMap</category>
      <category>HashSet</category>
      <category>Hashtable</category>
      <category>자료 구조</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/65</guid>
      <comments>https://myojun.tistory.com/entry/Data-Structures-Hash-%ED%8C%8C%ED%95%B4%EC%B9%98%EA%B8%B0-Hash-Table-HashMap-HashSet#entry65comment</comments>
      <pubDate>Tue, 30 Jun 2026 01:46:04 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] JVM과 Java의 실행 과정에 대해서 알아보자</title>
      <link>https://myojun.tistory.com/entry/JAVA-JVM%EA%B3%BC-Java%EC%9D%98-%EC%8B%A4%ED%96%89-%EA%B3%BC%EC%A0%95%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; JVM(Java Virtual Machine)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- JVM 이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM(Java Virtual Machine)&lt;/b&gt;이란&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Java 바이트 코드를 실행하기 위한 가상의 실행 환경(Virtual Machine)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 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;Java 프로그램은 CPU가 이해할 수 있는 기계어(Machine Code)를 직접 생성하지 않습니다.&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;JVM이 이해할 수 있는 바이트 코드&lt;/b&gt;를 생성하고, &lt;b&gt;JVM이 이를 OS에 맞는 기계어로 변환하여 실행&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Java 프로그램&amp;nbsp;&amp;rarr; JVM &amp;rarr; OS&amp;nbsp;&amp;rarr; CPU&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 구조로 실행됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- JVM이 필요한 이유&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 플랫폼 독립성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM이 존재하는 가장 큰 이유&lt;/b&gt;는 &lt;b&gt;플랫폼 독립성&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 프로그램은 Windows에서 컴파일 하면 Windows에서만 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Linux 에서 실행하려면 Linux 환경에서 다시 컴파일을 해야 합니다.&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;하지만 Java는 한번 컴파일한 .class 파일을&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;windows&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Linux&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;macOS&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떤 OS에서든 실행&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;각 OS마다 JVM만 존재한다면 Java 프로그램은 어디서든 사용가능&lt;/b&gt;해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 중재자&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM&lt;/b&gt;은 단순 실행 환경 제공을 넘어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Java와 운영체제(OS) 사이에서 중재자 역할&lt;/b&gt;을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 프로그램은 OS의 시스템 호출이나 하드웨어를 직접 다루지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 대신 &lt;b&gt;JVM이 대신 처리&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM&lt;/b&gt;이 Java와 OS 사이에서 &lt;b&gt;중재자 역할&lt;/b&gt;을 하는 이유는 무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 각 &lt;b&gt;OS마다 시스템 호출 방식, 메모리 관리 방식, 스레드 처리 방식 등이 서로 다르기 때문&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Java 프로그램이 직접 OS와 상호작용한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OS 마다 다른 코드를 작성해야 하고,&lt;/b&gt; &lt;b&gt;플랫폼 독립성&lt;/b&gt;이 깨지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM이 이 차이를 흡수함으로써 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;그 예시로&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;&lt;/li&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;li&gt;&lt;b&gt;네트워크 통신&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 작업은 JVM이 OS와 상호작용하여 수행합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- JDK, JRE, JVM?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK, JRE, JVM 이 세 가지는 많이 헷갈리는 개념이므로 정리해 보고 넘어가 보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ JVM&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서도 말했듯&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM은 Java 프로그램을 실행하는 가상 머신입니다.&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;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 관리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GC 수행&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행 엔진 제공&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣JRE (Java Runtime Environment)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JRE는 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;JRE는 다음과 같이 구성되어 있습니다.&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;JVM&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Java 표준 라이브러리 (String, List 등)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;3️⃣ JDK (Java Development Kit)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK는 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;구성은 다음처럼 구성되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JRE
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;JVM&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;javac&lt;/li&gt;
&lt;li&gt;javadoc&lt;/li&gt;
&lt;li&gt;jar&lt;/li&gt;
&lt;li&gt;기타 개발 도구&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Java 실행 과정&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 전반적인 Java 실행 과정&lt;/b&gt;&lt;/h3&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;Java 프로그램은 다음과 같은 순서로 실행됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Hello.java &amp;rarr; javac 컴파일 &amp;rarr; Hello.class(ByteCode) 생성 &amp;rarr; java Hello 실행 &amp;rarr; JVM 프로세스 생성&lt;br /&gt;&amp;rarr; OS로부터 메모리 할당 &amp;rarr; Runtime Data Area 생성 &lt;/b&gt;&lt;b&gt;&amp;rarr; Class Loader가. class 파일 동적 로딩 &lt;br /&gt;&amp;rarr; Execution Engine &amp;rarr; CPU가 기계어 실행&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나하나씩 살펴보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Java 소스 코드 작성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 진행되는 작업은 Java 소스코드를 작성하는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782728664600&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Hello { 
	public static void main(String[] args) { 
    	System.out.println(&quot;Hello&quot;); 
    } 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1080&quot; data-start=&quot;1056&quot; data-ke-size=&quot;size16&quot;&gt;작성한 파일의 확장자는. java입니다.&lt;/p&gt;
&lt;p data-end=&quot;1121&quot; data-start=&quot;1082&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 위 코드는 &lt;b&gt;Hello.java&lt;/b&gt;라는 파일로 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 상태에서는 아직 실행할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 Java 문법을 이해하지 못하기 때문입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. javac로 ByteCode 컴파일&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음으로는&lt;b&gt;&amp;nbsp;Java&amp;nbsp;컴파일러인&amp;nbsp;javac&lt;/b&gt;가&amp;nbsp;소스&amp;nbsp;코드를&amp;nbsp;컴파일합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782728798752&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;javac Hello.java&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 후에는 Hello&lt;b&gt;.class&lt;/b&gt; 파일이 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 생성되는 파일은 &lt;b&gt;기계어가 &lt;/b&gt;아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM이 이해할 수 있는 중간 언어인 ByteCode가 저장&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ByteCode&lt;/b&gt;는 &lt;b&gt;JVM을 위해 생성되는 중간 언어&lt;/b&gt;이기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.class 파일을 Windows, Linux, macOS 등 어떤 OS에서도 그대로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- C/C++과 Java의 차이&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 C와 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;C/C++은&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;소스코드 &amp;rarr; 컴파일러 &amp;rarr; 기계어&amp;nbsp;&amp;rarr; 실행&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 과정을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기계어는 OS와 CPU 환경에 따라 달리지므로&lt;b&gt; OS 마다 다시 컴파일&lt;/b&gt;해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Java는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&amp;nbsp;소스코드&amp;nbsp;&amp;rarr; javac &amp;rarr; ByteCode(.class) &amp;rarr; JVM&amp;nbsp;&amp;rarr; 기계어&amp;nbsp;&amp;rarr; 실행&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 과정을 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;OS 마다 JVM만 존재한다면 동일한 ByteCode를 그대로 실행할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 바로 Java의 대표적인 특징인&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Write Once, Run Anywhere(WORA)입니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. JVM 실행&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ByteCode 생성이 완료되면 다음 명령어로 프로그램을 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782729359813&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java Hello&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어를 실행하면 OS가 java(또는 java.exe) 프로세스를 생성하고 JVM이 시작됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- OS로부터 메모리 할당받기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM&lt;/b&gt;이 실행되면 가장 먼저 &lt;b&gt;OS로부터 프로그램 실행에 필요한 메모리를 할당받습니다.&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;2192&quot; data-start=&quot;2127&quot; data-ke-size=&quot;size16&quot;&gt;이 메모리는 Java 프로그램이 실행되는 동안&lt;/p&gt;
&lt;p data-end=&quot;2192&quot; data-start=&quot;2127&quot; data-ke-size=&quot;size16&quot;&gt;클래스 정보, 객체, 메서드 호출 정보 등을 저장하기 위해 &lt;b&gt;JVM이 사용하는 메모리 공간&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;2192&quot; data-start=&quot;2127&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2192&quot; data-start=&quot;2127&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;Java 실행에 특화된 메모리 영역&lt;/b&gt;이라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;2192&quot; data-start=&quot;2127&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2264&quot; data-start=&quot;2194&quot; data-ke-size=&quot;size16&quot;&gt;운영체제로부터 메모리를 할당받은 JVM은 내부적으로 자신의 메모리 구조인 &lt;b&gt;Runtime Data Area&lt;/b&gt;를 생성합니다.&lt;/p&gt;
&lt;p data-end=&quot;2264&quot; data-start=&quot;2194&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2264&quot; data-start=&quot;2194&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Runtime Data Area란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Runtime Data Area&lt;/b&gt;는 &lt;b&gt;JVM이 프로그램을 실행하기 위해 사용하는 메모리 공간&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 영역은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Runtime Data Area
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;Method Area&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Heap&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Java Stack&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PC Register&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Native Method Stack&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 영역에 대해 간단하게 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ Method Area&lt;/b&gt;&lt;/h4&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;li&gt;&lt;b&gt;static 변수&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Runtime Constant Pool&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등이 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ Heap&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 생성되는 공간입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782732573590&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;new Student();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 생성되는 대부분의 객체는 Heap에 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ Java Stack&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 호출 정보와 지역 변수가 저장되는 공간입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 호출이 종료되면 Stack Frame도 함께 제거됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ PC Register&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 실행 중인 ByteCOde 명령어의 위치를 저장하는 공간입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM은 이를 참고해서 다음에 실행할 명령어를 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5️⃣ Native Method Stack&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java가 아닌 Native 코드(C/C++)를 실행할 때 사용하는 메모리 영역입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Class Loader의 클래스 메모리 적재&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Class Loader의 동적 클래스 로딩&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Runtime Data Area가 준비되면 Class Loader가 작동&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Class Loader&lt;/b&gt;의 역할은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;필요한 .class 파일을 JVM 메모리로 동적으로 로드하는 것입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히 말하자면 &lt;b&gt;.class 파일을 읽어서 적재&lt;/b&gt;하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM이 클래스 메타데이터를 Method Area에 저장&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Dynamic Class Loading(동적 클래스 로딩) 이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dynamic Class Loading(동적 클래스 로딩)&lt;/b&gt;이란&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM이 필요한 순간에만 필요한 클래스를 메모리에 적재하는 것을&lt;/b&gt; 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 1000개의 클래스가 있다고 가정해 봅시다.&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;하지만 실제 실행에는 그중 20개 정도만 사용할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 모든 클래스를 프로그램 시작과 동시에 메모리에 올린다면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;불필요한 메모리 사용&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로그램 시작 시간 증가&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 단점이 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 JVM은 프로그램 시작 시 모든 클래스를 로드하지 않고,&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;필요한 시점에 필요한 클래스만 메모리에 적재하는 Dynamic Class Loading을 사용&lt;/b&gt;합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Execution Engine의 ByteCode 실행&lt;/b&gt;&lt;/h2&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;Execution Engine(실행 엔진)&lt;/b&gt;이 &lt;b&gt;ByteCode를 실행&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Execution Engine은 ByteCode를 그대로 실행하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 적절한 방식을 선택해서 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로&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;Interpreter(인터프리터)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JIT(Just In Time) Compiler&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Interpreter와 JIT Compiler&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ Interpreter&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Interpreter&lt;/b&gt;는 &lt;b&gt;ByteCode 명령어를 하나씩 해석하고 실행&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;319&quot; data-start=&quot;295&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 ByteCode가 100줄이라면,&lt;/p&gt;
&lt;p data-end=&quot;333&quot; data-start=&quot;321&quot; data-ke-size=&quot;size16&quot;&gt;1줄을 읽고 실행하고,&lt;/p&gt;
&lt;p data-end=&quot;363&quot; data-start=&quot;335&quot; data-ke-size=&quot;size16&quot;&gt;다음 1줄을 다시 읽고 실행하는 과정을 반복합니다.&lt;/p&gt;
&lt;p data-end=&quot;363&quot; data-start=&quot;335&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;495&quot; data-start=&quot;448&quot; data-ke-size=&quot;size16&quot;&gt;컴파일 과정이 필요 없기 때문에 &lt;b&gt;프로그램을 빠르게 시작할 수 있다는 장점&lt;/b&gt;이 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;495&quot; data-start=&quot;448&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;581&quot; data-start=&quot;497&quot; data-ke-size=&quot;size16&quot;&gt;하지만 같은 코드를 여러 번 실행하더라도&lt;/p&gt;
&lt;p data-end=&quot;581&quot; data-start=&quot;497&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매번 ByteCode를 다시 해석&lt;/b&gt;해야 하므로&lt;/p&gt;
&lt;p data-end=&quot;581&quot; data-start=&quot;497&quot; data-ke-size=&quot;size16&quot;&gt;해석 과정에서 &lt;b&gt;오버헤드가 발생&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-end=&quot;581&quot; data-start=&quot;497&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;625&quot; data-start=&quot;583&quot; data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;반복적으로 실행되는 코드에서는 성능이 다소 떨어질 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;625&quot; data-start=&quot;583&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ JIT Compiler&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interpreter를 사용했을 때의 단점을 보완하기 위해 JVM은 JIT Compiler를 함께 사용합니다.&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;JIT Compiler&lt;/b&gt;는 &lt;b&gt;자주 실행되는 ByteCode를 기계어로 컴파일하여 따로 저장해 둡니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 동일한 코드가 다시 실행되면 ByteCode를 다시 해석하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 만들어진 기계어를 바로 실행하기 때문에 훨씬 빠른 속도로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;518&quot; data-start=&quot;516&quot; data-ke-size=&quot;size16&quot;&gt;즉&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;600&quot; data-start=&quot;520&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;551&quot; data-start=&quot;520&quot;&gt;처음 실행은 &lt;b&gt;Interpreter&lt;/b&gt;가 담당하고,&lt;/li&gt;
&lt;li data-end=&quot;600&quot; data-start=&quot;552&quot;&gt;자주 실행되는 코드는 &lt;b&gt;JIT Compiler&lt;/b&gt;가 기계어로 변환하여 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;682&quot; data-start=&quot;602&quot; data-ke-size=&quot;size16&quot;&gt;이러한 방식 덕분에 Java는 실행이 계속될수록 성능이 향상되며&lt;/p&gt;
&lt;p data-end=&quot;682&quot; data-start=&quot;602&quot; data-ke-size=&quot;size16&quot;&gt;반복적으로 수행되는 작업에서는 네이티브 코드에 가까운 성능을 낼 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;682&quot; data-start=&quot;602&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;682&quot; data-start=&quot;602&quot; data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;자주 실행된다 &lt;/b&gt;라는 기준은 JVM이 실행 중 수집하는 프로파일링 정보를 통해 판단됩니다.&lt;br /&gt;이에&amp;nbsp;대한&amp;nbsp;내용은&amp;nbsp;다음&amp;nbsp;글에서&amp;nbsp;자세히&amp;nbsp;살펴보겠습니다.&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>class loader</category>
      <category>execution engine</category>
      <category>interpreter</category>
      <category>Java</category>
      <category>JIT Compiler</category>
      <category>JVM</category>
      <category>Runtime Data Area</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/64</guid>
      <comments>https://myojun.tistory.com/entry/JAVA-JVM%EA%B3%BC-Java%EC%9D%98-%EC%8B%A4%ED%96%89-%EA%B3%BC%EC%A0%95%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90#entry64comment</comments>
      <pubDate>Mon, 29 Jun 2026 21:39:12 +0900</pubDate>
    </item>
    <item>
      <title>[OpenAI] Response API와 Structured Outputs 이해하기</title>
      <link>https://myojun.tistory.com/entry/OpenAI-Response-API%EC%99%80-Structured-Outputs-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 기능을 서비스에 붙이다 보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 그럴듯한 문장을 잘 만들어주는 것만으로는 부족할 때가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 읽을 답변이라면 자연어 문장만으로도 충분하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버가 그 값을 저장하거나 DTO로 변환하거나&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프론트엔드 화면에 일정한 형태로 보여줘야 한다면&lt;/b&gt; 이야기가 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 모델에게&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학생들의 성적을 보내고, 분석하는 작업을 시킨다고 해봅시다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;국어 95점, 영어 80점, 수학 70점&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력을 토대로 &quot;이 성적을 분석해줘&quot; 라고 시켜봅시다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;평균은 81점이고, 가장 잘한 과목은 국어입니다.&lt;br /&gt;언어 영역(국어&amp;middot;영어)은 강점을 보이지만, 수학 학습을 보완하면 더욱 균형 잡힌 성적을 기대할 수 있습니다. &lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 이런식으로 정리를 해주는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 사람이 보기에는 충분히 이해가 되는 답변입니다.&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;하지만, 서버 코드 입장에서는 바로 쓰기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균 점수가 어디에 있는지, 가장 높은 과목은 어떤 값인지, 학습 코멘트는 어디부터 어디까지인지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 문자열에서 찾아야 하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 코드에서는 보통 JSON 방식으로 많이 표현하곤 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782322902363&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;average&quot;: 81,
  &quot;bestSubject&quot;: &quot;국어&quot;,
  &quot;worstSubject&quot;: &quot;수학&quot;,
  &quot;comment&quot;: &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;average&lt;/b&gt;를 숫자로 읽고, &lt;b&gt;bestSubject&lt;/b&gt;를 문자열로 읽는 등&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 결과를 DTO로 안정적으로 변환할 수 있습니다.&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;OpenAI 모델&lt;/b&gt;을 다룰때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 응답을 서비스에서 다루기 쉬운 JSON 구조로 받는 방법인&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Responses API&lt;/b&gt;와 &lt;b&gt;Structured Outputs&lt;/b&gt;에 대해서 공부해보고 정리해보는 시간을 가져보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 글은 OpenAI 공식 문서의 Repsonses API, Text generation, Structured Outputs 문서를 기반으로 정리했습니다.&lt;br /&gt;현 시점 문서의 최신 예시는 'gpt-5.5'를 사용하지만, 실제 서비스에서는 비용, 성능, 지연시간 등 각 상황에 맞게 모델을 선택하면 될것같습니다.&lt;/blockquote&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Responses API란 무엇인가&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Responses API란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Responses API&lt;/b&gt;는 &lt;b&gt;OpenAI 모델에 입력을 보내고 응답을 생성하는 API&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에는 직접적인 모델 요청에는 Responses API를 사용하도록 안내하고 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 생성뿐 아니라 구조화된 JSON 데이터, 도구 호출 같은 흐름도 이 API 안에서 다룰 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 엔드포인트는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;POST https://api.openai.com/v1/responses&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;/p&gt;
&lt;pre id=&quot;code_1782323755767&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;model&quot;: &quot;지정 모델&quot;,
  &quot;instructions&quot;: &quot;지시문.&quot;,
  &quot;input&quot;: &quot;입력값&quot;,
  &quot;max_output_tokens&quot;: &quot;출력 토큰 수 상한&quot;
}


------------------------------------------

{
  &quot;model&quot;: &quot;gpt-5.5&quot;,
  &quot;instructions&quot;: &quot;너는 학생 성적을 분석하는 선생님이야.&quot;,
  &quot;input&quot;: &quot;국어 95점, 영어 80점, 수학 70점&quot;,
  &quot;max_output_tokens&quot;: 1000
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 자주 보게 되는 필드는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;model, instructions, input, max_output_tokens&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 Structured Outputs를 쓸 땐 여기에 text.format이 추가됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 요청 필드 알아보기&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ model&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;model&lt;/b&gt;은 &lt;b&gt;어떤 OpenAI 모델을 사용할지 지정&lt;/b&gt;하는 필드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782323901825&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;model&quot;: &quot;gpt-5.5&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 코드에 모델명을 직접 받아두기보다는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정으로 분리하는 편이 좋습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782324056750&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;openai.model=gpt-5.5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 초기에는 비용과 속도가 적당한 모델로 시작하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 테스트에서 품질이 부족하다면 그때 더 강한 모델을 검토해보는것이 현실적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ instructions&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;instructions&lt;/b&gt;는 &lt;b&gt;모델이 응답을 만들때 따라야 하는 상위 지시문&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782324146393&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;instructions&quot;: &quot;너는 학생 성적을 분석하는 선생님이야.&quot;
}&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;&amp;nbsp;&lt;/p&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;너는 학생 성적을 분석하는 선생님이야. 입력된 과목별 점수를 바탕으로 평균, 가장 높은 과목, 가장 낮은 과목, 짧은 학습 조언을 같이 작성해줘.&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;instructions&lt;/b&gt;는 매 요청마다 공통으로 적용되는 규칙에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ input&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;input&lt;/b&gt;은 &lt;b&gt;모델에게 전달할 실제 입력값&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;instructions&lt;/b&gt;가&lt;b&gt; 매번 적용되는 규칙&lt;/b&gt;이라면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;input&lt;/b&gt;은 &lt;b&gt;매번 달라지는 사용자 입력&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782324437039&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;input&quot;: &quot;국어 95점, 영어 80점, 수학 70점&quot;
}&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;&amp;nbsp;&lt;/p&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;다음 학생의 성적을 분석해줘.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;국어 95점&lt;/b&gt;&lt;br /&gt;&lt;b&gt;영어 80점&lt;/b&gt;&lt;br /&gt;&lt;b&gt;수학 70점&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 모델이 &lt;b&gt;어떤 범위를 분석&lt;/b&gt;해야 하는지 더욱 &lt;b&gt;분명&lt;/b&gt;해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 사용자의 입력 앞뒤에 서비스에서 만든 설명을 함께 붙일 때는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자 입력과 시스템이 만든 지시문을 명확하게 구분&lt;/b&gt;하는것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ max_output_tokens&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;max_output_tokens&lt;/b&gt;는 &lt;b&gt;모델이 생성할 수 있는 출력 토큰 수의 상한&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782324641727&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;max_output_tokens&quot;: 1000
}&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;span style=&quot;color: #000000; text-align: start;&quot;&gt;이 값은 &lt;b&gt;비용&lt;/b&gt;과 &lt;b&gt;안정성&lt;/b&gt;에 모두 영향을 줍니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;너무 크게 잡으면 필요 이상으로 긴 응답&lt;/b&gt;이 나올 수 있고,&lt;b&gt; 비용도 늘어납니다. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;반대로 너무 작게 잡으면 응답이 중간에 끊길 수 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 이후에 나올 Structured Outputs에서는 이 점이 더 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 하나의 JSON으로 완성되어야 하는데, 출력 토큰이 부족하다면 닫는 중괄호가 생성되기 전에 응답이 끝날 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Structured Outputs란 무엇인가?&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Structured Outputs가 필요한 이유&lt;/b&gt;&lt;/h3&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;학생 성적을 분석해서 JSON으로 응답해줘.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 모델이 실제 JSON 처럼 보이는 응답을 줄 가능성이 높습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782398661733&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;average&quot;: 81,
  &quot;bestSubject&quot;: &quot;국어&quot;,
  &quot;worstSubject&quot;: &quot;수학&quot;,
  &quot;comment&quot;: &quot;수학 보완이 필요합니다.&quot;
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떨때는 마크다운 코드블록이 붙을 수도 있고, 필드명이 바뀔 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782398738365&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;avg&quot;: 81,
  &quot;topSubject&quot;: &quot;국어&quot;,
  &quot;lowSubject&quot;: &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;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;average, bestSubject, worstSubject, comment&lt;/b&gt;&lt;/blockquote&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;avg, topSubject, lowSubject&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 나오면 DTO 변환이 깨지거나 원하는 값을 꺼내질 못합니다.&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;그렇기 때문에 단순 &quot;JSON으로 줘&quot; 이런식으로 쓰는건 부족할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Structured Outputs의 핵심&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Structured Outputs&lt;/b&gt;는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발자가 원하는 응답 구조를 JSON Schema로 정의하고&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모델이 그 구조를 따르도록 요청하는 방식&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 JSON&amp;nbsp; 응답을 원한다고 가정해봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1782398964116&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;average&quot;: 81,
  &quot;bestSubject&quot;: &quot;국어&quot;,
  &quot;worstSubject&quot;: &quot;수학&quot;,
  &quot;comment&quot;: &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;JSON Schema&lt;/b&gt;로 표현한다면,&lt;/p&gt;
&lt;pre id=&quot;code_1782399047453&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;object&quot;,
  &quot;properties&quot;: {
    &quot;average&quot;: {
      &quot;type&quot;: &quot;integer&quot;,
      &quot;description&quot;: &quot;입력된 과목 점수의 평균&quot;
    },
    &quot;bestSubject&quot;: {
      &quot;type&quot;: &quot;string&quot;,
      &quot;description&quot;: &quot;가장 높은 점수를 받은 과목&quot;
    },
    &quot;worstSubject&quot;: {
      &quot;type&quot;: &quot;string&quot;,
      &quot;description&quot;: &quot;가장 낮은 점수를 받은 과목&quot;
    },
    &quot;comment&quot;: {
      &quot;type&quot;: &quot;string&quot;,
      &quot;description&quot;: &quot;성적 결과를 바탕으로 한 짧은 학습 조언&quot;
    }
  },
  &quot;required&quot;: [
    &quot;average&quot;,
    &quot;bestSubject&quot;,
    &quot;worstSubject&quot;,
    &quot;comment&quot;
  ],
  &quot;additionalProperties&quot;: false
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모델은 이 Schema에 맞게 JSON을 생성해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;서버는 이 JSON을 DTO로 변환해서 사용&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- JSON mode와 Structured Outputs의 차이&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JSON mode&lt;/b&gt;는 &lt;b&gt;모델이 유효한 JSON을 만들도록 돕는 기능&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 유효한 JSON이라고 해서 개발자가 원하는 필드와 타입을 가진다는 뜻은 아닙니다.&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;예를 들어 아래 응답은 JSON으로는 유효합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782399266261&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;result&quot;: &quot;평균은 81점입니다.&quot;
}&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782399292468&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;average&quot;: 81,
  &quot;bestSubject&quot;: &quot;국어&quot;,
  &quot;worstSubject&quot;: &quot;수학&quot;,
  &quot;comment&quot;: &quot;...&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Structured Outputs&lt;/b&gt;는 여기서 한 단계 더 나아갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 다음과 같습니다.&lt;/p&gt;
&lt;p 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;JSON mode&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유효한 JSON 생성에 유리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Structured Outputs&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유효한 JSON 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발자가 정의한 JSON Schema 준수&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 서비스에서 DTO로 변환해야 한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Structured Outputs&lt;/b&gt;가 &lt;b&gt;개발자의 의도대로 JSON 생성값들을 제한&lt;/b&gt;할 수 있기 때문에 더 적합합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; Response API에서 Structured Outputs 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response API에서 Structured Outputs를 사용하기 위해서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청에 text.format을 추가&lt;/b&gt;해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782399532846&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;model&quot;: &quot;gpt-5.5&quot;,
  &quot;instructions&quot;: &quot;너는 학생 성적을 분석하는 선생님이야.&quot;,
  &quot;input&quot;: &quot;국어 95점, 영어 80점, 수학 70점&quot;,
  &quot;max_output_tokens&quot;: 1000,
  &quot;text&quot;: {
    &quot;format&quot;: {
      ...
	}
  }
}&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Structured Outputs In Response API 구성 요소&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Structured Outputs에서 사용하는 중요한 요소들에 대해 알아봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1782401458791&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;text&quot;: {
    &quot;format&quot;: {
      &quot;type&quot;: &quot;json_schema&quot;,
      &quot;name&quot;: &quot;student_score_analysis&quot;,
      &quot;strict&quot;: true,
      &quot;schema&quot;: {
        &quot;type&quot;: &quot;object&quot;,
        &quot;properties&quot;: {
          &quot;average&quot;: {
            &quot;type&quot;: &quot;integer&quot;,
            &quot;description&quot;: &quot;입력된 과목 점수의 평균&quot;
          },
          &quot;bestSubject&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;description&quot;: &quot;가장 높은 점수를 받은 과목&quot;
          },
          &quot;worstSubject&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;description&quot;: &quot;가장 낮은 점수를 받은 과목&quot;
          },
          &quot;comment&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;description&quot;: &quot;성적 결과를 바탕으로 한 짧은 학습 조언&quot;
          }
        },
        &quot;required&quot;: [
          &quot;average&quot;,
          &quot;bestSubject&quot;,
          &quot;worstSubject&quot;,
          &quot;comment&quot;
        ],
        &quot;additionalProperties&quot;: false
      }
    }
  }
}&lt;/code&gt;&lt;/pre&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;1️⃣ text &amp;amp; format &amp;amp; type&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;text는 모델의 텍스트 출력 방식을 설정하는 영역&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반 텍스트&lt;/b&gt;를 받을 수도 있고, &lt;b&gt;Json Schema 기반의 구조화된 출력&lt;/b&gt;을 받을 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Structured Outputs를 사용할 때는 text.format 안에 Json Schema 설정을 넣습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782399718978&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;text&quot;: {
    	&quot;format&quot;: {
        	&quot;type&quot;: &quot;json_schema&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;format은 모델이 어떤 형식으로 텍스트 결과를 만들어야 하는지 지정하는 부분&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 텍스트 응답이 필요하면 일반 텍스트 형식&lt;/b&gt;을 쓰고,&lt;/p&gt;
&lt;pre id=&quot;code_1782399899096&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;format&quot;: {
    	&quot;type&quot;: &quot;text&quot;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JSON Schema를 따르는 응답이 필요하다면 json_schema&lt;/b&gt;를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782399954170&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;format&quot;: {
    	&quot;type&quot;: &quot;json_schema&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;type은 응답 형식을 지정&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;json_schema&quot;는 모델 응답을 JSON Schema 기반&lt;/b&gt;으로 받겠다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이건&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;일반 텍스트 말고, 내가 보내는 JSON Schema를 따르는 JSON으로 응답해줘&quot;&lt;/b&gt; 라고 하는것과 똑같은 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ name&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;이 응답 형식의 이름&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782400085545&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;name&quot;: &quot;student_score_analysis&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;이 schema가 &lt;b&gt;어떤 목적&lt;/b&gt;을 가진 구조인지 나타내는 메타 정보에 가깝다고 볼수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 이름은 기능의 목적이 잘 드러나도록 짓는것이 좋습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;student_score_analysis&lt;/b&gt;&lt;br /&gt;&lt;b&gt;review_summary&lt;/b&gt;&lt;br /&gt;&lt;b&gt;story_create_result&lt;/b&gt;&lt;br /&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 로그를 보거나 여러 schema를 관리할 때도 이름이 명확하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생했을때 추적하기가 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ strict&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1782400272146&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;strict&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;strict: true&lt;/b&gt;는 &lt;b&gt;모델이 schema를 더욱 엄격하게 따르도록 요청하는 설정&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Structured Outputs를 서비스 코드와 연결할 때는 보통 true로 설정&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 schema에 다음 필드들이 required로 정의되어 있다고 해봅시다&lt;/p&gt;
&lt;pre id=&quot;code_1782400431936&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;required&quot;: [
    &quot;average&quot;,
    &quot;bestSubject&quot;,
    &quot;worstSubject&quot;,
    &quot;comment&quot;
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 경우엔 required 안에 있는 네개의 필드 모두가 응답에 들어있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ schema&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;schema&lt;/b&gt;는 &lt;b&gt;실제 응답 구조를 정의하는 부분&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782400529503&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;schema&quot;: {
    &quot;type&quot;: &quot;object&quot;,
    &quot;properties&quot;: {
      &quot;average&quot;: {
        &quot;type&quot;: &quot;integer&quot;
      }
    },
    &quot;required&quot;: [&quot;average&quot;],
    &quot;additionalProperties&quot;: false
  }
}&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;schema에는 최상위 타입, 필드 목록, 각 필드의 타입, 필수 필드, 추가 필드 허용 여부 등을 정의합니다.&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;서버에서 사용할 DTO&lt;/b&gt;가 먼저 잡혀있다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;schema는 그 DTO와 같은 구조&lt;/b&gt;를 가져야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782400651012&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public record StudentScoreAnalysisResponse(
	int average,
    String bestSubject,
    String worstSubject,
    String comment
) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 DTO를 기반으로 구조를 잡았다면&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;Schema 또한 average, bestSubject, worstSubject, comment를 모두 포함해야 합니다.&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;DTO와 Schema가 어긋난다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- JSON Schema 구성요소&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782401378510&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;schema&quot;: {
        &quot;type&quot;: &quot;object&quot;,
        &quot;properties&quot;: {
          &quot;average&quot;: {
            &quot;type&quot;: &quot;integer&quot;,
            &quot;description&quot;: &quot;입력된 과목 점수의 평균&quot;
          },
          &quot;bestSubject&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;description&quot;: &quot;가장 높은 점수를 받은 과목&quot;
          },
          &quot;worstSubject&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;description&quot;: &quot;가장 낮은 점수를 받은 과목&quot;
          },
          &quot;comment&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;description&quot;: &quot;성적 결과를 바탕으로 한 짧은 학습 조언&quot;
          }
        },
        &quot;required&quot;: [
          &quot;average&quot;,
          &quot;bestSubject&quot;,
          &quot;worstSubject&quot;,
          &quot;comment&quot;
        ],
        &quot;additionalProperties&quot;: false
      }&lt;/code&gt;&lt;/pre&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;1️⃣ type&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;type&lt;/b&gt;은 &lt;b&gt;값의 타입을 뜻합니다.&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;객체&amp;nbsp;&amp;rarr; &quot;object&quot;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문자열 &amp;rarr; &quot;string&quot;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정수 &amp;rarr; &quot;integer&quot;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배열 &amp;rarr; &quot;array&quot;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1782401738524&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;average&quot;: {
    &quot;type&quot;: &quot;integer&quot;
  },
  &quot;bestSubject&quot;: {
    &quot;type&quot;: &quot;string&quot;
  },
  &quot;recommendedSubjects&quot;: {
    &quot;type&quot;: &quot;array&quot;,
    &quot;items&quot;: {
      &quot;type&quot;: &quot;string&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최상위 응답과 각 필드들에 대한 타입을 지정해둠으로써&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버에서 값을 다루기가 쉬워집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ properties&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;properties&lt;/b&gt;는 type이 객체 즉, object일때 &lt;b&gt;객체안에 들어갈 필드를 정의&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782401837262&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;properties&quot;: {
    &quot;average&quot;: {
      &quot;type&quot;: &quot;integer&quot;
    },
    &quot;bestSubject&quot;: {
      &quot;type&quot;: &quot;string&quot;
    },
    &quot;worstSubject&quot;: {
      &quot;type&quot;: &quot;string&quot;
    },
    &quot;comment&quot;: {
      &quot;type&quot;: &quot;string&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;이 Schema는 응답 객체에 average, bestSubject, worstSubject, comment라는 필드가 들어간다는 뜻입니다.&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;각 필드 안에는 타입뿐만 아니라 description, enum, 배열이라면 items 같은 규칙을 추가할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ required&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;required&lt;/b&gt;는 &lt;b&gt;반드시 포함되어야 하는 필드를 정의&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782401937098&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;required&quot;: [
    &quot;average&quot;,
    &quot;bestSubject&quot;,
    &quot;worstSubject&quot;,
    &quot;comment&quot;
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sturctured Outputs의 공식 문서 기준으로는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Schema의 필드를 모두 required에 넣는 방식이 기본&lt;/b&gt;으로 나와있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택적으로 표현하고 싶다면 required에서 빼는것보다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;null을 허용&lt;/b&gt;하는 방식으로 설계하는것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아예 빼버리면 서버측에서는 필드가 아예 없어지는 경우에는 파싱이 실패를 할수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 선택적으로 null을 허용하고 required에도 포함시키는 방식으로 설계하는것이 좋습니다.&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;예를 들어 comment를 null도 허용하도록 한다면&lt;/p&gt;
&lt;pre id=&quot;code_1782402207382&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;schema&quot;: {
  &quot;comment&quot;: {
    &quot;type&quot;: [&quot;string&quot;, &quot;null&quot;],
    &quot;description&quot;: &quot;성적 결과를 바탕으로 한 짧은 학습 조언. 만들 수 없으면 null&quot;
  }
},
&quot;required&quot;: [&quot;comment&quot;]&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ additionalProperties&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;additionalProperties&lt;/b&gt;는 &lt;b&gt;Schema에 정의되지 않은 필드를 허용할지 정합니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782402357218&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;additionalProperties&quot;: false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenAI Structured Outputs에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체&lt;/b&gt;마다 &lt;b&gt;&quot;additionalProperties&quot;: false&lt;/b&gt;를 설정해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정을 같이 해줘야 정의하지 않은 키가 응답에 섞이지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5️⃣ description &amp;amp; enum&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;description&lt;/b&gt;은 &lt;b&gt;필드의 의미를 설명하는 메타 정보&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782402564867&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;average&quot;: {
    &quot;type&quot;: &quot;integer&quot;,
    &quot;description&quot;: &quot;입력된 과목 점수의 평균&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;description&lt;/b&gt;은 서버 파싱에 직접적으로 쓰이지는 않지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모델이 해당 필드에 어떤 값을 넣어야 하는지 이해하는 데 도움&lt;/b&gt;을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &quot;comment&quot;에 다음 설명을 붙이면 모델은 단순 요약보다는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자의 설계의도에 맞게 문장을 생성해줄 가능성이 높아집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782402713318&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;comment&quot;: {
    &quot;type&quot;: &quot;string&quot;,
    &quot;description&quot;: &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;description&lt;/b&gt;을 달아놓는 편이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;enum&lt;/b&gt;은 &lt;b&gt;허용 가능한 값을 제한할 때 사용&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학생 성적 분석 결과에 등급을 추가한다고 가정한다면 다음과 같이 설계할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782402773165&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;grade&quot;: {
    &quot;type&quot;: &quot;string&quot;,
    &quot;enum&quot;: [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, &quot;F&quot;],
    &quot;description&quot;: &quot;평균 점수를 기준으로 계산한 등급&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에 grade는 반드시 A, B, C, D, F 중 하나여야 합니다.&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;보통 정해진 상태값, 카테고리, 분류 결과를 받아야 할때 enum을 사용하면 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6️⃣ array와 items&lt;/b&gt;&lt;/h4&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;&quot;type&quot;: &quot;array&quot;와 &quot;items&quot;&lt;/b&gt;를 함께 씁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 학생에게 학습 추천할 과목 목록을 받는다면 이렇게 설계할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782403031052&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;recommendedSubjects&quot;: {
    &quot;type&quot;: &quot;array&quot;,
    &quot;items&quot;: {
      &quot;type&quot;: &quot;string&quot;
    },
    &quot;description&quot;: &quot;보완 학습이 필요한 과목 목록&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답은 보통 다음처럼 나옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782403046375&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;recommendedSubjects&quot;: [&quot;수학&quot;, &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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가독성을 위해 중첩 구조를 너무 깊게 만들지 않는 편이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO와 Schema를 함께 관리 해야하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 객체에서 시작해서 필요한 만큼만 확장하는 방식이 안전합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 응답 구조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response API의 전체 응답에는 원하는 JSON만 들어있지 않을 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 우리가 보낸 요청을 읽고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 응답으로 응답 ID, 상태, 출력 배열, 사용량, 오류 정보 등을 함께 보내줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략적으로 이런식입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782403438709&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;resp_...&quot;,
  &quot;status&quot;: &quot;completed&quot;,
  &quot;error&quot;: null,
  &quot;incomplete_details&quot;: null,
  &quot;output&quot;: [
    {
      &quot;type&quot;: &quot;message&quot;,
      &quot;role&quot;: &quot;assistant&quot;,
      &quot;content&quot;: [
        {
          &quot;type&quot;: &quot;output_text&quot;,
          &quot;text&quot;: &quot;{\&quot;average\&quot;:81,\&quot;bestSubject\&quot;:\&quot;국어\&quot;,\&quot;worstSubject\&quot;:\&quot;수학\&quot;,\&quot;comment\&quot;:\&quot;수학 보완이 필요합니다.\&quot;}&quot;
        }
      ]
    }
  ],
  &quot;usage&quot;: {
    &quot;input_tokens&quot;: 100,
    &quot;output_tokens&quot;: 50,
    &quot;total_tokens&quot;: 150
  }
}&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;중요한 것은 output 배열에 항상 텍스트만 들어온다고 가정하면 안된다는 것입니다.&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;OpenAI 공식 문서에도 output 배열은 여러 항목을 가질수 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구 호출이나 reasoning 관련 항목이 포함될 수 있으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;output[0].content[0].text&lt;/b&gt; 위치에 텍스트가 있다고 단정짓지 말라고 설명하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenAI 공식 SDK에는 여러 텍스트 출력을 모아주는&lt;b&gt; output_text 편의 속성이나 메서드&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 HTTP 응답을 파싱한다면 &lt;b&gt;output 배열을 순회&lt;/b&gt;하면서 &lt;b&gt;type이 output_text인 값&lt;/b&gt;을 찾아야 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;Responses API&lt;/b&gt;와 &lt;b&gt;Structured Outputs&lt;/b&gt;가 무엇인지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;JSON Schema&lt;/b&gt;를 사용해 &lt;b&gt;AI 응답을 일정한 구조로 받는 방법&lt;/b&gt;을 살펴봤습니다.&lt;br /&gt;&lt;br /&gt;결국 Structured Outputs의 핵심은 사람이 읽기 위한 자연어 응답이 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 신뢰하고 사용할 수 있는 데이터를 만드는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 잘 활용하면 &lt;b&gt;AI 응답을 DTO로 변환하거나 DB에 저장&lt;/b&gt;하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프론트엔드에 일관된 형태로 전달하는 과정도 훨씬 안정적으로 구현&lt;/b&gt;할 수 있겠죠. &lt;br /&gt;&lt;br /&gt;다음 글에서는 Spring Boot와 Java를 사용해 Responses API를 직접 호출하고, JSON Schema와 DTO를 연결해 실제 서비스 코드에 적용하는 과정을 단계별로 구현해보는 시간을 가지면서 연습 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;lt;참고 공식 문서&amp;gt;&lt;br /&gt;- Responses API Reference: &lt;a href=&quot;https://developers.openai.com/api/reference/resources/responses/methods/create&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.openai.com/api/reference/resources/responses/methods/create&lt;/a&gt;&lt;br /&gt;- Text generation guide: &lt;a href=&quot;https://developers.openai.com/api/docs/guides/text&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.openai.com/api/docs/guides/text&lt;/a&gt;&lt;br /&gt;- Structured Outputs guide: &lt;a href=&quot;https://developers.openai.com/api/docs/guides/structured-outputs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.openai.com/api/docs/guides/structured-outputs&lt;/a&gt;&lt;br /&gt;- Latest model guide: &lt;a href=&quot;https://developers.openai.com/api/docs/guides/latest-model&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.openai.com/api/docs/guides/latest-model&lt;/a&gt;&lt;/blockquote&gt;</description>
      <category>BackEnd/AI &amp;amp; LLM</category>
      <category>GPT</category>
      <category>LLM</category>
      <category>openai</category>
      <category>Response API</category>
      <category>ResponseAPI</category>
      <category>Structured Ouputs</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/63</guid>
      <comments>https://myojun.tistory.com/entry/OpenAI-Response-API%EC%99%80-Structured-Outputs-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0#entry63comment</comments>
      <pubDate>Fri, 26 Jun 2026 01:25:33 +0900</pubDate>
    </item>
    <item>
      <title>[Data Structures] 배열(Array) 파해치기 (Static Array, Dynamic Array, ArrayList, 사용)</title>
      <link>https://myojun.tistory.com/entry/Data-Structures-%EB%B0%B0%EC%97%B4Array-%ED%8C%8C%ED%95%B4%EC%B9%98%EA%B8%B0-Static-Array-Dynamic-Array-ArrayList-%EC%82%AC%EC%9A%A9</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배열(Array)은&lt;/b&gt; 가장 기본적인 자료구조이자, 다양한 자료구조들의 기반이 되는 자료구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍을 하면서도 여러 군데에서 많이 찾아볼 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 ArrayList나 Python에서의 List 등 여러 군데에서 배열을 기반으로 구현되어 있는 자료구조들을 확인해 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 인덱스를 통해 특정 위치의 데이터에 매우 빠르게 접근할 수 있는 장점이 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입과 삭제 시에는 추가적인 이후 작업이 필요하기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상대적으로 비효율적이라는 특징을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 배열(Array)에 대해서 자세하게 파고들어봅시다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 배열(Array)이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배열(Array)&lt;/b&gt;이란 &lt;b&gt;동일한 타입의 데이터를 연속된 메모리 공간에 순차적으로 저장하는 자료구조&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 특징은 다음과 같습니다.&lt;/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;고정 크기(Fixed Size)&lt;/b&gt;&lt;/li&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;li&gt;&lt;b&gt;인덱스를 통한 빠른 접근&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 값을 저장할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연속된 메모리 공간&lt;/b&gt; 즉, &lt;b&gt;연속된 주소 공간&lt;/b&gt;에 순차적으로 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 배열은 특정 위치의 데이터를 매우 빠르게 찾을 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 배열이 빠른 이유&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;더욱 깊이 들어가 보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 변수는 &lt;b&gt;첫 번째 원소의 주소&lt;/b&gt;를 알고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 원소는 &lt;b&gt;동일한 크기&lt;/b&gt;를 가지고 있으므로 원하는 위치의 주소를 계산할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;pre id=&quot;code_1782236563136&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;arr[3]&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 주소는&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;주소 = 시작 주소 + (인덱스 * 자료형 크기)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같이 계산됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 배열 변수가 첫 번째 원소의 주소를 알고 있으므로 다음과 같이 원하는 값에 접근하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 변수 arr의 시작 주소가 1000이라고 가정하고, int 타입의 크기는 4byte 이므로,&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;arr[3] = 1000&amp;nbsp; + (3 * 4) = 1012&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같이 간단한 연산으로 접근 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 배열은 빠르게 원하는 값에 접근을 할수 있게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;b&gt; O(1)의 시간 복잡도&lt;/b&gt;를 가집니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 정적 배열(Static Array)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 정적 배열이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 배열은 생성할 때 크기가 결정되기 때문에 &lt;b&gt;생성 이후에 크기를 변경할 수 없습니다. &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782236911352&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] arr = new int[5];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 배열 arr은 크기가 5로 고정되고, 이후 크기를 늘리거나 줄일 수 없습니다.&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;이와 같은 배열들을 정적 배열(Static Array)라고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 정적 배열 장/단점&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;빠른 인덱스 접근 (O(1))&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연속된 저장으로 O(1) 시간에 인덱스로 직접 접근 가능&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;연속된 메모리 접근은 CPU 캐시 적중률이 높아짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 할당 X&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성 시 메모리가 한 번에 할당되므로, 이후 동적으로 할당될 게 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단순한 구현&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/h4&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성 시에 지정한 크기로 고정, 이후 변경 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;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;중간 삽입 및 삭제 시에는 다른 요소들을 이동해야 함&amp;nbsp;&amp;rarr; O(n) 소요&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;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 배열의 삽입과 삭제가 느린 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 연속된 메모리 공간을 사용하기 때문에 중간에 데이터를 삽입하면 &lt;b&gt;뒤쪽 데이터를 한 칸씩 이동&lt;/b&gt;시켜야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;pre id=&quot;code_1782237392279&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;10 20 30 40&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서 중간에 25를 삽입한다면&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;실질적으로는 idx=2에 25를 삽입하고, 남은 30과 40을 한 칸씩 뒤로 이동시켜야 합니다.&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;중간 삽입은 O(N)의 시간 복잡도&lt;/b&gt;를 가지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;삭제&lt;/b&gt; 또한 동일하게 뒤의 데이터를 앞으로 당겨야 하기 때문에 &lt;b&gt;O(N)의 시간 복잡도&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 정적 배열 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782239294720&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//배열 선언
int[] arr = new int[5];
String[] strArr = new String[10];
int[] arr2 = {1,2,3,4,5};

//접근
arr2[0];				// -&amp;gt; 1 
arr2[arr.length - 1];	// -&amp;gt; 5

//수정
arr[0] = 10;

//배열 초기화
Arrays.fill(arr, 5)

//배열 복사
int[] copy = Arrays.copyOf(arr, arr.lenth);

//부분 복사
Arrays.copyOfRange(arr, 2, 5);

//2차원 배열 Deep Copy
for (int i = 0; i &amp;lt; arr.length; i++) {
	copy[i] = Arrays.copyOf(arr[i], arr[0].length);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 동적 배열(Dynamic Array)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 동적 배열이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동적 배열&lt;/b&gt;은 &lt;b&gt;필요할 때 크기를 자동으로 늘릴 수 있는 배열&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Java의 ArrayList&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;C++의 Vector&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Python의 List&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 동적 배열을 기반으로 구현되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 크기가 4인 배열이 꽉 찼다면,&lt;/p&gt;
&lt;pre id=&quot;code_1782237711738&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[1][2][3][4]&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;보통 기존 크기의 2배로 증가시켜 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782237753635&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[1][2][3][4][][][][]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 자세하게는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기를 2배로 증가시킨 배열에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 데이터를 복사한 후, 기존 배열은 메모리에서 free 하는 과정을 거칩니다.&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;Resize&lt;/b&gt;라고 하고, &lt;b&gt;배열 크기를 두배로 늘리는 전략&lt;/b&gt;을 &lt;b&gt;Doubling&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; ArrayList&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서는 동적 배열을 직접 구현하지 않아도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java.util에 구현되어 있는 ArrayList를 사용할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- ArrayList 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 선언&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1782239721035&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; list1 = new ArrayList&amp;lt;&amp;gt;(); 
List&amp;lt;Integer&amp;gt; list2 = new ArrayList&amp;lt;&amp;gt;(List.of(1,2,3));&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 조회 및 수정&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1782239787857&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//조회
list.get(i);

//수정
list.set(i, 100);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ 삽입 및 삭제&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1782239862083&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//마지막 삽입
list.add(10);

//중간 삽입
list.add(2, 100);

//마지막 삭제
list.remove(list.size() - 1);

//중간 삭제
list.remove(2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 ArrayList가 Integer 타입이라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 2인&amp;nbsp; 요소를 지울 때는&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782240030591&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;list.remove(Integer.valueOf(2));&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 값이 2인 것과는 별개로 list의 index=2의 값이 지워질 가능성이 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ 2차원 ArrayList 및 복사&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1782240224443&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//2차원 ArrayList
List&amp;lt;List&amp;lt;Integer&amp;gt;&amp;gt; graph = new ArrayList&amp;lt;&amp;gt;();

//ArrayList 복사
List&amp;lt;Integer&amp;gt; copy = new ArrayList&amp;lt;&amp;gt;(list);

//Deep Copy
for (List&amp;lt;Integer&amp;gt; row : list) {
	copy.add(new ArrayList&amp;lt;&amp;gt;(row));
}&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- ArrayList 사용 시 주의 사항&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List를 순회할 때, 순회 도중 List의 원소 개수가 변경되어서는 안 됩니다.&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 data-ke-size=&quot;size16&quot;&gt;문자열의 길이가 2인 문자열을 제거한다고 할 때, 순회 도중 문자열을 제거하려고 하면 예외가 발생합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782240418630&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;(List.of(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;, &quot;four&quot;, &quot;five&quot;));

for (String line : list) {
    if (line.length() == 3) {
        list.remove(line);
    }
}

for (String line : list) {
    System.out.print(line);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; ConcurrentModificationException 발생&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;새로운 List를 생성하여 새 List에 저장&lt;/b&gt;을 해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782240490540&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;(List.of(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;, &quot;four&quot;, &quot;five&quot;));

List&amp;lt;String&amp;gt; resultList = new ArrayList&amp;lt;&amp;gt;();
for (String line : list) {
   if (line.length() == 3) continue; 
   resultList.add(line);
}

for (String line : resultList) {
    System.out.println(line);
}

//또는 Stream 사용
List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;(List.of(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;, &quot;four&quot;, &quot;five&quot;));

List&amp;lt;String&amp;gt; resultList = list.stream()
		.filter(s -&amp;gt; s.length() != 3)
        .collect(Collectors.toList());

resultList.forEach(System.out::println);&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 배열과 ArrayList 시간 복잡도&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 60.2326%; height: 102px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 28.9535%; text-align: center; height: 17px;&quot;&gt;&lt;b&gt;연산&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.2791%; text-align: center; height: 17px;&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: 28.9535%; text-align: center; height: 17px;&quot;&gt;access&lt;/td&gt;
&lt;td style=&quot;width: 31.2791%; text-align: center; height: 17px;&quot;&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 28.9535%; text-align: center; height: 17px;&quot;&gt;search&lt;/td&gt;
&lt;td style=&quot;width: 31.2791%; text-align: center; height: 17px;&quot;&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 28.9535%; text-align: center; height: 17px;&quot;&gt;insert back&lt;/td&gt;
&lt;td style=&quot;width: 31.2791%; text-align: center; height: 17px;&quot;&gt;Amortized O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 28.9535%; text-align: center; height: 17px;&quot;&gt;insert middle&lt;/td&gt;
&lt;td style=&quot;width: 31.2791%; text-align: center; height: 17px;&quot;&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 28.9535%; text-align: center; height: 17px;&quot;&gt;delete middle&lt;/td&gt;
&lt;td style=&quot;width: 31.2791%; text-align: center; height: 17px;&quot;&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Algorithm &amp;amp; Data Structures/Data Structures</category>
      <category>array</category>
      <category>ArrayList</category>
      <category>Dynamic Array</category>
      <category>static array</category>
      <category>동적 배열</category>
      <category>배열</category>
      <category>정적 배열</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/62</guid>
      <comments>https://myojun.tistory.com/entry/Data-Structures-%EB%B0%B0%EC%97%B4Array-%ED%8C%8C%ED%95%B4%EC%B9%98%EA%B8%B0-Static-Array-Dynamic-Array-ArrayList-%EC%82%AC%EC%9A%A9#entry62comment</comments>
      <pubDate>Wed, 24 Jun 2026 04:04:38 +0900</pubDate>
    </item>
    <item>
      <title>[Data Structures] 자료구조란 ? (선형/비선형 자료구조, 시간 복잡도, 공간 복잡도)</title>
      <link>https://myojun.tistory.com/entry/Data-Structures-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EB%9E%80-%EC%84%A0%ED%98%95%EB%B9%84%EC%84%A0%ED%98%95-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84-%EA%B3%B5%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 들어가며&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;어떤 프로젝트를 진행하든, 취업을 위한 코딩테스트를 준비하든, 그리고 CS 면접을 준비하든 항&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;상 빠지지 않고 등장하는 개념이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;바로 &lt;/span&gt;&lt;b&gt;&lt;span&gt;자료구조(Data Structure)입니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;자료구조는 단순히 코딩테스트를 위한 지식이 아닙니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;실제 개발에서도 데이터를 어떻게 저장하고 관리할지 결정하는 중요한 요소이며, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;어떤 자료구조를 선택하느냐에 따라 프로그램의 성능과 효율이 크게 달라질 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또한 자료구조와 밀접하게 연관된 개념인 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;시간 복잡도(Time Complexity)와&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;공간 복잡도(Space Complexity)&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 역시 함께 이해해야 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;더욱 효율적인 코드를 작성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 글에서는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;앞으로 다룰 배열(Array), 연결 리스트(Linked List), 스택(Stack), 큐(Queue), 해시(Hash), 트리(Tree), 그래프(Graph) 등을 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;공부하기에 앞서, 자료구조와 시간 복잡도, 공간 복잡도에 대해 먼저 정리해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 자료구조란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조란&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터를 저장하고 관리하는 방식&lt;/b&gt;을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램에서 데이터를 단순히 저장하는 것에서 끝나는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 어떤 형태로 구성하고 어떻게 접근할 것인지에 따라 성능은 크게 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;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;li&gt;&lt;b&gt;순서대로 데이터를 관리해야 할 때&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 따라 적합한 자료구조를 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 자료구조란 데이터를 효율적으로 저장하고 관리하기 위한 방법이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적절한 자료구조를 선택하는 것은 프로그램의 성능을 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 특정 자료구조를 기반으로 동작하는 알고리즘도 있기에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조에 대한 이해는 알고리즘 학습과도 깊은 연관이 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 자료구조의 종류&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조는 크게 선형 자료구조와 비선형 자료구조로 나눌 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 선형 자료구조&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선형 자료구조&lt;/b&gt;란 데이터가 &lt;b&gt;일렬로 배치되어 순서대로 접근 가능한 자료구조&lt;/b&gt;를 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예시로는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;배열(Array)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;연결 리스트(Linked List)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스택(Stack)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;큐(Queue)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;우선순위큐(Priority Queue)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등이 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 비선형 자료구조&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비선형 자료구조&lt;/b&gt;란 데이터가 &lt;b&gt;계층적 또는 네트워크의 형태로 배치된 자료구조&lt;/b&gt;를 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;트리(Tree)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;그래프(Graph)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비선형 자료구조는 데이터 간의 관계를 표현하거나 탐색하는 문제를 해결할 때 많이 사용됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 시간 복잡도 (Time Complexity)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 시간 복잡도란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시간 복잡도(Time Complexity)&lt;/b&gt;는 &lt;b&gt;입력 데이터의 크기가 증가함에 따라 연산 횟수가 얼마나 증가하는지를 나타내는 척도&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 &lt;b&gt;Big-O 표기법&lt;/b&gt;을 사용하여 표현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 시간 복잡도는 다음과 같습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 47.5581%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&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: 27.5581%; 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%; text-align: center;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; text-align: center;&quot;&gt;일정한 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;O(log N)&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; text-align: center;&quot;&gt;로그 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;O(N)&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; text-align: center;&quot;&gt;선형 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;O(N log N)&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; text-align: center;&quot;&gt;선형 로그 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; text-align: center;&quot;&gt;제곱 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;O(2ⁿ)&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; text-align: center;&quot;&gt;지수 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;O(N!)&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; 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;각 시간 복잡도는 특정 알고리즘에서 자주 등장합니다.&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; O(1): &lt;/b&gt;배열 인덱스 접근, Hash Table 조회&lt;/li&gt;
&lt;li&gt;&lt;b&gt; O(log N): &lt;/b&gt;이진 탐색(Binary Search) 등&lt;/li&gt;
&lt;li&gt;&lt;b&gt; O(N): &lt;/b&gt;배열 순회, 선형 탐색&lt;/li&gt;
&lt;li&gt;&lt;b&gt; O(N log N): &lt;/b&gt;병합 정렬, 퀵 정렬&lt;/li&gt;
&lt;li&gt;&lt;b&gt; O(N&amp;sup2;): &lt;/b&gt;버블 정렬, 선택 정렬, 삽입 정렬&lt;/li&gt;
&lt;li&gt;&lt;b&gt; O(2ⁿ): &lt;/b&gt;부분 집합 탐색, 백트래킹&lt;/li&gt;
&lt;li&gt;&lt;b&gt; O(N!): &lt;/b&gt;순열 생성, 모든 경우의 수 탐색&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 배열에서 특정 인덱스에 접근하는 것은 O(1)의 시간 복잡도를 가지며, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;배열 전체를 탐색하는 것은 O(N)의 시간 복잡도를 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 같은 문제를 해결하더라도 자료구조와 알고리즘에 따라 성능 차이가 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 시간 복잡도가 중요한 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;시간 복잡도&lt;/b&gt;는 단순한 이론이 아니라 실제 개발과 코딩테스트에서 매우 중요한 기준이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 코딩테스트에서는 입력 크기에 따라 사용할 수 있는 알고리즘이 어느 정도 정해져 있습니다.&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)가 100,000이라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통의 코딩 테스트는 &lt;b&gt;10⁸에서 시간 초과&lt;/b&gt;가 발생하기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O(N&amp;sup2;)의 알고리즘은 시간 초과가 발생할 가능성이 높습니다.&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;O(N log N), O(N), O(log N) 수준의 알고리즘&lt;/b&gt;을 고려해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 입력 크기가 작다면 완전 탐색과 같이 비교적 느린 알고리즘도 충분히 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리해 보자면 확정은 아니지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략적으로는 다음과 같이 생각할 수 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 58.4887%;&quot; border=&quot;1&quot; data-ke-style=&quot;style1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 41.5648%;&quot;&gt;&lt;b&gt;입력 크기(N)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 81.1736%;&quot;&gt;&lt;b&gt;고려할 시간 복잡도&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 41.5648%;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 81.1736%;&quot;&gt;O(N!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 41.5648%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 81.1736%;&quot;&gt;O(N&amp;sup3;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 41.5648%;&quot;&gt;1,000&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 81.1736%;&quot;&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 41.5648%;&quot;&gt;100,000&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 81.1736%;&quot;&gt;O(N log N), O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 41.5648%;&quot;&gt;1,000,000 이상&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 81.1736%;&quot;&gt;O(N), O(log N)&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;p 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;/li&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;/ol&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 공간 복잡도(Space Complexity)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공간 복잡도는 프로그램이 실행되는 동안 사용하는 메모리의 양을 나타내는 척도입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 문제를 해결하기 위해 얼마나 많은 메모리가 필요한지를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 배열 하나를 생성하면&lt;/p&gt;
&lt;pre id=&quot;code_1782229749035&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] arr = new int[n];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 크기에 비례하는 메모리가 필요하므로 공간 복잡도는 O(N)이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 몇 개의 변수만 사용할 경우엔&lt;/p&gt;
&lt;pre id=&quot;code_1782229789993&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int max = 0;
int sum = 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공간 복잡도는 O(1)이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 복잡도뿐 아니라 공간 복잡도 역시 중요한 요소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 상황에 따라 둘 사이의 균형을 고려해 볼 필요도 있습니다.&lt;/p&gt;</description>
      <category>Algorithm &amp;amp; Data Structures/Data Structures</category>
      <category>Big O</category>
      <category>공간 복잡도</category>
      <category>비선형 자료구조</category>
      <category>선형 자료구조</category>
      <category>시간 복잡도</category>
      <category>자료구조</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/61</guid>
      <comments>https://myojun.tistory.com/entry/Data-Structures-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EB%9E%80-%EC%84%A0%ED%98%95%EB%B9%84%EC%84%A0%ED%98%95-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84-%EA%B3%B5%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84#entry61comment</comments>
      <pubDate>Wed, 24 Jun 2026 01:43:06 +0900</pubDate>
    </item>
    <item>
      <title>[Moassam] 좋아요/북마크 동시성 정합성 테스트 3 - K6 부하 테스트 (성능 개선 후)</title>
      <link>https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-3-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-%ED%9B%84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;middot; 들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서는 ON CONFLICT DO NOTHING과 Atomic Update를 적용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요와 북마크의 count 변경 기준을 실제 데이터 변경 여부로 수정했습니다.&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;a href=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-2-%EC%A0%95%ED%95%A9%EC%84%B1-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-ON-CONFLICT-Atomic-Update&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전글 바로가기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781289262984&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;[Moassam] 좋아요/북마크 동시성 정합성 테스트 2 - 정합성 개선하기 (ON CONFLICT, Atomic Update)&quot; data-og-description=&quot;&amp;middot; 들어가며이전 글에서는 K6 부하 테스트를 통해 좋아요와 북마크 기능에서 동시성 문제로 인해 데이터 정합성이 맞지 않는 문제가 발생하는 것을 확인했습니다.이전글 바로가기 [Moassam] 좋아요&quot; data-og-host=&quot;myojun.tistory.com&quot; data-og-source-url=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-2-%EC%A0%95%ED%95%A9%EC%84%B1-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-ON-CONFLICT-Atomic-Update&quot; data-og-url=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-2-%EC%A0%95%ED%95%A9%EC%84%B1-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-ON-CONFLICT-Atomic-Update&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b6rPn2/dJMb83kBtNi/a0G3pnsisoCARx5krkaJC0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bdV9Lw/dJMb86Pae6j/Fsb6xzo30KQFckWvpFOObK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-2-%EC%A0%95%ED%95%A9%EC%84%B1-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-ON-CONFLICT-Atomic-Update&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-2-%EC%A0%95%ED%95%A9%EC%84%B1-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-ON-CONFLICT-Atomic-Update&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b6rPn2/dJMb83kBtNi/a0G3pnsisoCARx5krkaJC0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bdV9Lw/dJMb86Pae6j/Fsb6xzo30KQFckWvpFOObK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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;[Moassam] 좋아요/북마크 동시성 정합성 테스트 2 - 정합성 개선하기 (ON CONFLICT, Atomic Update)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;middot; 들어가며이전 글에서는 K6 부하 테스트를 통해 좋아요와 북마크 기능에서 동시성 문제로 인해 데이터 정합성이 맞지 않는 문제가 발생하는 것을 확인했습니다.이전글 바로가기 [Moassam] 좋아요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;myojun.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;이번 글에서는 동일한 K6 테스트 환경에서 다시 부하 테스트를 진행하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 테스트 환경&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선 전 결과와 정확한 비교를 위해 동일한 환경 및 시나리오를 통 테스트를 진행했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 테스트 환경&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;테스트 유저&lt;/b&gt;: 1000명&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;대상 게시글&lt;/b&gt;: 1개 (id = 100001)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;부하&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;도구&lt;/b&gt;: K6&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;측정 API&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;POST /api/v1/posts/{postId}/likes&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;DELETE /api/v1/posts/{postId}/likes&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;POST /api/v1/posts/{postId}/bookmarks&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;DELETE /api/v1/posts/{postId}/bookmarks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;진행 시나리오&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;시나리오 1 - 좋아요 등록 집중&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;동일 게시글에 여러 유저가 동시에 좋아요 등록&amp;nbsp;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;동시 사용자(VU): 100/300/500/1000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;시나리오 2 - 북마크 등록 집중&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;동일 게시글에 여러 유저가 동시에 북마크 등록&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;동시 사용자(VU): 100/300/500/1000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;시나리오 3 - 좋아요 등록/취소 혼합 테스트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;일부 유저는 POST /likes, 일부 유저는 DELETE /likes&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;동시 사용자(VU): 100/1000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;시나리오 4 - 북마크 등록/취소 혼합 테스트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;일부 유저는 POST /bookmarks, 일부 유저는 DELETE /bookmarks&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;동시 사용자(VU): 100/1000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;인증 방식&lt;/b&gt;: JWT&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;테스트 시간&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;30초&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;확인 지표&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;K6&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;http_req_failed&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;http_req_duration_avg&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;http_req_duration_p95&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;http_req_duration_p99&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;status&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;DB&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;stored count(실제 저장된 값)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;actual row count(기댓값)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;diff(stored count - actual row count)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 방식은 이전과 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 이전 게시글을 확인해 주세요 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot;&gt;이전 게시글&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781289618840&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;[Moassam] 좋아요/북마크 동시성 정합성 테스트 1 - 테스트 환경 구성 및 K6 부하 테스트(성능 개선 &quot; data-og-description=&quot;&amp;middot; 들어가며커뮤니티 서비스에서 실제 좋아요와 북마크 데이터 수는 posts 테이블 안에 있는 count 컬럼과 항상 동일해야 합니다. 하지만 여러 사용자가 동시에 같은 게시글에 좋아요나 북마크 요&quot; data-og-host=&quot;myojun.tistory.com&quot; data-og-source-url=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; data-og-url=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3WQHe/dJMb8SXGnXm/Nq68oergSKtJHtByXCGpI0/img.png?width=800&amp;amp;height=54&amp;amp;face=0_0_800_54,https://scrap.kakaocdn.net/dn/MA0WG/dJMb8Rkax8Y/1KHLLVKyadlofVoAiSBkkK/img.png?width=800&amp;amp;height=54&amp;amp;face=0_0_800_54,https://scrap.kakaocdn.net/dn/BCd8Z/dJMb8WeIhqT/ggTwVogoTJSWjZ89fbCfTK/img.png?width=951&amp;amp;height=222&amp;amp;face=0_0_951_222&quot;&gt;&lt;a href=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3WQHe/dJMb8SXGnXm/Nq68oergSKtJHtByXCGpI0/img.png?width=800&amp;amp;height=54&amp;amp;face=0_0_800_54,https://scrap.kakaocdn.net/dn/MA0WG/dJMb8Rkax8Y/1KHLLVKyadlofVoAiSBkkK/img.png?width=800&amp;amp;height=54&amp;amp;face=0_0_800_54,https://scrap.kakaocdn.net/dn/BCd8Z/dJMb8WeIhqT/ggTwVogoTJSWjZ89fbCfTK/img.png?width=951&amp;amp;height=222&amp;amp;face=0_0_951_222');&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;[Moassam] 좋아요/북마크 동시성 정합성 테스트 1 - 테스트 환경 구성 및 K6 부하 테스트(성능 개선&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;middot; 들어가며커뮤니티 서비스에서 실제 좋아요와 북마크 데이터 수는 posts 테이블 안에 있는 count 컬럼과 항상 동일해야 합니다. 하지만 여러 사용자가 동시에 같은 게시글에 좋아요나 북마크 요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;myojun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 시나리오 1. 좋아요 등록 집중 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 100 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781289712105&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=820.9ms
    ??'p(99)&amp;lt;3000' p(99)=1.42s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=0.00%


  ??TOTAL RESULTS 

    checks_total.......: 12270   405.19106/s
    checks_succeeded...: 100.00% 12270 out of 12270
    checks_failed......: 0.00%   0 out of 12270

    ??status is 200
    ??not 5xx

    HTTP
    http_req_duration..............: avg=380.86ms min=67.31ms  med=334.13ms max=4s   p(90)=699.94ms p(95)=820.9ms 
      { expected_response:true }...: avg=380.86ms min=67.31ms  med=334.13ms max=4s   p(90)=699.94ms p(95)=820.9ms 
    http_req_failed................: 0.00%  0 out of 6135
    http_reqs......................: 6135   202.59553/s

    EXECUTION
    iteration_duration.............: avg=491.67ms min=167.89ms med=434.66ms max=4.8s p(90)=800.65ms p(95)=921.47ms
    iterations.....................: 6135   202.59553/s
    vus............................: 100    min=100       max=100
    vus_max........................: 100    min=100       max=100

    NETWORK
    data_received..................: 3.8 MB 125 kB/s
    data_sent......................: 2.1 MB 68 kB/s




running (0m30.3s), 000/100 VUs, 6135 complete and 0 interrupted iterations
like_create ??[ 100% ] 100 VUs  30s&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;http_reqs: 6,135건&lt;/li&gt;
&lt;li&gt;http_req_failed: 0.00% (0건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 380.86ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 820.9ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 1.42s&lt;/li&gt;
&lt;li&gt;checks: 100.00% (12,270 / 12,270)&lt;/li&gt;
&lt;li&gt;status 200: 6,135건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLjsOJ/dJMcaaZVw95/U5BU43oSz7xKlEkwlxgM1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLjsOJ/dJMcaaZVw95/U5BU43oSz7xKlEkwlxgM1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLjsOJ/dJMcaaZVw95/U5BU43oSz7xKlEkwlxgM1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLjsOJ%2FdJMcaaZVw95%2FU5BU43oSz7xKlEkwlxgM1k%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;1102&quot; height=&quot;72&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 100&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 100&lt;/li&gt;
&lt;li&gt;diff: 0&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 300 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781289876465&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=2.27s
    ??'p(99)&amp;lt;3000' p(99)=2.87s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=32.47%


  ??TOTAL RESULTS 

    checks_total.......: 20276  506.883751/s
    checks_succeeded...: 83.76% 16984 out of 20276
    checks_failed......: 16.23% 3292 out of 20276

    ??status is 200
      ?? 67% ????6846 / ??3292
    ??not 5xx

    HTTP
    http_req_duration..............: avg=650.28ms min=0s       med=197.03ms max=30.13s p(90)=1.72s p(95)=2.27s
      { expected_response:true }...: avg=940.02ms min=66.85ms  med=846.86ms max=4.67s  p(90)=1.95s p(95)=2.38s
    http_req_failed................: 32.47% 3292 out of 10138
    http_reqs......................: 10138  253.441875/s

    EXECUTION
    iteration_duration.............: avg=903.38ms min=100.61ms med=521.43ms max=30.23s p(90)=2.11s p(95)=2.58s
    iterations.....................: 10138  253.441875/s
    vus............................: 2      min=2             max=300
    vus_max........................: 300    min=300           max=300

    NETWORK
    data_received..................: 29 MB  712 kB/s
    data_sent......................: 11 MB  271 kB/s




running (0m40.0s), 000/300 VUs, 10138 complete and 0 interrupted iterations
like_create ??[ 100% ] 300 VUs  30s
time=&quot;2026-06-11T23:50:20+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_duration, http_req_failed' have been 
crossed&quot;&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;http_reqs: 10,138건&lt;/li&gt;
&lt;li&gt;http_req_failed: 32.47% (3,292건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 650.28ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 2.27s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 2.87s&lt;/li&gt;
&lt;li&gt;checks: 83.76% (16,984 / 20,276)&lt;/li&gt;
&lt;li&gt;status 200: 6,846건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFehuS/dJMcageMNw0/bIT608raKVr94XnnGVfZU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFehuS/dJMcageMNw0/bIT608raKVr94XnnGVfZU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFehuS/dJMcageMNw0/bIT608raKVr94XnnGVfZU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFehuS%2FdJMcageMNw0%2FbIT608raKVr94XnnGVfZU0%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;1101&quot; height=&quot;70&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 300&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 300&lt;/li&gt;
&lt;li&gt;diff: 0&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 500 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781289945761&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=789.53ms
    ??'p(99)&amp;lt;3000' p(99)=1.85s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=33.28%


  ??TOTAL RESULTS 

    checks_total.......: 15862  325.889729/s
    checks_succeeded...: 83.35% 13222 out of 15862
    checks_failed......: 16.64% 2640 out of 15862

    ??status is 200
      ?? 66% ????5291 / ??2640
    ??not 5xx

    HTTP
    http_req_duration..............: avg=164.63ms min=0s       med=51.9ms  max=30.09s p(90)=315.52ms p(95)=789.53ms
      { expected_response:true }...: avg=216.22ms min=11.24ms  med=80.62ms max=5.35s  p(90)=516.53ms p(95)=1.19s   
    http_req_failed................: 33.28% 2640 out of 7931
    http_reqs......................: 7931   162.944865/s

    EXECUTION
    iteration_duration.............: avg=1.95s    min=102.01ms med=1.53s   max=30.34s p(90)=4.58s    p(95)=5.55s   
    iterations.....................: 7931   162.944865/s
    vus............................: 3      min=3            max=500
    vus_max........................: 500    min=500          max=500

    NETWORK
    data_received..................: 31 MB  634 kB/s
    data_sent......................: 13 MB  264 kB/s




running (0m48.7s), 000/500 VUs, 7931 complete and 0 interrupted iterations
like_create ??[ 100% ] 500 VUs  30s
time=&quot;2026-06-11T23:54:01+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_failed' have been crossed&quot;&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;http_reqs: 7,931건&lt;/li&gt;
&lt;li&gt;http_req_failed: 33.28% (2,640건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 164.63ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 789.53ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 1.85s&lt;/li&gt;
&lt;li&gt;checks: 83.35% (13,222 / 15,862)&lt;/li&gt;
&lt;li&gt;status 200: 5,291건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1095&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yUQac/dJMcacDu0ag/qBYznJD1LbqfTdmP4d6K51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yUQac/dJMcacDu0ag/qBYznJD1LbqfTdmP4d6K51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yUQac/dJMcacDu0ag/qBYznJD1LbqfTdmP4d6K51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyUQac%2FdJMcacDu0ag%2FqBYznJD1LbqfTdmP4d6K51%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;1095&quot; height=&quot;71&quot; data-origin-width=&quot;1095&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 499&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 499&lt;/li&gt;
&lt;li&gt;diff: 0&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 1000 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781289984564&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=1.47s
    ??'p(99)&amp;lt;3000' p(99)=2.02s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=67.56%


  ??TOTAL RESULTS 

    checks_total.......: 21504  358.384975/s
    checks_succeeded...: 66.21% 14239 out of 21504
    checks_failed......: 33.78% 7265 out of 21504

    ??status is 200
      ?? 32% ????3487 / ??7265
    ??not 5xx

    HTTP
    http_req_duration..............: avg=314.03ms min=0s      med=0s      max=6.37s  p(90)=1.2s  p(95)=1.47s
      { expected_response:true }...: avg=707.03ms min=11.11ms med=608.1ms max=6.37s  p(90)=1.43s p(95)=1.63s
    http_req_failed................: 67.56% 7265 out of 10752
    http_reqs......................: 10752  179.192487/s

    EXECUTION
    iteration_duration.............: avg=2.93s    min=115.4ms med=2.52s   max=31.83s p(90)=5.27s p(95)=6.25s
    iterations.....................: 10752  179.192487/s
    vus............................: 1      min=1             max=1000
    vus_max........................: 1000   min=1000          max=1000

    NETWORK
    data_received..................: 20 MB  326 kB/s
    data_sent......................: 16 MB  266 kB/s




running (1m00.0s), 0000/1000 VUs, 10752 complete and 1 interrupted iterations
like_create ??[ 100% ] 1000 VUs  30s
time=&quot;2026-06-11T23:57:37+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_duration, http_req_failed' have been 
crossed&quot;&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;http_reqs: 10,752건&lt;/li&gt;
&lt;li&gt;http_req_failed: 67.56% (7,265건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 314.03ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 1.47s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 2.02s&lt;/li&gt;
&lt;li&gt;checks: 66.21% (14,239 / 21,504)&lt;/li&gt;
&lt;li&gt;status 200: 3,487건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/epD9hw/dJMcac4u50G/y1K3Duv1I5munaqw5W07hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/epD9hw/dJMcac4u50G/y1K3Duv1I5munaqw5W07hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/epD9hw/dJMcac4u50G/y1K3Duv1I5munaqw5W07hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FepD9hw%2FdJMcac4u50G%2Fy1K3Duv1I5munaqw5W07hk%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;1102&quot; height=&quot;77&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 975&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 975&lt;/li&gt;
&lt;li&gt;diff: 0&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 개선 전과 비교&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  개선 전/후 전체 결과 비교&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 193px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; text-align: center;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center;&quot;&gt;&lt;b&gt;100 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center;&quot;&gt;&lt;b&gt;300 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center;&quot;&gt;&lt;b&gt;500 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center;&quot;&gt;&lt;b&gt;1000 VU&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;&lt;b&gt;요청 실패율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;0.00% &amp;rarr; 0.00%&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;18.02% &amp;rarr; 32.47%&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;23.46% &amp;rarr; 33.28%&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;64.57% &amp;rarr; 67.56%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;&lt;b&gt;변화율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;14.45%p 증가&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;9.82%p 증가&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;2.99%p 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;&lt;b&gt;p95 응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;903ms &amp;rarr; 820.9ms&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;2.03s &amp;rarr; 2.27s&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;216ms &amp;rarr; 789.53ms&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;1.22s &amp;rarr; 1.47s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;&lt;b&gt;응답 시간 변화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;약 9.2% 개선&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;약 11.8% 증가&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;약 265.5% 증가&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;약 20.5% 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;&lt;b&gt;처리 요청 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;5,999건 &amp;rarr; 6,135건&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;6,567건 &amp;rarr; 10,138건&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;5,497건 &amp;rarr; 7,931건&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;3,686건 &amp;rarr; 10,752건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;&lt;b&gt;처리량 변화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;약 2.3% 증가&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;약 54.4% 증가&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;약 44.3% 증가&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;약 191.7% 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;&lt;b&gt;데이터 차이(diff)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;-83 &amp;rarr; 0&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;-240 &amp;rarr; 0&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;-356 &amp;rarr; 0&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;-376 &amp;rarr; 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;&lt;b&gt;정합성 개선율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;100% 해결&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;100% 해결&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;100% 해결&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center;&quot;&gt;100% 해결&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 개선 후 데이터 정합성 문제 해결&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;879&quot; data-end=&quot;947&quot; data-ke-size=&quot;size16&quot;&gt;모든 VU 환경에서 diff 값이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;0으로 유지&lt;/b&gt;되면서 기존에 발생하던&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;데이터 불일치 문제가 완전히 해결&lt;/b&gt;되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;879&quot; data-end=&quot;947&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;949&quot; data-end=&quot;1122&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 exists 조회 &amp;rarr; count 증가 &amp;rarr; row insert 과정에서 insert 실패 이후에도 count가 증가하는 문제가 있었지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;949&quot; data-end=&quot;1122&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ON CONFLICT DO NOTHING 적용 이후 실제 relation row 생성 여부를 기준으로 count를 변경&lt;/b&gt;하도록 개선했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;949&quot; data-end=&quot;1122&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1124&quot; data-end=&quot;1170&quot; data-ke-size=&quot;size16&quot;&gt;그 결과 1000 VU 환경에서도&lt;b&gt;&lt;span&gt; 376&lt;/span&gt;건의 데이터 불일치가 제거&lt;/b&gt;되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1124&quot; data-end=&quot;1170&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 낮은 동시성 환경에서는 안정적인 처리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100 VU 환경에서는 모든 요청이 정상 처리되었다.&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;0.00% 유지&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;p95 응답 시간: &lt;b&gt;903ms &amp;rarr; 820.9ms (약 9.2% 개선)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;처리 요청 수: &lt;b&gt;5,999건 &amp;rarr; 6,135건 (약 2.3% 증가)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1337&quot; data-end=&quot;1405&quot; data-ke-size=&quot;size16&quot;&gt;즉, 중복 확인을 위한 별도 SELECT 쿼리를 제거하고 ON CONFLICT로 통합하면서 처리 효율이 향상되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1337&quot; data-end=&quot;1405&quot; 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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ 높은 동시성 환경에서는 UPDATE 경합 발생&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 300 VU 이상의 환경에서는 &lt;b&gt;데이터 정합성은 해결&lt;/b&gt;되었지만, 성능 저하가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 1000 VU 환경에서는:&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;64.57% &amp;rarr; 67.56%&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;p95 응답 시간: &lt;b&gt;1.22초 &amp;rarr; 1.47초&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 성능 기준을 만족하지 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 &lt;b&gt;ON CONFLICT&lt;/b&gt;를 통해 insert 단계의 중복 문제는 해결했지만, 실제 데이터가 저장된 이후에는 여전히 동일한 posts row에 대해 다음과 같은 UPDATE가 수행되기 때문입니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;UPDATE posts
SET like_count = like_count + 1
WHERE id = ?;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 높은 동시성 환경에서는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하나의 posts row에 대한 update 요청이 집중되면서 쓰기 경합이 발생하게 됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1446&quot; data-end=&quot;1525&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 시나리오 2. 북마크 등록 집중 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 100 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781290819772&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=334.82ms
    ??'p(99)&amp;lt;3000' p(99)=453.66ms

    http_req_failed
    ??'rate&amp;lt;0.01' rate=0.00%


  ??TOTAL RESULTS 

    checks_total.......: 22682   751.12406/s
    checks_succeeded...: 100.00% 22682 out of 22682
    checks_failed......: 0.00%   0 out of 22682

    ??status is 200
    ??not 5xx

    HTTP
    http_req_duration..............: avg=161.04ms min=10.68ms  med=156.31ms max=1.05s p(90)=276.69ms p(95)=334.82ms
      { expected_response:true }...: avg=161.04ms min=10.68ms  med=156.31ms max=1.05s p(90)=276.69ms p(95)=334.82ms
    http_req_failed................: 0.00%  0 out of 11341
    http_reqs......................: 11341  375.56203/s

    EXECUTION
    iteration_duration.............: avg=265.46ms min=111.28ms med=256.99ms max=1.49s p(90)=379.54ms p(95)=440.46ms
    iterations.....................: 11341  375.56203/s
    vus............................: 100    min=100        max=100
    vus_max........................: 100    min=100        max=100

    NETWORK
    data_received..................: 6.5 MB 217 kB/s
    data_sent......................: 3.7 MB 123 kB/s




running (0m30.2s), 000/100 VUs, 11341 complete and 0 interrupted iterations
bookmark_create ??[ 100% ] 100 VUs  30s&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;http_reqs: 11,341건&lt;/li&gt;
&lt;li&gt;http_req_failed: 0.00% (0건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 161.04ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 334.82ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 453.66ms&lt;/li&gt;
&lt;li&gt;checks: 100.00% (22,682 / 22,682)&lt;/li&gt;
&lt;li&gt;status 200: 11,341건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q0ftj/dJMcaayTJPx/WwEGH1XPtAly7KIiLkOz3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q0ftj/dJMcaayTJPx/WwEGH1XPtAly7KIiLkOz3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q0ftj/dJMcaayTJPx/WwEGH1XPtAly7KIiLkOz3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ0ftj%2FdJMcaayTJPx%2FWwEGH1XPtAly7KIiLkOz3K%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;1200&quot; height=&quot;62&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 100&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 100&lt;/li&gt;
&lt;li&gt;diff: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 300 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781290856938&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=1.58s
    ??'p(99)&amp;lt;3000' p(99)=2.05s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=23.21%


  ??TOTAL RESULTS 

    checks_total.......: 24400  548.269933/s
    checks_succeeded...: 88.39% 21568 out of 24400
    checks_failed......: 11.60% 2832 out of 24400

    ??status is 200
      ?? 76% ????9368 / ??2832
    ??not 5xx

    HTTP
    http_req_duration..............: avg=515.62ms min=0s       med=180.93ms max=30.05s p(90)=1.18s p(95)=1.58s
      { expected_response:true }...: avg=644.51ms min=22.48ms  med=588.1ms  max=4.27s  p(90)=1.29s p(95)=1.64s
    http_req_failed................: 23.21% 2832 out of 12200
    http_reqs......................: 12200  274.134967/s

    EXECUTION
    iteration_duration.............: avg=751.49ms min=100.21ms med=609.39ms max=30.16s p(90)=1.59s p(95)=1.87s
    iterations.....................: 12200  274.134967/s
    vus............................: 3      min=3             max=300
    vus_max........................: 300    min=300           max=300

    NETWORK
    data_received..................: 34 MB  757 kB/s
    data_sent......................: 13 MB  280 kB/s




running (0m44.5s), 000/300 VUs, 12200 complete and 0 interrupted iterations
bookmark_create ??[ 100% ] 300 VUs  30s
time=&quot;2026-06-12T00:10:12+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_duration, http_req_failed' have been 
crossed&quot;&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;http_reqs: 12,200건&lt;/li&gt;
&lt;li&gt;http_req_failed: 23.21% (2,832건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 515.62ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 1.58s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 2.05s&lt;/li&gt;
&lt;li&gt;checks: 88.39% (21,568 / 24,400)&lt;/li&gt;
&lt;li&gt;status 200: 9,368건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWg7Fw/dJMcafGYTLz/kNDrj1szze8KlzJ3lAyv9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWg7Fw/dJMcafGYTLz/kNDrj1szze8KlzJ3lAyv9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWg7Fw/dJMcafGYTLz/kNDrj1szze8KlzJ3lAyv9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWg7Fw%2FdJMcafGYTLz%2FkNDrj1szze8KlzJ3lAyv9k%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;1197&quot; height=&quot;77&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 300&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 300&lt;/li&gt;
&lt;li&gt;diff: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 500 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781290892607&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=745.77ms
    ??'p(99)&amp;lt;3000' p(99)=1.32s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=30.94%


  ??TOTAL RESULTS 

    checks_total.......: 28576  544.855981/s
    checks_succeeded...: 84.52% 24155 out of 28576
    checks_failed......: 15.47% 4421 out of 28576

    ??status is 200
      ?? 69% ????9867 / ??4421
    ??not 5xx

    HTTP
    http_req_duration..............: avg=228.98ms min=0s       med=117.19ms max=30.07s p(90)=519.76ms p(95)=745.77ms
      { expected_response:true }...: avg=297.85ms min=7.83ms   med=193.11ms max=5.24s  p(90)=621.74ms p(95)=888.93ms
    http_req_failed................: 30.94% 4421 out of 14288
    http_reqs......................: 14288  272.42799/s

    EXECUTION
    iteration_duration.............: avg=1.08s    min=100.05ms med=495.94ms max=30.28s p(90)=2.6s     p(95)=3.27s   
    iterations.....................: 14288  272.42799/s
    vus............................: 2      min=2             max=500
    vus_max........................: 500    min=500           max=500

    NETWORK
    data_received..................: 40 MB  757 kB/s
    data_sent......................: 15 MB  292 kB/s




running (0m52.4s), 000/500 VUs, 14288 complete and 0 interrupted iterations
bookmark_create ??[ 100% ] 500 VUs  30s
time=&quot;2026-06-12T00:14:50+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_failed' have been crossed&quot;&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;http_reqs: 14,288건&lt;/li&gt;
&lt;li&gt;http_req_failed: 30.94% (4,421건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 228.98ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 745.77ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 1.32s&lt;/li&gt;
&lt;li&gt;checks: 84.52% (24,155 / 28,576)&lt;/li&gt;
&lt;li&gt;status 200: 9,867건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9HMnj/dJMcafAbW95/yrdUflwrAgNMi0kWVxkPh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9HMnj/dJMcafAbW95/yrdUflwrAgNMi0kWVxkPh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9HMnj/dJMcafAbW95/yrdUflwrAgNMi0kWVxkPh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9HMnj%2FdJMcafAbW95%2FyrdUflwrAgNMi0kWVxkPh1%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;1193&quot; height=&quot;73&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 498&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 498&lt;/li&gt;
&lt;li&gt;diff: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 1000 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781290924072&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=4.14s
    ??'p(99)&amp;lt;3000' p(99)=28.09s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=47.90%


  ??TOTAL RESULTS 

    checks_total.......: 11402  189.998524/s
    checks_succeeded...: 76.04% 8671 out of 11402
    checks_failed......: 23.95% 2731 out of 11402

    ??status is 200
      ?? 52% ????2970 / ??2731
    ??not 5xx

    HTTP
    http_req_duration..............: avg=1.21s min=0s       med=145.8ms  max=53.12s p(90)=2.1s   p(95)=4.14s 
      { expected_response:true }...: avg=2.16s min=7.89ms   med=642.66ms max=53.12s p(90)=3.84s  p(95)=7.65s 
    http_req_failed................: 47.90% 2731 out of 5701
    http_reqs......................: 5701   94.999262/s

    EXECUTION
    iteration_duration.............: avg=6.62s min=101.63ms med=2.17s    max=58.96s p(90)=21.14s p(95)=31.62s
    iterations.....................: 5700   94.982598/s
    vus............................: 36     min=36           max=1000
    vus_max........................: 1000   min=1000         max=1000

    NETWORK
    data_received..................: 13 MB  216 kB/s
    data_sent......................: 7.0 MB 116 kB/s




running (1m00.0s), 0000/1000 VUs, 5700 complete and 35 interrupted iterations
bookmark_create ??[ 100% ] 1000 VUs  30s
time=&quot;2026-06-12T00:17:53+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_duration, http_req_failed' have been 
crossed&quot;&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;http_reqs: 5,701건&lt;/li&gt;
&lt;li&gt;http_req_failed: 47.90% (2,731건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 1.21s&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 4.14s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 28.09s&lt;/li&gt;
&lt;li&gt;checks: 76.04% (8,671 / 11,402)&lt;/li&gt;
&lt;li&gt;status 200: 2,970건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FZYzC/dJMcaa6Ksda/A2NoeEDkGK5CUIcKzFda5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FZYzC/dJMcaa6Ksda/A2NoeEDkGK5CUIcKzFda5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FZYzC/dJMcaa6Ksda/A2NoeEDkGK5CUIcKzFda5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFZYzC%2FdJMcaa6Ksda%2FA2NoeEDkGK5CUIcKzFda5K%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;1193&quot; height=&quot;67&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 953&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 953&lt;/li&gt;
&lt;li&gt;diff: 0&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 개선 전과 비교&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  개선 전/후 전체 결과 비교&lt;/b&gt;&lt;/h4&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;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;500 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;1000 VU&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;요청 실패율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;0.00% &amp;rarr; 0.00%&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;21.01% &amp;rarr; 23.21%&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;33.97% &amp;rarr; 30.94%&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;54.99% &amp;rarr; 47.90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;변화율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;2.20%p 증가&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;3.03%p 감소&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;7.09%p 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;p95 응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;748.87ms &amp;rarr; 334.82ms&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1.06s &amp;rarr; 1.58s&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;704.79ms &amp;rarr; 745.77ms&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;1.78s &amp;rarr; 4.14s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;응답 시간 변화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;약 55.3% 개선&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;약 49.1% 증가&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;약 5.8% 증가&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;약 132.6% 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;처리 요청 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;7,522건 &amp;rarr; 11,341건&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;17,839건 &amp;rarr; 12,200건&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;18,808건 &amp;rarr; 14,288건&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;20,260건 &amp;rarr; 5,701건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;처리량 변화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;약 50.8% 증가&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;약 31.6% 감소&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;약 24.0% 감소&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;약 71.9% 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;데이터 차이(diff)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;-83 &amp;rarr; 0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;-248 &amp;rarr; 0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;-410 &amp;rarr; 0&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;-416 &amp;rarr; 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;정합성 개선율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;100% 해결&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;100% 해결&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;100% 해결&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;100% 해결&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 개선 후 데이터 정합성 문제 해결&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;947&quot; data-start=&quot;879&quot; data-ke-size=&quot;size16&quot;&gt;모든 VU 환경에서 diff 값이 &lt;b&gt;0으로 유지&lt;/b&gt;되면서 기존에 발생하던 &lt;b&gt;데이터 불일치 문제가 완전히 해결&lt;/b&gt;되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;947&quot; data-start=&quot;879&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1122&quot; data-start=&quot;949&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 exists 조회 &amp;rarr; count 증가 &amp;rarr; row insert 과정에서 insert 실패 이후에도 count가 증가하는 문제가 있었지만,&lt;/p&gt;
&lt;p data-end=&quot;1122&quot; data-start=&quot;949&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ON CONFLICT DO NOTHING 적용 이후 실제 relation row 생성 여부를 기준으로 count를 변경&lt;/b&gt;하도록 개선했습니다.&lt;/p&gt;
&lt;p data-end=&quot;1122&quot; data-start=&quot;949&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1170&quot; data-start=&quot;1124&quot; data-ke-size=&quot;size16&quot;&gt;그 결과 1000 VU 환경에서도 &lt;b&gt;416건의 데이터 불일치가 제거&lt;/b&gt;되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;1170&quot; data-start=&quot;1124&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 낮은 동시성 환경에서는 성능 개선&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;1243&quot; data-start=&quot;1204&quot; data-ke-size=&quot;size16&quot;&gt;100 VU 환경에서는 exists 조회 제거 효과가 나타났습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1335&quot; data-start=&quot;1245&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1292&quot; data-start=&quot;1245&quot;&gt;p95 응답 시간: &lt;b&gt;748.87ms &amp;rarr; 334.82ms (약 55% 감소)&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1335&quot; data-start=&quot;1293&quot;&gt;처리 요청 수: &lt;b&gt;7,522건 &amp;rarr; 11,341건 (약 51% 증가)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1405&quot; data-start=&quot;1337&quot; data-ke-size=&quot;size16&quot;&gt;즉, 중복 확인을 위한 별도 SELECT 쿼리를 제거하고 ON CONFLICT로 통합하면서 처리 효율이 향상되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;1405&quot; data-start=&quot;1337&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ 높은 동시성 환경에서는 여전히 성능 한계 존재&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;1525&quot; data-start=&quot;1446&quot; data-ke-size=&quot;size16&quot;&gt;다만 300 VU 이상의 환경에서는 성능 개선 효과보다 posts 테이블의 count 증가 쿼리에서 발생하는 경합이 더 크게 나타났습니다.&lt;/p&gt;
&lt;p data-end=&quot;1544&quot; data-start=&quot;1527&quot; data-ke-size=&quot;size16&quot;&gt;특히 1000 VU 환경에서는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1608&quot; data-start=&quot;1546&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1576&quot; data-start=&quot;1546&quot;&gt;p95 응답 시간: &lt;b&gt;1.78초 &amp;rarr; 4.14초&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1608&quot; data-start=&quot;1577&quot;&gt;처리 요청 수: &lt;b&gt;20,260건 &amp;rarr; 5,701건&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1627&quot; data-start=&quot;1610&quot; data-ke-size=&quot;size16&quot;&gt;으로 성능 저하가 발생했습니다.&lt;/p&gt;
&lt;p data-end=&quot;1627&quot; data-start=&quot;1610&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1742&quot; data-start=&quot;1629&quot; data-ke-size=&quot;size16&quot;&gt;이는 &lt;b&gt;ON CONFLICT&lt;/b&gt;를 통해 insert 단계의 중복 문제는 해결했지만, 실제 데이터가 저장된 이후에는 여전히 동일한 posts row에 대해 다음과 같은 UPDATE가 수행되기 때문입니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;UPDATE posts
SET bookmark_count = bookmark_count + 1
WHERE id = ?;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 높은 동시성 환경에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 posts row에 대한 update 요청이 집중되면서 쓰기 경합이 발생하게 됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 시나리오 3. 좋아요 혼합 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 100 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781291563888&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1500' p(95)=677.76ms
    ??'p(99)&amp;lt;4000' p(99)=871.78ms

    http_req_failed
    ??'rate&amp;lt;0.05' rate=0.00%


  ??TOTAL RESULTS 

    checks_total.......: 13348   438.315901/s
    checks_succeeded...: 100.00% 13348 out of 13348
    checks_failed......: 0.00%   0 out of 13348

    ??status is 200
    ??not 5xx

    HTTP
    http_req_duration..............: avg=345.24ms min=14.43ms  med=352.58ms max=1.34s p(90)=540.34ms p(95)=677.76ms
      { expected_response:true }...: avg=345.24ms min=14.43ms  med=352.58ms max=1.34s p(90)=540.34ms p(95)=677.76ms
    http_req_failed................: 0.00%  0 out of 6674
    http_reqs......................: 6674   219.157951/s

    EXECUTION
    iteration_duration.............: avg=453.17ms min=115.27ms med=455.26ms max=2.62s p(90)=667.54ms p(95)=805.39ms
    iterations.....................: 6674   219.157951/s
    vus............................: 100    min=100       max=100
    vus_max........................: 100    min=100       max=100

    NETWORK
    data_received..................: 4.1 MB 134 kB/s
    data_sent......................: 2.2 MB 71 kB/s




running (0m30.5s), 000/100 VUs, 6674 complete and 0 interrupted iterations
like_mixed ??[ 100% ] 100 VUs  30s&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;http_reqs: 6,674건&lt;/li&gt;
&lt;li&gt;http_req_failed: 0.00% (0건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 345.24ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 677.76ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 871.78ms&lt;/li&gt;
&lt;li&gt;checks: 100.00% (13,348 / 13,348)&lt;/li&gt;
&lt;li&gt;status 200: 6,674건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1107&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uogSh/dJMb9904cpt/K8QCwCVXr8cKAeIUD9VILk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uogSh/dJMb9904cpt/K8QCwCVXr8cKAeIUD9VILk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uogSh/dJMb9904cpt/K8QCwCVXr8cKAeIUD9VILk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuogSh%2FdJMb9904cpt%2FK8QCwCVXr8cKAeIUD9VILk%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;1107&quot; height=&quot;77&quot; data-origin-width=&quot;1107&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 448&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 448&lt;/li&gt;
&lt;li&gt;diff: 0&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 1000 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781291603902&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1500' p(95)=1.97s
    ??'p(99)&amp;lt;4000' p(99)=5.3s

    http_req_failed
    ??'rate&amp;lt;0.05' rate=59.22%


  ??TOTAL RESULTS 

    checks_total.......: 20632  343.850403/s
    checks_succeeded...: 70.38% 14522 out of 20632
    checks_failed......: 29.61% 6110 out of 20632

    ??status is 200
      ?? 40% ????4206 / ??6110
    ??not 5xx

    HTTP
    http_req_duration..............: avg=399.26ms min=0s      med=37.05ms  max=30.6s  p(90)=811.54ms p(95)=1.97s 
      { expected_response:true }...: avg=850.17ms min=7.26ms  med=280.97ms max=29.77s p(90)=2.19s    p(95)=3.48s 
    http_req_failed................: 59.22% 6110 out of 10316
    http_reqs......................: 10316  171.925202/s

    EXECUTION
    iteration_duration.............: avg=3.68s    min=101.8ms med=2.11s    max=39.09s p(90)=8.48s    p(95)=14.15s
    iterations.....................: 10316  171.925202/s
    vus............................: 1      min=1             max=1000
    vus_max........................: 1000   min=1000          max=1000

    NETWORK
    data_received..................: 26 MB  430 kB/s
    data_sent......................: 16 MB  262 kB/s




running (1m00.0s), 0000/1000 VUs, 10316 complete and 1 interrupted iterations
like_mixed ??[ 100% ] 1000 VUs  30s
time=&quot;2026-06-12T00:32:26+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_duration, http_req_failed' have been 
crossed&quot;&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;http_reqs: 10,316건&lt;/li&gt;
&lt;li&gt;http_req_failed: 59.22% (6,110건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 399.26ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 1.97s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 5.3s&lt;/li&gt;
&lt;li&gt;checks: 70.38% (14,522 / 20,632)&lt;/li&gt;
&lt;li&gt;status 200: 4,206건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgx00o/dJMcafmGqBW/1kMwCWXtQJDiHu78uIQe9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgx00o/dJMcafmGqBW/1kMwCWXtQJDiHu78uIQe9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgx00o/dJMcafmGqBW/1kMwCWXtQJDiHu78uIQe9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgx00o%2FdJMcafmGqBW%2F1kMwCWXtQJDiHu78uIQe9k%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;1101&quot; height=&quot;72&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 533&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 533&lt;/li&gt;
&lt;li&gt;diff: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 개선 전과 비교&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  개선 전/후 전체 결과 비교&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 74.8837%; height: 198px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 19.3023%;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.5582%;&quot;&gt;&lt;b&gt;100 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.9069%;&quot;&gt;&lt;b&gt;1000 VU&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 19.3023%;&quot;&gt;&lt;b&gt;요청 실패율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.5582%;&quot;&gt;0.00% &amp;rarr; 0.00%&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.9069%;&quot;&gt;57.70% &amp;rarr; 59.22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 19.3023%;&quot;&gt;&lt;b&gt;변화율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.5582%;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.9069%;&quot;&gt;1.52%p 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 19.3023%;&quot;&gt;&lt;b&gt;p95 응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.5582%;&quot;&gt;570ms &amp;rarr; 677.76ms&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.9069%;&quot;&gt;1.05s &amp;rarr; 1.97s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 19.3023%;&quot;&gt;&lt;b&gt;응답 시간 변화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.5582%;&quot;&gt;약 18.9% 증가&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.9069%;&quot;&gt;약 87.6% 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 19.3023%;&quot;&gt;&lt;b&gt;처리 요청 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.5582%;&quot;&gt;7,384건 &amp;rarr; 6,674건&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.9069%;&quot;&gt;4,024건 &amp;rarr; 10,316건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 19.3023%;&quot;&gt;&lt;b&gt;처리량 변화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.5582%;&quot;&gt;약 9.6% 감소&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.9069%;&quot;&gt;약 156.4% 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 19.3023%;&quot;&gt;&lt;b&gt;데이터 차이(diff)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.5582%;&quot;&gt;+114 &amp;rarr; 0&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.9069%;&quot;&gt;+32 &amp;rarr; 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 19.3023%;&quot;&gt;&lt;b&gt;정합성 개선율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.5582%;&quot;&gt;100% 해결&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px; width: 27.9069%;&quot;&gt;100% 해결&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 개선 후 데이터 정합성 문제 해결&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;568&quot; data-start=&quot;424&quot; data-ke-size=&quot;size16&quot;&gt;ON CONFLICT DO NOTHING을 적용하여&lt;/p&gt;
&lt;p data-end=&quot;568&quot; data-start=&quot;424&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 relation row가 생성된 경우에만 count를 증가&lt;/b&gt;하도록 변경하였고,&lt;/p&gt;
&lt;p data-end=&quot;568&quot; data-start=&quot;424&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DELETE 또한 실제 삭제 성공 여부를 기준으로 count를 감소&lt;/b&gt;하도록 수정했습니다.&lt;/p&gt;
&lt;p data-end=&quot;568&quot; data-start=&quot;424&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;607&quot; data-start=&quot;570&quot; data-ke-size=&quot;size16&quot;&gt;그 결과 &lt;b&gt;모든 부하 테스트 환경에서 데이터 정합성&lt;/b&gt;이 유지되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;607&quot; data-start=&quot;570&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 낮은 동시성 환경에서는 안정적인 처리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;872&quot; data-start=&quot;843&quot; data-ke-size=&quot;size16&quot;&gt;100 VU 환경에서는 모든 요청이 정상 처리되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;955&quot; data-start=&quot;874&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;896&quot; data-start=&quot;874&quot;&gt;요청 실패율: &lt;b&gt;0.00% 유지&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;930&quot; data-start=&quot;897&quot;&gt;p95 응답 시간: &lt;b&gt;570ms &amp;rarr; 677.76ms&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;955&quot; data-start=&quot;931&quot;&gt;데이터 불일치: &lt;b&gt;114건 &amp;rarr; 0건&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1011&quot; data-start=&quot;957&quot; data-ke-size=&quot;size16&quot;&gt;K6 기준인 http_req_failed &amp;lt; 5%, p95 &amp;lt; 1.5초를 모두 만족했습니다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;1092&quot; data-start=&quot;1013&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;일반적인 사용자 규모에서는 좋아요 등록과 취소가 동시에 발생하는 환경에서도 데이터 정합성을 보장&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;1092&quot; data-start=&quot;1013&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1092&quot; data-start=&quot;1013&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ 높은 동시성 환경에서는 여전히 쓰기 경합 발생&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1000 VU 환경에서는 요청 실패율이 크게 증가하고 응답 시간이 악화되었습니다.&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;59.22%&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;p95 응답 시간: &lt;b&gt;1.97초&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;HTTP 200 응답: &lt;b&gt;4,206건&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 요청 처리 방식 개선으로 데이터 정합성 문제는 해결되었지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K6의 성능 기준(http_req_failed &amp;lt; 5%, p95 &amp;lt; 1.5초)을 만족하지 못하여 테스트는 실패로 판단되었습니다.&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;성공한 좋아요 등록과 취소 요청이 모두 동일한 posts.like_count Row를 갱신하기 때문이라고 예상됩니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;UPDATE posts
SET like_count= like_count &amp;plusmn;1
WHERE id= ?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Mixed 시나리오는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 증가와 감소 요청이 동시에 발생하기 때문에 단순 등록 테스트보다 &lt;b&gt;더 많은 UPDATE 경쟁이 발생&lt;/b&gt;합니다.&lt;/p&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;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 시나리오 4. 북마크 혼합 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 100 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781292121219&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1500' p(95)=537.17ms
    ??'p(99)&amp;lt;4000' p(99)=679.86ms

    http_req_failed
    ??'rate&amp;lt;0.05' rate=0.00%


  ??TOTAL RESULTS 

    checks_total.......: 15104   498.81124/s
    checks_succeeded...: 100.00% 15104 out of 15104
    checks_failed......: 0.00%   0 out of 15104

    ??status is 200
    ??not 5xx

    HTTP
    http_req_duration..............: avg=292.25ms min=11.01ms  med=290.03ms max=1.22s p(90)=420.3ms  p(95)=537.17ms
      { expected_response:true }...: avg=292.25ms min=11.01ms  med=290.03ms max=1.22s p(90)=420.3ms  p(95)=537.17ms
    http_req_failed................: 0.00%  0 out of 7552
    http_reqs......................: 7552   249.40562/s

    EXECUTION
    iteration_duration.............: avg=399.24ms min=116.43ms med=391.84ms max=1.92s p(90)=537.61ms p(95)=648.14ms
    iterations.....................: 7552   249.40562/s
    vus............................: 100    min=100       max=100
    vus_max........................: 100    min=100       max=100

    NETWORK
    data_received..................: 4.5 MB 150 kB/s
    data_sent......................: 2.5 MB 81 kB/s




running (0m30.3s), 000/100 VUs, 7552 complete and 0 interrupted iterations
bookmark_mixed ??[ 100% ] 100 VUs  30s&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;http_reqs: 7,552건&lt;/li&gt;
&lt;li&gt;http_req_failed: 0.00% (0건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 292.25ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 537.17ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 679.86ms&lt;/li&gt;
&lt;li&gt;checks: 100.00% (15,104 / 15,104)&lt;/li&gt;
&lt;li&gt;status 200: 7,552건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfTHZu/dJMcaiKnSAY/MlnU9ao9UJ4yzZ7DM3di9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfTHZu/dJMcaiKnSAY/MlnU9ao9UJ4yzZ7DM3di9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfTHZu/dJMcaiKnSAY/MlnU9ao9UJ4yzZ7DM3di9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfTHZu%2FdJMcaiKnSAY%2FMlnU9ao9UJ4yzZ7DM3di9k%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;1203&quot; height=&quot;71&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 446&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 446&lt;/li&gt;
&lt;li&gt;diff: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 1000 VU&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781292158808&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1500' p(95)=2.6s
    ??'p(99)&amp;lt;4000' p(99)=9.69s

    http_req_failed
    ??'rate&amp;lt;0.05' rate=48.07%


  ??TOTAL RESULTS 

    checks_total.......: 17776  312.015457/s
    checks_succeeded...: 75.96% 13503 out of 17776
    checks_failed......: 24.03% 4273 out of 17776

    ??status is 200
      ?? 51% ????4615 / ??4273
    ??not 5xx

    HTTP
    http_req_duration..............: avg=700.21ms min=0s       med=164.89ms max=49.27s p(90)=1.36s p(95)=2.6s  
      { expected_response:true }...: avg=1.16s    min=8.7ms    med=477.46ms max=49.27s p(90)=2.29s p(95)=4.03s 
    http_req_failed................: 48.07% 4273 out of 8888
    http_reqs......................: 8888   156.007728/s

    EXECUTION
    iteration_duration.............: avg=3.77s    min=104.16ms med=1.91s    max=52.88s p(90)=8.52s p(95)=15.89s
    iterations.....................: 8888   156.007728/s
    vus............................: 1      min=1            max=1000
    vus_max........................: 1000   min=1000         max=1000

    NETWORK
    data_received..................: 23 MB  395 kB/s
    data_sent......................: 12 MB  208 kB/s




running (0m57.0s), 0000/1000 VUs, 8888 complete and 0 interrupted iterations
bookmark_mixed ??[ 100% ] 1000 VUs  30s
time=&quot;2026-06-12T00:41:16+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_duration, http_req_failed' have been 
crossed&quot;&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;http_reqs: 8,888건&lt;/li&gt;
&lt;li&gt;http_req_failed: 48.07% (4,273건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 700.21ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 2.6s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 9.69s&lt;/li&gt;
&lt;li&gt;checks: 75.96% (13,503 / 17,776)&lt;/li&gt;
&lt;li&gt;status 200: 4,615건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ️DB 정합성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kKFRp/dJMcacQXb8v/UYMmR6EkKXHN4Xa9wHkQ5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kKFRp/dJMcacQXb8v/UYMmR6EkKXHN4Xa9wHkQ5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kKFRp/dJMcacQXb8v/UYMmR6EkKXHN4Xa9wHkQ5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkKFRp%2FdJMcacQXb8v%2FUYMmR6EkKXHN4Xa9wHkQ5k%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;1192&quot; height=&quot;73&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 491&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 491&lt;/li&gt;
&lt;li&gt;diff: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 개선 전과 비교&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  개선 전/후 전체 결과 비교&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 74.6512%; height: 198px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 21.5116%;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 25.4651%;&quot;&gt;&lt;b&gt;100 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 27.5581%;&quot;&gt;&lt;b&gt;1000 VU&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 21.5116%;&quot;&gt;&lt;b&gt;요청 실패율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 25.4651%;&quot;&gt;0.00% &amp;rarr; 0.00%&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 27.5581%;&quot;&gt;54.57% &amp;rarr; 48.07%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 21.5116%;&quot;&gt;&lt;b&gt;변화율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 25.4651%;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 27.5581%;&quot;&gt;6.50%p 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 21.5116%;&quot;&gt;&lt;b&gt;p95 응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 25.4651%;&quot;&gt;633.56ms &amp;rarr; 537.17ms&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 27.5581%;&quot;&gt;2.86s &amp;rarr; 2.6s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 21.5116%;&quot;&gt;&lt;b&gt;응답 시간 변화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 25.4651%;&quot;&gt;약 15.2% 개선&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 27.5581%;&quot;&gt;약 9.1% 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 21.5116%;&quot;&gt;&lt;b&gt;처리 요청 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 25.4651%;&quot;&gt;6,994건 &amp;rarr; 7,552건&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 27.5581%;&quot;&gt;8,014건 &amp;rarr; 8,888건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 21.5116%;&quot;&gt;&lt;b&gt;처리량 변화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 25.4651%;&quot;&gt;약 8.0% 증가&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 27.5581%;&quot;&gt;약 10.9% 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 21.5116%;&quot;&gt;&lt;b&gt;데이터 차이(diff)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 25.4651%;&quot;&gt;+134 &amp;rarr; 0&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 27.5581%;&quot;&gt;+58 &amp;rarr; 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 21.5116%;&quot;&gt;&lt;b&gt;정합성 개선율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 25.4651%;&quot;&gt;100% 해결&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 27.5581%;&quot;&gt;100% 해결&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 개선 후 데이터 정합성 문제 해결&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;424&quot; data-end=&quot;568&quot; data-ke-size=&quot;size16&quot;&gt;북마크 혼합 테스트 역시&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;424&quot; data-end=&quot;568&quot; data-ke-size=&quot;size16&quot;&gt;ON CONFLICT DO NOTHING을 적용하여&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;424&quot; data-end=&quot;568&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 relation row가 생성된 경우에만 count를 증가&lt;/b&gt;하도록 변경하였고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;424&quot; data-end=&quot;568&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DELETE 또한 실제 삭제 성공 여부를 기준으로 count를 감소&lt;/b&gt;하도록 수정했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;424&quot; data-end=&quot;568&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;570&quot; data-end=&quot;607&quot; data-ke-size=&quot;size16&quot;&gt;그 결과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;모든 부하 테스트 환경에서 데이터 정합성&lt;/b&gt;이 유지되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;570&quot; data-end=&quot;607&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 낮은 동시성 환경에서는 안정적인 처리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100 VU 환경에서는 모든 요청이 정상 처리되었다.&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;0.00% 유지&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;p95 응답 시간: &lt;b&gt;633.56ms &amp;rarr; 537.17ms (약 15% 개선)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;처리 요청 수: &lt;b&gt;6,994건 &amp;rarr; 7,552건 (약 8% 증가)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K6 기준인 http_req_failed &amp;lt; 5%, p95 &amp;lt; 1.5초를 모두 만족했습니다.&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;일반적인 사용자 규모에서는 좋아요 등록과 취소가 동시에 발생하는 환경에서도 데이터 정합성을 보장&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ 높은 동시성 환경에서는 여전히 UPDATE 경합 발생&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1000 VU 환경에서는 개선 이후에도 높은 동시성에 따른 병목이 존재했습니다.&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;54.57% &amp;rarr; 48.07% (약 6.5%p 개선)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;p95 응답 시간: &lt;b&gt;2.86초 &amp;rarr; 2.6초 (약 9% 개선)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;처리 요청 수: &lt;b&gt;8,014건 &amp;rarr; 8,888건 (약 11% 증가)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;중복 요청 처리 방식 개선으로&lt;b&gt; 데이터 정합성 문제는 해결&lt;/b&gt;되었지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 역시 K6의 성능 기준(http_req_failed &amp;lt; 5%, p95 &amp;lt; 1.5초)을 만족하지 못하여 테스트는 실패로 판단되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이것 또한 성공한 좋아요 등록과 취소 요청이 모두 동일한 posts.like_count Row를 갱신하기 때문이라고 예상됩니다.&lt;/p&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;UPDATE posts
SET like_count= like_count &amp;plusmn;1
WHERE id= ?&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 Mixed 시나리오는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;좋아요 증가와 감소 요청이 동시에 발생하기 때문에 단순 등록 테스트보다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;더 많은 UPDATE 경쟁이 발생&lt;/b&gt;합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 개선 결과&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 개선을 통해 가장 크게 달라진 점은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동시 요청 상황에서도 데이터 정합성을 보장할 수 있게 되었다는 점&lt;/b&gt;입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이번 부하 테스트의 목적은 달성했다고 말할 수 있겠군요 :)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;북마크 등록 100 VU 테스트 기준&lt;/b&gt;으로,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선 전에는 100명의 사용자가 동시에 요청했음에도 post_bookmarks row와 posts.bookmark_count 값이 일치하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개선 후에는&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 80.3489%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;&lt;b&gt;구분&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;&lt;b&gt;개선 전&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.1861%;&quot;&gt;&lt;b&gt;개선 후&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 28.393%;&quot;&gt;&lt;b&gt;개선 효과&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;&lt;b&gt;요청 실패율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;0.00%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.1861%;&quot;&gt;0.00%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 28.393%;&quot;&gt;동일 (100% 성공 유지)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;&lt;b&gt;p95 응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;748.87ms&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.1861%;&quot;&gt;334.82ms&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 28.393%;&quot;&gt;&lt;b&gt;약 55.3% 감소&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;&lt;b&gt;처리 요청 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;7,522건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.1861%;&quot;&gt;11,341건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 28.393%;&quot;&gt;&lt;b&gt;약 50.8% 증가&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;&lt;b&gt;실제 count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;17&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.1861%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 28.393%;&quot;&gt;&lt;b&gt;데이터 정합성 확보 (+488.2%)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;&lt;b&gt;기대 count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.1861%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 28.393%;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;&lt;b&gt;데이터 차이(diff)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 17.5581%;&quot;&gt;-83&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.1861%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 28.393%;&quot;&gt;&lt;b&gt;오차 100% 제거&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 효과를 얻을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이번 개선은 정합성 문제 해결에만 초점을 맞춘 개선이기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1&lt;b&gt;000 VU와 같은 극단적인 트래픽 환경&lt;/b&gt;에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여전히 posts 테이블의 count update가 하나의 row에 집중되어 높은 요청 실패율이 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이번 개선으로&lt;b&gt; 데이터 정합성&lt;/b&gt;은 얻었지만, &lt;b&gt;대용량 트래픽으로 인한 쓰기 경합은 해결되지 않았습니다&lt;/b&gt;.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요와 북마크 기능은 단순한 관계 데이터 저장 기능처럼 보일 수 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count 컬럼과 같은 집계 데이터를 별도로 관리하는 순간 동시성 환경에서는 &lt;b&gt;데이터 정합성을 고려&lt;/b&gt;해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 테스트에서는 K6 부하 테스트를 통해&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;API 요청 성공 여부뿐만 아니라 데이터의 정합성까지 함께 검증&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, &lt;b&gt;ON CONFLICT DO NOTHING&lt;/b&gt;과&lt;b&gt; Atomic Update&lt;/b&gt;를 적용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 Lock 없이도 변경 순서를 재설계함으로써 &lt;b&gt;데이터 정합성&lt;/b&gt;을 얻을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이러한 개선에도 높은 트래픽 환경에서는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;posts 테이블의 count Update가 성능적으로 새로운 병목 지점임을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후에는 이러한 동시성 문제를 해결해 보고 성능 측정을 해보는 시간을 가져보면 유익할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수가 되는 그날까지.. 화이팅 &lt;/p&gt;</description>
      <category>Project/Moassam</category>
      <category>Atomic Update</category>
      <category>K6</category>
      <category>ON CONFLICT</category>
      <category>부하 테스트</category>
      <category>성능 개선</category>
      <category>성능 테스트</category>
      <category>정합성</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/60</guid>
      <comments>https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-3-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-%ED%9B%84#entry60comment</comments>
      <pubDate>Sat, 13 Jun 2026 05:07:00 +0900</pubDate>
    </item>
    <item>
      <title>[Moassam] 좋아요/북마크 동시성 정합성 테스트 2 - 정합성 개선하기 (ON CONFLICT, Atomic Update)</title>
      <link>https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-2-%EC%A0%95%ED%95%A9%EC%84%B1-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-ON-CONFLICT-Atomic-Update</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서는 K6 부하 테스트를 통해 좋아요와 북마크 기능에서 동시성 문제로 인해 데이터 정합성이 맞지 않는 문제가 발생하는 것을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전글 바로가기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781284252687&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;[Moassam] 좋아요/북마크 동시성 정합성 테스트 1 - 테스트 환경 구성 및 K6 부하 테스트(성능 개선 &quot; data-og-description=&quot;&amp;middot; 들어가며커뮤니티 서비스에서 실제 좋아요와 북마크 데이터 수는 posts 테이블 안에 있는 count 컬럼과 항상 동일해야 합니다. 하지만 여러 사용자가 동시에 같은 게시글에 좋아요나 북마크 요&quot; data-og-host=&quot;myojun.tistory.com&quot; data-og-source-url=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; data-og-url=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3WQHe/dJMb8SXGnXm/Nq68oergSKtJHtByXCGpI0/img.png?width=800&amp;amp;height=54&amp;amp;face=0_0_800_54,https://scrap.kakaocdn.net/dn/MA0WG/dJMb8Rkax8Y/1KHLLVKyadlofVoAiSBkkK/img.png?width=800&amp;amp;height=54&amp;amp;face=0_0_800_54,https://scrap.kakaocdn.net/dn/BCd8Z/dJMb8WeIhqT/ggTwVogoTJSWjZ89fbCfTK/img.png?width=951&amp;amp;height=222&amp;amp;face=0_0_951_222&quot;&gt;&lt;a href=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3WQHe/dJMb8SXGnXm/Nq68oergSKtJHtByXCGpI0/img.png?width=800&amp;amp;height=54&amp;amp;face=0_0_800_54,https://scrap.kakaocdn.net/dn/MA0WG/dJMb8Rkax8Y/1KHLLVKyadlofVoAiSBkkK/img.png?width=800&amp;amp;height=54&amp;amp;face=0_0_800_54,https://scrap.kakaocdn.net/dn/BCd8Z/dJMb8WeIhqT/ggTwVogoTJSWjZ89fbCfTK/img.png?width=951&amp;amp;height=222&amp;amp;face=0_0_951_222');&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;[Moassam] 좋아요/북마크 동시성 정합성 테스트 1 - 테스트 환경 구성 및 K6 부하 테스트(성능 개선&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;middot; 들어가며커뮤니티 서비스에서 실제 좋아요와 북마크 데이터 수는 posts 테이블 안에 있는 count 컬럼과 항상 동일해야 합니다. 하지만 여러 사용자가 동시에 같은 게시글에 좋아요나 북마크 요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;myojun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 100 VU와 같은 비교적 낮은 동시성 환경에서도 HTTP 요청은 정상적으로 처리되었지만, 실제 데이터와 posts 테이블의 count 값은 일치하지 않았습니다.&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;count 변경과 실제 데이터 변경이 하나의 기준으로 관리되지 않았기 때문&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이러한 문제를 해결하기 위해 좋아요/북마크 로직을 어떻게 개선했는지 정리해보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 기존 구현의 문제와 개선 방향&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 기존 문제점&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 좋아요 등록 로직은 아래와 같았습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1781284496177&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (postLikeRepository.existsByPostIdAndUserId(postId, userId)) {
    return;
}

post.increaseLikeCount();
postLikeRepository.save(PostLike.create(userId, postId));&lt;/code&gt;&lt;/pre&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;요청 A &amp;rarr; exists = false &amp;rarr; count 증가 &amp;rarr; insert 성공&lt;/b&gt;&lt;br /&gt;&lt;b&gt;요청 B&amp;nbsp;&amp;rarr; exists = false&amp;nbsp;&amp;rarr; count 증가&amp;nbsp;&amp;rarr; insert 실패&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;post_likes 테이블은 (post_id, user_id)에 Unique Constraint가 존재하기 때문에 중복 데이터 저장은 방지됩니다.&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;하지만 이미 증가한 like_count는 돌아오지 않기에 실제 데이터와 저장된 count 값에는 차이가 발생하게 됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 개선 방향&lt;/b&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;실제 데이터 변경 여부를 기준으로 count를 변경하도록 하는 &lt;b&gt;원자적으로 데이터를 변경하는 방식&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;기존 구조&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;exists 확인&amp;nbsp;&amp;rarr; count 증가&amp;nbsp;&amp;rarr; row insert&lt;/blockquote&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;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;row insert 시도&amp;nbsp;&amp;rarr; INSERT 성공 여부 판단&amp;nbsp;&amp;rarr; Atomic count UPDATE&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 개선하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- ON CONFLICT를 이용한 중복 요청 처리&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;하지만 exists 조회와 insert 사이에는 다른 요청이 먼저 데이터를 저장할 수 있는 문제가 있었습니다.&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;ON CONFLICT&lt;/b&gt; 기능을 통해 개선을 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ON CONFLICT&lt;/b&gt;란,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PostgreSQL에서 INSERT 과정에서 Unique Constraint 충돌이 발생 했을 때, &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지정한 동작으로 처리할 수 있도록 지원하는 기능&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 PostgreSQL의 &lt;b&gt;ON CONFLICT DO NOTHING을 이용하여 DB 레벨에서 중복을 처리&lt;/b&gt;하도록 변경했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1781286130121&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Modifying
@Query(
    value = &quot;&quot;&quot;
        INSERT INTO post_likes (post_id, user_id, created_at, updated_at)
        VALUES (:postId, :userId, NOW(), NOW())
        ON CONFLICT (post_id, user_id) DO NOTHING
        &quot;&quot;&quot;,
    nativeQuery = true
)
int insertIgnore(Long postId, Long userId);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 insertIgnore Repository 메서드는 실제 데이터가 저장되었을 때만 1을 반환합니다.&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;서비스단에서는 이 결과를 기준으로 count를 증가시키도록 수정했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1781286333818&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int inserted = postLikeRepository.insertIgnore(postId, userId);

if (inserted == 1) {
    postRepository.increaseLikeCount(postId);
}&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- Atomic Update를 이용한 count 변경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 JPA Dirty Checking을 이용해 count를 변경했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;여러 트랜잭션이 같은 데이터를 조회하고 수정하는 과정에서 갱신 손실&lt;/b&gt;이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;Atomic Update&lt;/b&gt;를 이용하여 개선했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Atomic Update&lt;/b&gt;는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터를 먼저 조회하지 않고, &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DB가 하나의 SQL 문에서 직접 값을 변경하여 동시 요청 상황에서도 안전하게 연산&lt;/b&gt;할 수 있도록 하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 count 증가 및 감소는 DB에서 직접 연산 하도록 수정했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 증가&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781286451393&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Modifying
@Query(&quot;&quot;&quot;
    update Post p
    set p.likeCount = p.likeCount + 1
    where p.id = :postId
&quot;&quot;&quot;)
int increaseLikeCount(Long postId);&lt;/code&gt;&lt;/pre&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;2️⃣ 감소&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781286504427&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Modifying
@Query(&quot;&quot;&quot;
    update Post p
    set p.likeCount = case
        when p.likeCount &amp;gt; 0 then p.likeCount - 1
        else 0
    end
    where p.id = :postId
&quot;&quot;&quot;)
int decreaseLikeCount(Long postId);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취소 기능 역시 동일한 방식으로 실제 delete 성공 여부를 기준으로 count를 감소하도록 수정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;북마크 기능 또한 동일한 구조를 가졌기에 같은 방식으로 개선했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 개선 효과와 한계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 개선의 핵심은 &lt;b&gt;count 변경 기준을 요청 자체가 아닌 실제 데이터 변경 여부로 변경한 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 다음과 같은 문제를 해결할 수 있습니다.&lt;/p&gt;
&lt;p 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 data-end=&quot;2694&quot; data-start=&quot;2670&quot;&gt;중복 요청으로 인한 count 증가 문제&lt;/li&gt;
&lt;li data-end=&quot;2723&quot; data-start=&quot;2695&quot;&gt;insert 실패 후 count만 증가하는 문제&lt;/li&gt;
&lt;li data-end=&quot;2747&quot; data-start=&quot;2724&quot;&gt;중복 취소로 인한 count 감소 문제&lt;/li&gt;
&lt;li data-end=&quot;2775&quot; data-start=&quot;2748&quot;&gt;실제 row 수와 count 컬럼 불일치 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 이번 개선은 모든 동시성 문제를 해결한 것은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여전히 특정 게시글에 많은 요청이 집중될 경우,&lt;/p&gt;
&lt;pre id=&quot;code_1781287003059&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UPDATE posts
SET like_count = like_count + 1
WHERE id = :postId;&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;동일한 posts row에 대한 update 경합&lt;/b&gt;은 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개선 후에는 &lt;b&gt;동시 요청이 많은 상황에서 데이터 정합성을 보장하도록 개선&lt;/b&gt;되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선에 대한 결과는 다음 포스팅에서 이어나아 가 보도록 하겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;middot; 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 개선에서는 복잡한 Lock을 도입하기보다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정합성을 보장하기 위해 데이터 변경 순서를 재설계하는 방향을 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 exists &amp;rarr; count 증가 &amp;rarr; insert 구조를 제거하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 데이터가 변경된 경우에만 count를 수정하는 방식으로 수정&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 중복 요청이나 동시 요청이 발생하는 상황에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 데이터와 count 컬럼 간의 불일치를 방지&lt;/b&gt;할 수 있도록 개선했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 동일한 K6 테스트 환경에서 다시 부하 테스트를 진행하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선 전과 비교해가면서 성능 지표와 데이터 정합성이 어떻게 변화했는지 확인해보겠습니다.&lt;/p&gt;</description>
      <category>Project/Moassam</category>
      <category>Atomic Update</category>
      <category>DB</category>
      <category>ON CONFLICT</category>
      <category>postgresql</category>
      <category>sql</category>
      <category>개선</category>
      <category>정합성</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/59</guid>
      <comments>https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-2-%EC%A0%95%ED%95%A9%EC%84%B1-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-ON-CONFLICT-Atomic-Update#entry59comment</comments>
      <pubDate>Sat, 13 Jun 2026 03:05:03 +0900</pubDate>
    </item>
    <item>
      <title>[Moassam] 좋아요/북마크 동시성 정합성 테스트 1 - 테스트 환경 구성 및 K6 부하 테스트(성능 개선 전)</title>
      <link>https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;커뮤니티 서비스에서 실제 좋아요와 북마크 데이터 수는 &lt;/span&gt;&lt;span&gt;posts&lt;/span&gt;&lt;span&gt; 테이블 안에 있는 count 컬럼과 항상 동일해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 여러 사용자가 동시에 같은 게시글에 좋아요나 북마크 요청을 보내는 상황이 발생한다면, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예상하지 못한 데이터 불일치 문제가 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;특히 많은 사용자가 한 번에 접근하는 인기 게시글의 경우 이러한 문제가 발생할 가능성이 높아지기 때문에, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;동시 요청 환경에서도 좋아요와 북마크의 데이터 정합성을 보장하는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 테스트에서는 실제로 동시에 여러 요청을 발생시켜 정합성 문제가 발생하는지 확인하고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;개선 과정을 통해 안정적으로 데이터를 관리할 수 있도록 만들어보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 문제 가설&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 현재의 구현 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Moassam의 좋아요와 북마크 기능은 다음과 같이 구현되어 있습니다.&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;/li&gt;
&lt;li&gt;&lt;b&gt;이미 좋아요/북마크가 있는지 확인&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;없으면 count 증가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;post_likes 또는 post_bookmarks insert&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 단건 요청에서는 문제가 없을 테지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 많은 상황에서의 동시 요청에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 제어가 제대로 이루어지지 않을 때 발생할 수 있는 Race Condition이 발생할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 가설&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;요청 A와 B가 동시에 요청을 보냈습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;요청 A &amp;rarr; exists = false &amp;rarr; count + 1 &amp;rarr; insert 성공&lt;br /&gt;요청 B &amp;rarr; exists = false &amp;rarr; count + 1 &amp;rarr; insert 실패&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;post_likes 또는 post_bookmarks 테이블에서는 Unique Constraint가 존재하기 때문에 중복 데이터 저장은 방지됩니다.&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;하지만, count가 먼저 증가한 뒤 insert가 실패한다면, 실제 데이터와 count 값이 일치하지 않는 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적이면 A와 B 요청 두 개가 모두 처리되어 count는 총 2가 되어야 하는데 말이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 대표적인 Lost Update 문제의 사례입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 테스트 환경 준비&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://myojun.tistory.com/entry/Moassam-Moassam-%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%80%EB%B9%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4-%EC%84%A4%EA%B3%84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이번 테스트에서도 앞서 만들어놓은 더미 데이터를 사용합니다.&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781240523624&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;[Moassam] Moassam 성능 테스트 준비 - 테스트 데이터 생성 및 시나리오 설계&quot; data-og-description=&quot;&amp;middot; 하는 이유?아직 Moassam 프로젝트 전체에 대한 회고 및 기술 정리 포스팅은 작성하지 않았지만, 서비스 운영을 고려했을 때 실제 트래픽 상황에서 어떤 병목이 발생할 수 있는지 검증하고 개선&quot; data-og-host=&quot;myojun.tistory.com&quot; data-og-source-url=&quot;https://myojun.tistory.com/entry/Moassam-Moassam-%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%80%EB%B9%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4-%EC%84%A4%EA%B3%84&quot; data-og-url=&quot;https://myojun.tistory.com/entry/Moassam-Moassam-%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%80%EB%B9%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4-%EC%84%A4%EA%B3%84&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dnjlIJ/dJMb8T976sS/kZWDVuaA3WXkak5NybtQg0/img.png?width=800&amp;amp;height=451&amp;amp;face=0_0_800_451,https://scrap.kakaocdn.net/dn/cHnFAx/dJMb8UHXFkZ/V4dAN4veKWLeTBCrvKJUT0/img.png?width=800&amp;amp;height=451&amp;amp;face=0_0_800_451,https://scrap.kakaocdn.net/dn/bhbVuc/dJMb896b6Hh/EQWmkodYAmTwDkBKESkEek/img.png?width=1202&amp;amp;height=678&amp;amp;face=0_0_1202_678&quot;&gt;&lt;a href=&quot;https://myojun.tistory.com/entry/Moassam-Moassam-%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%80%EB%B9%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4-%EC%84%A4%EA%B3%84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://myojun.tistory.com/entry/Moassam-Moassam-%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%80%EB%B9%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4-%EC%84%A4%EA%B3%84&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dnjlIJ/dJMb8T976sS/kZWDVuaA3WXkak5NybtQg0/img.png?width=800&amp;amp;height=451&amp;amp;face=0_0_800_451,https://scrap.kakaocdn.net/dn/cHnFAx/dJMb8UHXFkZ/V4dAN4veKWLeTBCrvKJUT0/img.png?width=800&amp;amp;height=451&amp;amp;face=0_0_800_451,https://scrap.kakaocdn.net/dn/bhbVuc/dJMb896b6Hh/EQWmkodYAmTwDkBKESkEek/img.png?width=1202&amp;amp;height=678&amp;amp;face=0_0_1202_678');&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;[Moassam] Moassam 성능 테스트 준비 - 테스트 데이터 생성 및 시나리오 설계&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;middot; 하는 이유?아직 Moassam 프로젝트 전체에 대한 회고 및 기술 정리 포스팅은 작성하지 않았지만, 서비스 운영을 고려했을 때 실제 트래픽 상황에서 어떤 병목이 발생할 수 있는지 검증하고 개선&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;myojun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 더미 데이터 초기화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 테스트는 동일한 조건에서 진행하기 위해 각 테스트 전 데이터를 초기화했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1781240567905&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DELETE FROM post_likes WHERE post_id = 100001;
DELETE FROM post_bookmarks WHERE post_id = 100001;

UPDATE posts
SET 
    like_count = 0,
    bookmark_count = 0
WHERE id = 100001;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 정합성 검증 SQL&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하 테스트 이후 실제 데이터와 count 컬럼이 일치하는지 확인했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 좋아요 검증&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781240606938&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 
    p.like_count AS stored_like_count,
    COUNT(pl.id) AS actual_like_count,
    p.like_count - COUNT(pl.id) AS diff
FROM posts p
LEFT JOIN post_likes pl
ON p.id = pl.post_id
WHERE p.id = 100001
GROUP BY p.id;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 북마크 검증&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781240632320&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 
    p.bookmark_count AS stored_bookmark_count,
    COUNT(pb.id) AS actual_bookmark_count,
    p.bookmark_count - COUNT(pb.id) AS diff
FROM posts p
LEFT JOIN post_bookmarks pb
ON p.id = pb.post_id
WHERE p.id = 100001
GROUP BY p.id;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; K6 부하 테스트 준비&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- K6 부하 테스트 시나리오&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 총 4가지 시나리오로 진행했습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 69.1813%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.59179%; text-align: center;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.5575%; text-align: center;&quot;&gt;&lt;b&gt;시나리오&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.4945%; 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: 5.59179%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 35.5575%; text-align: center;&quot;&gt;좋아요 등록 집중&lt;/td&gt;
&lt;td style=&quot;width: 30.4945%; text-align: center;&quot;&gt;여러 사용자가 동시에 좋아요 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.59179%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 35.5575%; text-align: center;&quot;&gt;북마크 등록 집중&lt;/td&gt;
&lt;td style=&quot;width: 30.4945%; text-align: center;&quot;&gt;여러 사용자가 동시에 북마크 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.59179%; text-align: center;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 35.5575%; text-align: center;&quot;&gt;좋아요 등록/취소 혼합&lt;/td&gt;
&lt;td style=&quot;width: 30.4945%; text-align: center;&quot;&gt;등록과 취소 요청이 동시에 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.59179%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 35.5575%; text-align: center;&quot;&gt;북마크 등록/취소 혼합&lt;/td&gt;
&lt;td style=&quot;width: 30.4945%; 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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 시나리오 3, 4를 위한 사전 데이터 준비&lt;/b&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;테스트를 진행하는 것이 측정하기에 효과적일 것이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 혼합 테스트 전에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 유저 1000명 중 500명은 미리 데이터를 가지고 있는 상태로 시작할 수 있도록 했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 좋아요 500개 생성&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781240897998&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO post_likes (post_id, user_id, created_at, updated_at)
SELECT 100001, id, NOW(), NOW()
FROM (
    SELECT id
    FROM users
    WHERE provider = 'KAKAO'
      AND provider_id LIKE 'load-test-user-%'
    ORDER BY id
    LIMIT 500
) u
ON CONFLICT (post_id, user_id) DO NOTHING;


UPDATE posts
SET like_count = (
    SELECT COUNT(*)
    FROM post_likes
    WHERE post_id = 100001
)
WHERE id = 100001;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 북마크 500개 생성&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781240915324&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO post_bookmarks (post_id, user_id, created_at, updated_at)
SELECT 100001, id, NOW(), NOW()
FROM (
    SELECT id
    FROM users
    WHERE provider = 'KAKAO'
      AND provider_id LIKE 'load-test-user-%'
    ORDER BY id
    LIMIT 500
) u
ON CONFLICT (post_id, user_id) DO NOTHING;


UPDATE posts
SET bookmark_count = (
    SELECT COUNT(*)
    FROM post_bookmarks
    WHERE post_id = 100001
)
WHERE id = 100001;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- JWT 인증 토큰 생성&lt;/b&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;따로 테스트에서 사용할 JWT 토큰을 미리 생성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1781241108888&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT id
FROM users
WHERE provider = 'KAKAO'
  AND provider_id LIKE 'load-test-user-%'
ORDER BY id
LIMIT 1000;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 이전 부하 테스트에서 만들어둔 1000개의 테스트 유저 데이터의 Id를 csv 형태로 따로 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEvWI5/dJMcafUu6b8/ZtBrgvUNmejKfhDPsgrWUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEvWI5/dJMcafUu6b8/ZtBrgvUNmejKfhDPsgrWUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEvWI5/dJMcafUu6b8/ZtBrgvUNmejKfhDPsgrWUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEvWI5%2FdJMcafUu6b8%2FZtBrgvUNmejKfhDPsgrWUk%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;951&quot; height=&quot;222&quot; data-origin-width=&quot;951&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;그 후 개발 서버에서 사용하는 JWT_SECRET을 이용해 테스트용 JWT 토큰을 생성했습니다.&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;이때 JWT_SECRET은 환경 변수로 주입했으며, 실제 값은 코드나 문서에 노출하지 않았습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1781241227489&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const crypto = require('crypto');
const fs = require('fs');

const secret = process.env.JWT_SECRET;
if (!secret) throw new Error('JWT_SECRET is required');

const users = fs.readFileSync('moassam_dev_public_users.csv', 'utf8')
  .trim()
  .split('\n')
  .slice(1)
  .map((line) =&amp;gt; line.trim())
  .filter(Boolean);

function base64url(value) {
  return Buffer.from(value)
    .toString('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');
}

function sign(userId) {
  const now = Math.floor(Date.now() / 1000);

  const header = { alg: 'HS256', typ: 'JWT' };
  const payload = {
    sub: String(userId),
    iat: now,
    exp: now + 60 * 60 * 24 * 7,
  };

  const encodedHeader = base64url(JSON.stringify(header));
  const encodedPayload = base64url(JSON.stringify(payload));
  const data = `${encodedHeader}.${encodedPayload}`;

  const signature = crypto
    .createHmac('sha256', secret)
    .update(data)
    .digest('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');

  return `${data}.${signature}`;
}

const rows = users.map((userId) =&amp;gt; `${userId},${sign(userId)}`);

fs.writeFileSync('tokens.csv', `userId,token\n${rows.join('\n')}\n`);
console.log(`Generated ${rows.length} tokens`);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 만료 시간은 넉넉하게 7일로 설정했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;처음에는 6시간으로 설정했다가 다음날 자고 일어나서 해야지 하고 잠에 들었는데,&lt;br /&gt;토큰이 초기화되는 일이 있었기에... 넉넉하게 설정했습니다!&lt;/blockquote&gt;
&lt;pre id=&quot;code_1781241246349&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$env:JWT_SECRET=&quot;개발서버_JWT_SECRET&quot;
node generate-tokens.js&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  JWT 토큰 생성 확인해 보기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 파일을 들어가서 확인해 본 결과&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYMFaH/dJMcaip7qWz/pdTTLdipLZbd9lMSF48KYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYMFaH/dJMcaip7qWz/pdTTLdipLZbd9lMSF48KYk/img.png&quot; data-alt=&quot;생성된 tokens.csv&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYMFaH/dJMcaip7qWz/pdTTLdipLZbd9lMSF48KYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYMFaH%2FdJMcaip7qWz%2FpdTTLdipLZbd9lMSF48KYk%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;358&quot; height=&quot;311&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;생성된 tokens.csv&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 생성된 모습입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 JWT토큰이 잘 작동하는지 확인해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 방식은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 서버에 tokens.csv 파일을 옮기고, 생성된 JWT 토큰이 실제 API에서 정상적으로 인증되는지 확인했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1781241436595&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TOKEN=$(sed -n '2p' /tmp/tokens.csv | cut -d',' -f2)

curl -i -X POST &quot;https://개발서버_IP/api/v1/posts/100001/likes&quot; \
  -H &quot;Authorization: Bearer $TOKEN&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과&lt;/p&gt;
&lt;pre id=&quot;code_1781241485511&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;유저@moassam-dev:~$ TOKEN=$(sed -n '2p' /tmp/tokens.csv | cut -d',' -f2)

curl -i -X POST &quot;https://개발서버_IP/api/v1/posts/100001/likes&quot; \
  -H &quot;Authorization: Bearer $TOKEN&quot;
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 10 Jun 2026 17:59:43 GMT

{&quot;data&quot;:null}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 200 상태 코드가 반환되었고, 이를 통해 생성한 JWT 토큰이 정상적으로 인증에 사용되는 것을 확인했습니다.&lt;/p&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; K6 부하 테스트 (개선 전)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 테스트 환경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 환경은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트 유저&lt;/b&gt;: 1000명&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대상 게시글&lt;/b&gt;: 1개 (id = 100001)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부하&lt;/b&gt; &lt;b&gt;도구&lt;/b&gt;: K6&lt;/li&gt;
&lt;li&gt;&lt;b&gt;측정 API&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;POST /api/v1/posts/{postId}/likes&lt;/li&gt;
&lt;li&gt;DELETE /api/v1/posts/{postId}/likes&lt;/li&gt;
&lt;li&gt;POST /api/v1/posts/{postId}/bookmarks&lt;/li&gt;
&lt;li&gt;DELETE /api/v1/posts/{postId}/bookmarks&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;시나리오 1 - 좋아요 등록 집중&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일 게시글에 여러 유저가 동시에 좋아요 등록&amp;nbsp;&lt;/li&gt;
&lt;li&gt;동시 사용자(VU): 100/300/500/1000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시나리오 2 - 북마크 등록 집중&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일 게시글에 여러 유저가 동시에 북마크 등록&lt;/li&gt;
&lt;li&gt;동시 사용자(VU): 100/300/500/1000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시나리오 3 - 좋아요 등록/취소 혼합 테스트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일부 유저는 POST /likes, 일부 유저는 DELETE /likes&lt;/li&gt;
&lt;li&gt;동시 사용자(VU): 100/1000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시나리오 4 - 북마크 등록/취소 혼합 테스트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일부 유저는 POST /bookmarks, 일부 유저는 DELETE /bookmarks&lt;/li&gt;
&lt;li&gt;동시 사용자(VU): 100/1000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증 방식&lt;/b&gt;: JWT&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 시간&lt;/b&gt; 30초&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;K6&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http_req_failed&lt;/li&gt;
&lt;li&gt;http_req_duration_avg&lt;/li&gt;
&lt;li&gt;http_req_duration_p95&lt;/li&gt;
&lt;li&gt;http_req_duration_p99&lt;/li&gt;
&lt;li&gt;status&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DB&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored count(실제 저장된 값)&lt;/li&gt;
&lt;li&gt;actual row count(기댓값)&lt;/li&gt;
&lt;li&gt;diff(stored count - actual row count)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 시나리오 1. 좋아요 등록 집중&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  테스트 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781243909667&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

const BASE_URL = __ENV.BASE_URL;
const POST_ID = __ENV.POST_ID;
const VUS = Number(__ENV.VUS || 100);
const DURATION = __ENV.DURATION || '30s';

const tokens = new SharedArray('tokens', function () {
  return open('./tokens.csv')
    .trim()
    .split('\n')
    .slice(1)
    .map((line) =&amp;gt; {
      const [userId, token] = line.split(',');
      return { userId, token };
    });
});

export const options = {
  scenarios: {
    like_create: {
      executor: 'constant-vus',
      vus: VUS,
      duration: DURATION,
    },
  },
  thresholds: {
    http_req_failed: ['rate&amp;lt;0.01'],
    http_req_duration: ['p(95)&amp;lt;1000', 'p(99)&amp;lt;3000'],
  },
};

export default function () {
  const user = tokens[(__VU - 1) % tokens.length];

  const res = http.post(
    `${BASE_URL}/api/v1/posts/${POST_ID}/likes`,
    null,
    {
      headers: {
        Authorization: `Bearer ${user.token}`,
      },
      tags: {
        api: 'like_create',
      },
    }
  );

  check(res, {
    'status is 200': (r) =&amp;gt; r.status === 200,
    'not 5xx': (r) =&amp;gt; r.status &amp;lt; 500,
  });

  sleep(0.1);
}&lt;/code&gt;&lt;/pre&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;  테스트 ENV&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BASE_URL=&quot;https://개발서버주소&quot;&lt;/li&gt;
&lt;li&gt;POST_ID=&quot;100001&quot;&lt;/li&gt;
&lt;li&gt;VUS=가상 유저 수&lt;/li&gt;
&lt;li&gt;DURATION=실행 시간&lt;/li&gt;
&lt;/ul&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;1️⃣ 100VU&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781244058026&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=100 `
  -e DURATION=&quot;30s&quot; `
  .\like-create.js *&amp;gt; .\results\before\like-create-100vu-before.txt&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;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781244091222&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=903.52ms
    ??'p(99)&amp;lt;3000' p(99)=1.27s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=0.00%


  ??TOTAL RESULTS 

    checks_total.......: 11998   396.619119/s
    checks_succeeded...: 100.00% 11998 out of 11998
    checks_failed......: 0.00%   0 out of 11998

    ??status is 200
    ??not 5xx

    HTTP
    http_req_duration..............: avg=389.67ms min=55.21ms  med=345.43ms max=2.33s p(90)=783.31ms p(95)=903.52ms
      { expected_response:true }...: avg=389.67ms min=55.21ms  med=345.43ms max=2.33s p(90)=783.31ms p(95)=903.52ms
    http_req_failed................: 0.00%  0 out of 5999
    http_reqs......................: 5999   198.30956/s

    EXECUTION
    iteration_duration.............: avg=502.25ms min=155.47ms med=447.61ms max=3.17s p(90)=910.25ms p(95)=1.05s   
    iterations.....................: 5999   198.30956/s
    vus............................: 100    min=100       max=100
    vus_max........................: 100    min=100       max=100

    NETWORK
    data_received..................: 3.7 MB 123 kB/s
    data_sent......................: 2.0 MB 67 kB/s




running (0m30.3s), 000/100 VUs, 5999 complete and 0 interrupted iterations
like_create ??[ 100% ] 100 VUs  30s&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;http_reqs: 5,999건&lt;/li&gt;
&lt;li&gt;http_req_failed: 0.00% (0건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 389.67ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 903.52ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 1.27s&lt;/li&gt;
&lt;li&gt;checks: 100.00% (11,998 / 11,998)&lt;/li&gt;
&lt;li&gt;status 200: 5,999건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;75&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIGlkO/dJMcaiRbh0K/h9KkQBSg31qnDVXDJPrf70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIGlkO/dJMcaiRbh0K/h9KkQBSg31qnDVXDJPrf70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIGlkO/dJMcaiRbh0K/h9KkQBSg31qnDVXDJPrf70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIGlkO%2FdJMcaiRbh0K%2Fh9KkQBSg31qnDVXDJPrf70%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;75&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;75&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count: 17&lt;/li&gt;
&lt;li&gt;actual_like_count: 100&lt;/li&gt;
&lt;li&gt;diff: -83&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 300VU&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781244209245&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=300 `
  -e DURATION=&quot;30s&quot; `
  .\like-create.js *&amp;gt; .\results\before\like-create-300vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781244219515&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=2.03s
    ??'p(99)&amp;lt;3000' p(99)=2.48s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=18.02%


  ??TOTAL RESULTS 

    checks_total.......: 16022  510.25474/s
    checks_succeeded...: 90.98% 14578 out of 16022
    checks_failed......: 9.01%  1444 out of 16022

    ??status is 200
      ?? 81% ????6567 / ??1444
    ??not 5xx

    HTTP
    http_req_duration..............: avg=688.83ms min=0s       med=400.93ms max=3.93s p(90)=1.65s p(95)=2.03s
      { expected_response:true }...: avg=837.88ms min=12.19ms  med=811.11ms max=3.93s p(90)=1.73s p(95)=2.16s
    http_req_failed................: 18.02% 1444 out of 8011
    http_reqs......................: 8011   255.12737/s

    EXECUTION
    iteration_duration.............: avg=1.13s    min=100.89ms med=972.77ms max=5.64s p(90)=2.41s p(95)=2.78s
    iterations.....................: 8011   255.12737/s
    vus............................: 32     min=32           max=300
    vus_max........................: 300    min=300          max=300

    NETWORK
    data_received..................: 26 MB  811 kB/s
    data_sent......................: 9.3 MB 295 kB/s




running (0m31.4s), 000/300 VUs, 8011 complete and 0 interrupted iterations
like_create ??[ 100% ] 300 VUs  30s&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;http_reqs: 8,011건&lt;/li&gt;
&lt;li&gt;http_req_failed: 18.02% (1,444건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 688.83ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 2.03s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 2.48s&lt;/li&gt;
&lt;li&gt;checks: 90.98% (14,578 / 16,022)&lt;/li&gt;
&lt;li&gt;status 200: 6,567건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xlAFM/dJMcac4uDho/nNpKTtH4bJjxwnxdX4t82K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xlAFM/dJMcac4uDho/nNpKTtH4bJjxwnxdX4t82K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xlAFM/dJMcac4uDho/nNpKTtH4bJjxwnxdX4t82K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxlAFM%2FdJMcac4uDho%2FnNpKTtH4bJjxwnxdX4t82K%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;1106&quot; height=&quot;73&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 60&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 300&lt;/li&gt;
&lt;li&gt;diff: -240&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ 500VU&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781244369508&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=500 `
  -e DURATION=&quot;30s&quot; `
  .\like-create.js *&amp;gt; .\results\before\like-create-500vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781244379159&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=216.53ms
    ??'p(99)&amp;lt;3000' p(99)=708.27ms

    http_req_failed
    ??'rate&amp;lt;0.01' rate=23.46%


  ??TOTAL RESULTS 

    checks_total.......: 14364  314.694287/s
    checks_succeeded...: 88.26% 12679 out of 14364
    checks_failed......: 11.73% 1685 out of 14364

    ??status is 200
      ?? 76% ????5497 / ??1685
    ??not 5xx

    HTTP
    http_req_duration..............: avg=83.98ms  min=0s       med=60.03ms max=6.59s  p(90)=114.61ms p(95)=216.53ms
      { expected_response:true }...: avg=103.04ms min=39.17ms  med=65.39ms max=6.59s  p(90)=139.48ms p(95)=341ms   
    http_req_failed................: 23.46% 1685 out of 7182
    http_reqs......................: 7182   157.347143/s

    EXECUTION
    iteration_duration.............: avg=2.14s    min=101.25ms med=1.71s   max=21.23s p(90)=4.98s    p(95)=6.19s   
    iterations.....................: 7182   157.347143/s
    vus............................: 1      min=1            max=500
    vus_max........................: 500    min=500          max=500

    NETWORK
    data_received..................: 31 MB  671 kB/s
    data_sent......................: 11 MB  243 kB/s




running (0m45.6s), 000/500 VUs, 7182 complete and 0 interrupted iterations
like_create ??[ 100% ] 500 VUs  30s
time=&quot;2026-06-11T04:19:38+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_failed' have been crosse
d&quot;&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;http_reqs: 7,182건&lt;/li&gt;
&lt;li&gt;http_req_failed: 23.46% (1,685건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 83.98ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 216.53ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 708.27ms&lt;/li&gt;
&lt;li&gt;checks: 88.26% (12,679 / 14,364)&lt;/li&gt;
&lt;li&gt;status 200: 5,497건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때부터 http_req_failed threshold가 초과되어 K6는 실패로 판단됩니다.&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;500명의 사용자가 동시에 좋아요 요청을 보냈을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 속도는 p95 261ms로 안정적이었으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 요청의 23.46%가 실패하여 동시 요청 상황에서 안정적인 처리가 이루어지지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMIpOt/dJMcafmFV0x/LDB7oliq6KSU2LgW0blHiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMIpOt/dJMcafmFV0x/LDB7oliq6KSU2LgW0blHiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMIpOt/dJMcafmFV0x/LDB7oliq6KSU2LgW0blHiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMIpOt%2FdJMcafmFV0x%2FLDB7oliq6KSU2LgW0blHiK%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;1100&quot; height=&quot;73&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 144&lt;/li&gt;
&lt;li&gt;actual_like_count(기대값): 500&lt;/li&gt;
&lt;li&gt;diff: -356&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ 1000VU&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781244543959&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=1000 `
  -e DURATION=&quot;30s&quot; `
  .\like-create.js *&amp;gt; .\results\before\like-create-1000vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781244552699&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=1.22s
    ??'p(99)&amp;lt;3000' p(99)=1.71s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=64.57%


  ??TOTAL RESULTS 

    checks_total.......: 20808  379.398833/s
    checks_succeeded...: 67.71% 14090 out of 20808
    checks_failed......: 32.28% 6718 out of 20808

    ??status is 200
      ?? 35% ????3686 / ??6718
    ??not 5xx

    HTTP
    http_req_duration..............: avg=310.51ms min=0s       med=0s       max=6.49s  p(90)=1.03s p(95)=1.22s
      { expected_response:true }...: avg=518.14ms min=7.97ms   med=417.39ms max=6.49s  p(90)=1.08s p(95)=1.31s
    http_req_failed................: 64.57% 6718 out of 10404
    http_reqs......................: 10404  189.699416/s

    EXECUTION
    iteration_duration.............: avg=2.96s    min=108.58ms med=2.55s    max=32.58s p(90)=5.63s p(95)=6.62s
    iterations.....................: 10404  189.699416/s
    vus............................: 1      min=1             max=1000
    vus_max........................: 1000   min=1000          max=1000

    NETWORK
    data_received..................: 21 MB  391 kB/s
    data_sent......................: 15 MB  279 kB/s




running (0m54.8s), 0000/1000 VUs, 10404 complete and 0 interrupted iterations
like_create ??[ 100% ] 1000 VUs  30s
time=&quot;2026-06-11T04:35:33+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_duration, http_req_faile
d' have been crossed&quot;&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;http_reqs: 10,404건&lt;/li&gt;
&lt;li&gt;http_req_failed: 64.57% (6,718건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 310.51ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 1.22s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 1.71s&lt;/li&gt;
&lt;li&gt;checks: 67.71% (14,090 / 20,808)&lt;/li&gt;
&lt;li&gt;status 200: 3,686건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cb48n5/dJMcaip7vQF/Ikxf4hrlwTKafKfzSYcl0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cb48n5/dJMcaip7vQF/Ikxf4hrlwTKafKfzSYcl0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cb48n5/dJMcaip7vQF/Ikxf4hrlwTKafKfzSYcl0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcb48n5%2FdJMcaip7vQF%2FIkxf4hrlwTKafKfzSYcl0K%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;1102&quot; height=&quot;77&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 622&lt;/li&gt;
&lt;li&gt;actual_like_count(기대값): 998&lt;/li&gt;
&lt;li&gt;diff: -376&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  K6 부하테스트 (개선 전) 결과 비교 분석&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 88.2558%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 23.9535%;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;&lt;b&gt;100 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.4651%;&quot;&gt;&lt;b&gt;300 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3953%;&quot;&gt;&lt;b&gt;500 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;&lt;b&gt;1000 VU&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 23.9535%;&quot;&gt;&lt;b&gt;총 요청 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;5,999&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.4651%;&quot;&gt;8,011&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3953%;&quot;&gt;7,182&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;10,404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 23.9535%;&quot;&gt;&lt;b&gt;요청 실패율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;0.00%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.4651%;&quot;&gt;18.02%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3953%;&quot;&gt;23.46%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;64.57%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 23.9535%;&quot;&gt;&lt;b&gt;p95 응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;903ms&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.4651%;&quot;&gt;2.03s&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3953%;&quot;&gt;216ms&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;1.22s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 23.9535%;&quot;&gt;&lt;b&gt;HTTP 200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;5,999&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.4651%;&quot;&gt;6,567&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3953%;&quot;&gt;5,497&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;3,686&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 23.9535%;&quot;&gt;&lt;b&gt;5xx 오류&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;0건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.4651%;&quot;&gt;0건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3953%;&quot;&gt;0건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;0건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 23.9535%;&quot;&gt;&lt;b&gt;stored_like_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;17&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.4651%;&quot;&gt;60&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3953%;&quot;&gt;144&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;622&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 23.9535%;&quot;&gt;&lt;b&gt;actual_like_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.4651%;&quot;&gt;300&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3953%;&quot;&gt;500&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;998&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 23.9535%;&quot;&gt;&lt;b&gt;데이터 차이(diff)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;-83&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.4651%;&quot;&gt;-240&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3953%;&quot;&gt;-356&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1628%;&quot;&gt;-376&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣ 100 VU 환경에서도 데이터 정합성 문제 발생&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100명의 사용자가 동시에 좋아요 요청을 보냈을 때 모든 HTTP 요청은 정상적으로 처리되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 DB 정합성 확인 결과,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 좋아요 데이터(post_likes)는 100건 저장되었지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 posts.like_count는 17건만 반영되어 &lt;b&gt;83건의 좋아요 수 증가가 누락&lt;/b&gt;되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 낮은 동시성 환경에서도 좋아요 수 증가 과정에서 이미 데이터 &lt;b&gt;정합성 문제&lt;/b&gt;가 발생하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2️⃣동시 사용자 증가와 함께 요청 실패율 증가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;300 VU부터는 요청 실패가 발생하기 시작했습니다.&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;300 VU&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 실패율: 18.02%&lt;/li&gt;
&lt;li&gt;p95 응답 시간: 2.03초&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;500 VU&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 실패율: 23.46%&lt;/li&gt;
&lt;li&gt;p95 응답 시간: 216.53ms&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;500 VU 테스트에서는 응답 속도 자체는 안정적이었지만, 요청 실패율이 증가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 현상으로 봐서는 동시 요청 처리 과정에서 충돌 또는 예외가 발생하고 있음을 짐작할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3️⃣ 1000VU 환경에서 서비스 안정성 저하&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1000명의 사용자가 동시에 좋아요 요청을 보낸 결과, 서비스 안정성이 크게 저하되었습니다.&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;64.57%&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;p95 응답 시간: &lt;b&gt;1.22초&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;HTTP 200 응답: &lt;b&gt;3,686건&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 요청 실패율이 절반 이상 발생하였으며, K6에서 설정한 성능 기준인 http_req_failed &amp;lt; 1%와 p95 &amp;lt; 1초를 모두 만족하지 못하고 있는 모습입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4️⃣ 동시성 증가에 따른 데이터 정합성 문제 심화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 좋아요 데이터(post_likes)와 게시글의 좋아요 개수(posts.like_count)를 비교한 결과,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시 사용자가 증가할수록 데이터 손실량이 증가했습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 76.6279%; height: 85px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 13.4884%;&quot;&gt;&lt;b&gt;VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 22.093%;&quot;&gt;&lt;b&gt;실제 좋아요 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 20.1855%;&quot;&gt;&lt;b&gt;저장된 like_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 19.8566%;&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;height: 17px; text-align: center; width: 13.4884%;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 22.093%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 20.1855%;&quot;&gt;17&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 19.8566%;&quot;&gt;83&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 13.4884%;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 22.093%;&quot;&gt;300&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 20.1855%;&quot;&gt;60&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 19.8566%;&quot;&gt;240&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 13.4884%;&quot;&gt;&lt;b&gt;500&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 22.093%;&quot;&gt;500&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 20.1855%;&quot;&gt;144&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 19.8566%;&quot;&gt;356&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 13.4884%;&quot;&gt;&lt;b&gt;1000&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 22.093%;&quot;&gt;998&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 20.1855%;&quot;&gt;622&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 19.8566%;&quot;&gt;376&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 여러 사용자가 동시에 동일한 게시글의 like_count를 증가시키는 과정에서 발생한 &lt;b&gt;Lost Update(갱신 손실)&amp;nbsp;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 시나리오 2. 북마크 등록 집중&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  테스트 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781245271112&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

const BASE_URL = __ENV.BASE_URL;
const POST_ID = __ENV.POST_ID;
const VUS = Number(__ENV.VUS || 100);
const DURATION = __ENV.DURATION || '30s';

const tokens = new SharedArray('tokens', function () {
  return open('./tokens.csv')
    .trim()
    .split('\n')
    .slice(1)
    .map((line) =&amp;gt; {
      const [userId, token] = line.split(',');
      return { userId, token };
    });
});

export const options = {
  scenarios: {
    bookmark_create: {
      executor: 'constant-vus',
      vus: VUS,
      duration: DURATION,
    },
  },
  thresholds: {
    http_req_failed: ['rate&amp;lt;0.01'],
    http_req_duration: ['p(95)&amp;lt;1000', 'p(99)&amp;lt;3000'],
  },
};

export default function () {
  const user = tokens[(__VU - 1) % tokens.length];

  const res = http.post(
    `${BASE_URL}/api/v1/posts/${POST_ID}/bookmarks`,
    null,
    {
      headers: {
        Authorization: `Bearer ${user.token}`,
      },
      tags: {
        api: 'bookmark_create',
      },
    }
  );

  check(res, {
    'status is 200': (r) =&amp;gt; r.status === 200,
    'not 5xx': (r) =&amp;gt; r.status &amp;lt; 500,
  });

  sleep(0.1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  테스트 ENV&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동일&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 100VU&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781245306177&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=100 `
  -e DURATION=&quot;30s&quot; `
  .\bookmark-create.js *&amp;gt; .\results\before\bookmark-create-100vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781245315512&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=748.87ms
    ??'p(99)&amp;lt;3000' p(99)=1s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=0.00%


  ??TOTAL RESULTS 

    checks_total.......: 15044   498.075038/s
    checks_succeeded...: 100.00% 15044 out of 15044
    checks_failed......: 0.00%   0 out of 15044

    ??status is 200
    ??not 5xx

    HTTP
    http_req_duration..............: avg=292.42ms min=42.99ms  med=241.31ms max=2.01s p(90)=594.79ms p(95)=748.87ms
      { expected_response:true }...: avg=292.42ms min=42.99ms  med=241.31ms max=2.01s p(90)=594.79ms p(95)=748.87ms
    http_req_failed................: 0.00%  0 out of 7522
    http_reqs......................: 7522   249.037519/s

    EXECUTION
    iteration_duration.............: avg=400.33ms min=143.28ms med=341.69ms max=2.76s p(90)=705.55ms p(95)=887.91ms
    iterations.....................: 7522   249.037519/s
    vus............................: 100    min=100       max=100
    vus_max........................: 100    min=100       max=100

    NETWORK
    data_received..................: 4.5 MB 150 kB/s
    data_sent......................: 2.5 MB 83 kB/s




running (0m30.2s), 000/100 VUs, 7522 complete and 0 interrupted iterations
bookmark_create ??[ 100% ] 100 VUs  30s&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;http_reqs: 7,522건&lt;/li&gt;
&lt;li&gt;http_req_failed: 0.00% (0건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 292.42ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 748.87ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 1s&lt;/li&gt;
&lt;li&gt;checks: 100.00% (15,044 / 15,044)&lt;/li&gt;
&lt;li&gt;status 200: 7,522건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCvOu8/dJMcafGYq0L/e0Juf5UcFZahDzQ8rPrEcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCvOu8/dJMcafGYq0L/e0Juf5UcFZahDzQ8rPrEcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCvOu8/dJMcafGYq0L/e0Juf5UcFZahDzQ8rPrEcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCvOu8%2FdJMcafGYq0L%2Fe0Juf5UcFZahDzQ8rPrEcK%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;1197&quot; height=&quot;72&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 17&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 100&lt;/li&gt;
&lt;li&gt;diff: -83&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 300VU&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781245369939&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=300 `
  -e DURATION=&quot;30s&quot; `
  .\bookmark-create.js *&amp;gt; .\results\before\bookmark-create-300vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781245382568&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=1.06s
    ??'p(99)&amp;lt;3000' p(99)=1.41s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=21.01%


  ??TOTAL RESULTS 

    checks_total.......: 35678  1135.884549/s
    checks_succeeded...: 89.49% 31930 out of 35678
    checks_failed......: 10.50% 3748 out of 35678

    ??status is 200
      ?? 78% ????14091 / ??3748
    ??not 5xx

    HTTP
    http_req_duration..............: avg=355.76ms min=0s       med=159.09ms max=2.69s p(90)=864.03ms p(95)=1.06s
      { expected_response:true }...: avg=441.2ms  min=38.77ms  med=354.43ms max=2.69s p(90)=942.45ms p(95)=1.1s 
    http_req_failed................: 21.01% 3748 out of 17839
    http_reqs......................: 17839  567.942275/s

    EXECUTION
    iteration_duration.............: avg=508.75ms min=102.34ms med=332.84ms max=4.57s p(90)=1.05s    p(95)=1.27s
    iterations.....................: 17839  567.942275/s
    vus............................: 6      min=6             max=300
    vus_max........................: 300    min=300           max=300

    NETWORK
    data_received..................: 30 MB  948 kB/s
    data_sent......................: 12 MB  388 kB/s




running (0m31.4s), 000/300 VUs, 17839 complete and 0 interrupted iterations
bookmark_create ??[ 100% ] 300 VUs  30s
time=&quot;2026-06-11T14:30:59+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_dur
ation, http_req_failed' have been crossed&quot;&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;http_reqs: 17,839건&lt;/li&gt;
&lt;li&gt;http_req_failed: 21.01% (3,748건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 355.76ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 1.06s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 1.41s&lt;/li&gt;
&lt;li&gt;checks: 89.49% (31,930 / 35,678)&lt;/li&gt;
&lt;li&gt;status 200: 14,091건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kRhTg/dJMcajvNM6q/5bfUk0kDkmWt1tLyeBhJFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kRhTg/dJMcajvNM6q/5bfUk0kDkmWt1tLyeBhJFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kRhTg/dJMcajvNM6q/5bfUk0kDkmWt1tLyeBhJFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkRhTg%2FdJMcajvNM6q%2F5bfUk0kDkmWt1tLyeBhJFk%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;1202&quot; height=&quot;72&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 52&lt;/li&gt;
&lt;li&gt;actual_like_count(기대값): 300&lt;/li&gt;
&lt;li&gt;diff: -248&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ 500VU&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781245432714&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=500 `
  -e DURATION=&quot;30s&quot; `
  .\bookmark-create.js *&amp;gt; .\results\before\bookmark-create-500vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781245439548&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=704.85ms
    ??'p(99)&amp;lt;3000' p(99)=2.07s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=33.97%


  ??TOTAL RESULTS 

    checks_total.......: 37616  960.602994/s
    checks_succeeded...: 82.98% 31215 out of 37616
    checks_failed......: 17.01% 6401 out of 37616

    ??status is 200
      ?? 66% ????12418 / ??6390
    ??not 5xx
      ?? 99% ????18797 / ??11

    HTTP
    http_req_duration..............: avg=220.08ms min=0s       med=127.22ms max=4.86s  p(90)=399.72ms p(95)=704.85ms
      { expected_response:true }...: avg=314.12ms min=38.56ms  med=197.83ms max=4.86s  p(90)=555.48ms p(95)=1.01s   
    http_req_failed................: 33.97% 6390 out of 18808
    http_reqs......................: 18808  480.301497/s

    EXECUTION
    iteration_duration.............: avg=826.89ms min=101.81ms med=344.18ms max=31.36s p(90)=1.92s    p(95)=2.41s   
    iterations.....................: 18808  480.301497/s
    vus............................: 5      min=5             max=500
    vus_max........................: 500    min=500           max=500

    NETWORK
    data_received..................: 41 MB  1.1 MB/s
    data_sent......................: 18 MB  446 kB/s




running (0m39.2s), 000/500 VUs, 18808 complete and 0 interrupted iterations
bookmark_create ??[ 100% ] 500 VUs  30s
time=&quot;2026-06-11T14:36:37+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_fai
led' have been crossed&quot;&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;http_reqs: 18,808건&lt;/li&gt;
&lt;li&gt;http_req_failed: 33.97% (6,390건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 220.08ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 704.85ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 2.07s&lt;/li&gt;
&lt;li&gt;checks: 82.98% (31,215 / 37,616)&lt;/li&gt;
&lt;li&gt;status 200: 12,418건&lt;/li&gt;
&lt;li&gt;status 5xx: 11&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;75&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m3sbG/dJMcadI1SKE/hiTtxAp8Xkw3WbgTFlO3Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m3sbG/dJMcadI1SKE/hiTtxAp8Xkw3WbgTFlO3Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m3sbG/dJMcadI1SKE/hiTtxAp8Xkw3WbgTFlO3Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm3sbG%2FdJMcadI1SKE%2FhiTtxAp8Xkw3WbgTFlO3Kk%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;1193&quot; height=&quot;75&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;75&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 84&lt;/li&gt;
&lt;li&gt;actual_like_count(기대값): 494&lt;/li&gt;
&lt;li&gt;diff: -410&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4️⃣ 1000VU&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781245486392&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=1000 `
  -e DURATION=&quot;30s&quot; `
  .\bookmark-create.js *&amp;gt; .\results\before\bookmark-create-1000vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781245495621&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1000' p(95)=1.78s
    ??'p(99)&amp;lt;3000' p(99)=7.74s

    http_req_failed
    ??'rate&amp;lt;0.01' rate=54.99%


  ??TOTAL RESULTS 

    checks_total.......: 20260  361.558877/s
    checks_succeeded...: 72.50% 14689 out of 20260
    checks_failed......: 27.49% 5571 out of 20260

    ??status is 200
      ?? 45% ????4559 / ??5571
    ??not 5xx

    HTTP
    http_req_duration..............: avg=532.9ms  min=0s       med=118.28ms max=17.13s p(90)=1.03s p(95)=1.78s
      { expected_response:true }...: avg=948.75ms min=38.49ms  med=405.91ms max=17.13s p(90)=1.82s p(95)=5.45s
    http_req_failed................: 54.99% 5571 out of 10130
    http_reqs......................: 10130  180.779438/s

    EXECUTION
    iteration_duration.............: avg=3.15s    min=102.16ms med=2s       max=56.03s p(90)=8.05s p(95)=9.86s
    iterations.....................: 10130  180.779438/s
    vus............................: 5      min=5             max=1000
    vus_max........................: 1000   min=1000          max=1000

    NETWORK
    data_received..................: 24 MB  425 kB/s
    data_sent......................: 15 MB  259 kB/s




running (0m56.0s), 0000/1000 VUs, 10130 complete and 0 interrupted iterations
bookmark_create ??[ 100% ] 1000 VUs  30s
time=&quot;2026-06-11T14:40:40+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_dur
ation, http_req_failed' have been crossed&quot;&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;http_reqs: 10,130건&lt;/li&gt;
&lt;li&gt;http_req_failed: 54.99% (5,571건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 532.9ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 1.78s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 7.74s&lt;/li&gt;
&lt;li&gt;checks: 72.50% (14,689 / 20,260)&lt;/li&gt;
&lt;li&gt;status 200: 4,559건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TZXz6/dJMcai4EYun/4gNVZHmRs9k65at09ltCX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TZXz6/dJMcai4EYun/4gNVZHmRs9k65at09ltCX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TZXz6/dJMcai4EYun/4gNVZHmRs9k65at09ltCX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTZXz6%2FdJMcai4EYun%2F4gNVZHmRs9k65at09ltCX1%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;1198&quot; height=&quot;77&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 582&lt;/li&gt;
&lt;li&gt;actual_like_count(기대값): 998&lt;/li&gt;
&lt;li&gt;diff: -416&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  K6 부하테스트 (개선 전) 결과 비교 분석&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 92.4419%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 26.9767%;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.7844%;&quot;&gt;&lt;b&gt;100 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.9588%;&quot;&gt;&lt;b&gt;300 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1152%;&quot;&gt;&lt;b&gt;500 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3742%;&quot;&gt;&lt;b&gt;1000 VU&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 26.9767%;&quot;&gt;&lt;b&gt;총 요청 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.7844%;&quot;&gt;7,522&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.9588%;&quot;&gt;17,839&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1152%;&quot;&gt;18,808&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3742%;&quot;&gt;10,130&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 26.9767%;&quot;&gt;&lt;b&gt;요청 실패율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.7844%;&quot;&gt;0.00%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.9588%;&quot;&gt;21.01%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1152%;&quot;&gt;33.97%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3742%;&quot;&gt;54.99%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 26.9767%;&quot;&gt;&lt;b&gt;p95 응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.7844%;&quot;&gt;748.87ms&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.9588%;&quot;&gt;1.06s&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1152%;&quot;&gt;704.85ms&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3742%;&quot;&gt;1.78s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 26.9767%;&quot;&gt;&lt;b&gt;HTTP 200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.7844%;&quot;&gt;7,522&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.9588%;&quot;&gt;14,091&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1152%;&quot;&gt;12,418&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3742%;&quot;&gt;4,559&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 26.9767%;&quot;&gt;&lt;b&gt;5xx 오류&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.7844%;&quot;&gt;0건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.9588%;&quot;&gt;0건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1152%;&quot;&gt;11건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3742%;&quot;&gt;0건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 26.9767%;&quot;&gt;&lt;b&gt;stored_bookmark_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.7844%;&quot;&gt;17&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.9588%;&quot;&gt;52&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1152%;&quot;&gt;84&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3742%;&quot;&gt;582&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 26.9767%;&quot;&gt;&lt;b&gt;actual_bookmark_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.7844%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.9588%;&quot;&gt;300&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1152%;&quot;&gt;494&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3742%;&quot;&gt;998&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 26.9767%;&quot;&gt;&lt;b&gt;데이터 차이(diff)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.7844%;&quot;&gt;-83&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 15.9588%;&quot;&gt;-248&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.1152%;&quot;&gt;-410&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 16.3742%;&quot;&gt;-416&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣ 100 VU 환경에서도 데이터 정합성 문제 발생&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;100명의 사용자가 동시에 북마크 요청을 보냈을 때 모든 HTTP 요청은 정상적으로 처리되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 DB 정합성 확인 결과,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 북마크 데이터(post_bookmarks)는 100건 저장되었지만&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 posts.bookmark_count는 17건만 반영되어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;83건의 북마크 수 증가가 누락&lt;/b&gt;되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 낮은 동시성 환경에서도 북마크 수 증가 과정에서 이미 데이터&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;정합성 문제&lt;/b&gt;가 발생하고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2️⃣동시 사용자 증가와 함께 요청 실패율 증가&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;300 VU부터는 요청 실패가 발생하기 시작했습니다.&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;300 VU&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 실패율: 21.01%&lt;/li&gt;
&lt;li&gt;p95 응답 시간: 1.06초&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;500 VU&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 실패율: 33.97%&lt;/li&gt;
&lt;li&gt;p95 응답 시간: 704.85ms&lt;/li&gt;
&lt;li&gt;HTTP 5xx 오류: 11건&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;500 VU 테스트에서는 응답 속도 자체는 안정적이었지만, 요청 실패율이 증가하고 일부 서버 오류도 발생했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 현상으로 봐서는 동시 요청 처리 과정에서 충돌 또는 예외가 발생하고 있음을 짐작할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3️⃣&lt;span&gt;&amp;nbsp;&lt;/span&gt;1000VU 환경에서 서비스 안정성 저하&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1000명의 사용자가 동시에 좋아요 요청을 보낸 결과, 서비스 안정성이 크게 저하되었습니다.&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;span&gt; 54.99&lt;/span&gt;%&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;p95 응답 시간:&lt;b&gt;&lt;span&gt; 1.78&lt;/span&gt;초&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;HTTP 200 응답:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;3,686건&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;K6에서 설정한 성능 기준인 http_req_failed &amp;lt; 1%와 p95 &amp;lt; 1초를 모두 만족하지 못하고 있는 모습입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4️⃣ 동시성 증가에 따른 데이터 정합성 문제 심화&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 북마크 데이터(post_bookmarks)와 게시글의 북마크 개수(posts.bookmark_count)를 비교한 결과,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동시 사용자가 증가할수록 데이터 손실량이 증가했습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 76.6279%; height: 85px;&quot; border=&quot;1&quot; data-ke-style=&quot;style1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;실제 좋아요 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;저장된 like_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;누락 개수&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;17&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;83&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;300&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;52&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;248&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;500&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;500&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;84&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;410&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;1000&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;998&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;582&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;416&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이는 여러 사용자가 동시에 동일한 게시글의 like_count를 증가시키는 과정에서 발생한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Lost Update(갱신 손실)&amp;nbsp;&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 시나리오 3. 좋아요 혼합 테스트&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  테스트 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781246036874&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

const BASE_URL = __ENV.BASE_URL;
const POST_ID = __ENV.POST_ID;
const VUS = Number(__ENV.VUS || 100);
const DURATION = __ENV.DURATION || '30s';

const tokens = new SharedArray('tokens', function () {
  return open('./tokens.csv')
    .trim()
    .split('\n')
    .slice(1)
    .map((line) =&amp;gt; {
      const [userId, token] = line.split(',');
      return { userId, token };
    });
});

export const options = {
  scenarios: {
    like_mixed: {
      executor: 'constant-vus',
      vus: VUS,
      duration: DURATION,
    },
  },
  thresholds: {
    http_req_failed: ['rate&amp;lt;0.05'],
    http_req_duration: ['p(95)&amp;lt;1500', 'p(99)&amp;lt;4000'],
  },
};

export default function () {
  const user = tokens[(__VU - 1) % tokens.length];
  const shouldCreate = (__VU + __ITER) % 2 === 0;

  const url = `${BASE_URL}/api/v1/posts/${POST_ID}/likes`;
  const params = {
    headers: {
      Authorization: `Bearer ${user.token}`,
    },
    tags: {
      api: shouldCreate ? 'like_create' : 'like_delete',
    },
  };

  const res = shouldCreate
    ? http.post(url, null, params)
    : http.del(url, null, params);

  check(res, {
    'status is 200': (r) =&amp;gt; r.status === 200,
    'not 5xx': (r) =&amp;gt; r.status &amp;lt; 500,
  });

  sleep(0.1);
}&lt;/code&gt;&lt;/pre&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;1️⃣ 100 VU&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781246062061&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=100 `
  -e DURATION=&quot;30s&quot; `
  .\like-mixed.js *&amp;gt; .\results\before\like-mixed-100vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781246085374&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1500' p(95)=570.57ms
    ??'p(99)&amp;lt;4000' p(99)=771.16ms

    http_req_failed
    ??'rate&amp;lt;0.05' rate=0.00%


  ??TOTAL RESULTS 

    checks_total.......: 14768   485.308785/s
    checks_succeeded...: 100.00% 14768 out of 14768
    checks_failed......: 0.00%   0 out of 14768

    ??status is 200
    ??not 5xx

    HTTP
    http_req_duration..............: avg=302.64ms min=52.04ms  med=290.35ms max=1.2s  p(90)=491.59ms p(95)=570.57ms
      { expected_response:true }...: avg=302.64ms min=52.04ms  med=290.35ms max=1.2s  p(90)=491.59ms p(95)=570.57ms
    http_req_failed................: 0.00%  0 out of 7384
    http_reqs......................: 7384   242.654392/s

    EXECUTION
    iteration_duration.............: avg=409.19ms min=152.65ms med=392.34ms max=1.65s p(90)=603.6ms  p(95)=693.47ms
    iterations.....................: 7384   242.654392/s
    vus............................: 100    min=100       max=100
    vus_max........................: 100    min=100       max=100

    NETWORK
    data_received..................: 4.5 MB 146 kB/s
    data_sent......................: 2.4 MB 78 kB/s




running (0m30.4s), 000/100 VUs, 7384 complete and 0 interrupted iterations
like_mixed ??[ 100% ] 100 VUs  30s&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;http_reqs: 7,384건&lt;/li&gt;
&lt;li&gt;http_req_failed: 0.00% (0건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 302.64ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 570.57ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 771.16ms&lt;/li&gt;
&lt;li&gt;checks: 100.00% (14,768 / 14,768)&lt;/li&gt;
&lt;li&gt;status 200: 7,384건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1095&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IOXOc/dJMcahLwmQA/KWrTkJgd4F9yHvvOkCDK1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IOXOc/dJMcahLwmQA/KWrTkJgd4F9yHvvOkCDK1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IOXOc/dJMcahLwmQA/KWrTkJgd4F9yHvvOkCDK1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIOXOc%2FdJMcahLwmQA%2FKWrTkJgd4F9yHvvOkCDK1K%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;1095&quot; height=&quot;67&quot; data-origin-width=&quot;1095&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 562&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 448&lt;/li&gt;
&lt;li&gt;diff: 114&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 1000 VU 테스트&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781246168670&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=1000 `
  -e DURATION=&quot;30s&quot; `
  .\like-mixed.js *&amp;gt; .\results\before\like-mixed-1000vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781246176539&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1500' p(95)=1.05s
    ??'p(99)&amp;lt;4000' p(99)=1.77s

    http_req_failed
    ??'rate&amp;lt;0.05' rate=57.70%


  ??TOTAL RESULTS 

    checks_total.......: 19030  317.151667/s
    checks_succeeded...: 71.14% 13539 out of 19030
    checks_failed......: 28.85% 5491 out of 19030

    ??status is 200
      ?? 42% ????4024 / ??5491
    ??not 5xx

    HTTP
    http_req_duration..............: avg=275.12ms min=0s      med=68.77ms  max=7.92s  p(90)=860.85ms p(95)=1.05s
      { expected_response:true }...: avg=403ms    min=40.07ms med=251.53ms max=7.92s  p(90)=929.12ms p(95)=1.05s
    http_req_failed................: 57.70% 5491 out of 9515
    http_reqs......................: 9515   158.575833/s

    EXECUTION
    iteration_duration.............: avg=3.3s     min=103.1ms med=2.85s    max=16.12s p(90)=6.37s    p(95)=7.71s
    iterations.....................: 9515   158.575833/s
    vus............................: 1      min=1            max=1000
    vus_max........................: 1000   min=1000         max=1000

    NETWORK
    data_received..................: 23 MB  386 kB/s
    data_sent......................: 14 MB  231 kB/s




running (1m00.0s), 0000/1000 VUs, 9515 complete and 1 interrupted iterations
like_mixed ??[ 100% ] 1000 VUs  30s
time=&quot;2026-06-11T15:27:42+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_fai
led' have been crossed&quot;&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;http_reqs: 9,515건&lt;/li&gt;
&lt;li&gt;http_req_failed: 57.70% (5,491건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 275.12ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 1.05s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 1.77s&lt;/li&gt;
&lt;li&gt;checks: 71.14% (13,539 / 19,030)&lt;/li&gt;
&lt;li&gt;status 200: 4,024건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cS2fw7/dJMcaalicD9/Klq1XdNWodmuEGgnKlYKh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cS2fw7/dJMcaalicD9/Klq1XdNWodmuEGgnKlYKh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cS2fw7/dJMcaalicD9/Klq1XdNWodmuEGgnKlYKh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcS2fw7%2FdJMcaalicD9%2FKlq1XdNWodmuEGgnKlYKh1%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;1102&quot; height=&quot;73&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 530&lt;/li&gt;
&lt;li&gt;actual_like_count(기대값): 498&lt;/li&gt;
&lt;li&gt;diff: 32&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  K6 부하테스트 (개선 전) 결과 비교 분석&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 61.0465%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 21.9767%;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.8838%;&quot;&gt;&lt;b&gt;100 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.7674%;&quot;&gt;&lt;b&gt;1000 VU&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 21.9767%;&quot;&gt;&lt;b&gt;총 요청 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.8838%;&quot;&gt;7,384&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.7674%;&quot;&gt;9,515&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 21.9767%;&quot;&gt;&lt;b&gt;요청 실패율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.8838%;&quot;&gt;0.00%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.7674%;&quot;&gt;57.70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 21.9767%;&quot;&gt;&lt;b&gt;p95 응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.8838%;&quot;&gt;570.57ms&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.7674%;&quot;&gt;1.05s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 21.9767%;&quot;&gt;&lt;b&gt;HTTP 200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.8838%;&quot;&gt;7,384&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.7674%;&quot;&gt;4,024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 21.9767%;&quot;&gt;&lt;b&gt;5xx 오류&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.8838%;&quot;&gt;0건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.7674%;&quot;&gt;0건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 21.9767%;&quot;&gt;&lt;b&gt;stored_like_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.8838%;&quot;&gt;562&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.7674%;&quot;&gt;530&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 21.9767%;&quot;&gt;&lt;b&gt;actual_like_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.8838%;&quot;&gt;448&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.7674%;&quot;&gt;498&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 21.9767%;&quot;&gt;&lt;b&gt;데이터 차이(diff)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.8838%;&quot;&gt;+114&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 19.7674%;&quot;&gt;+32&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣ 100 VU 환경에서 정상적인 요청 처리 확인&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;100명의 사용자가 동시에 좋아요 등록/취소 요청을 수행한 결과 모든 HTTP 요청이 정상적으로 처리되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;K6에서 설정한 성능 기준인 http_req_failed &amp;lt; 5%와 p95 &amp;lt; 1.5초를 만족하여&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;낮은 동시성 환경에서는 안정적인 응답 성능을 보여줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2️⃣1000VU 환경에서 요청 실패율 증가&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1000명의 사용자가 동시에 좋아요/취소 요청을 수행한 결과, 응답 속도는 기준을 만족하였지만 요청 실패율이 크게 증가했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;응답 시간은 K6 기준(p95 &amp;lt; 1.5)을 만족했지만, http_req_failed &amp;lt; 5% 기준을 크게 조과하여 테스트 실패로 판단됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, 5xx 서버 오류는 발생하지 않았고, 높은 동시 요청 환경에서 동시성 충돌 또는 다른 예외가 발생했을 가능성이 다분합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3️⃣&lt;span&gt; 좋아요 수 정합성 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존 좋아요 생성 테스트와 달리 혼합 요청 환경에서는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;stored_like_count와 actual_like_count의 차이가 상대적으로 적게 나타났습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 76.1628%; height: 56px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 14.5349%;&quot;&gt;&lt;b&gt;VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 20.4865%;&quot;&gt;&lt;b&gt;저장된 like_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 18.1226%;&quot;&gt;&lt;b&gt;실제 좋아요 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; text-align: center; width: 22.7863%;&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;height: 17px; text-align: center; width: 14.5349%;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 20.4865%;&quot;&gt;562&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 18.1226%;&quot;&gt;448&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 22.7863%;&quot;&gt;+114&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 14.5349%;&quot;&gt;&lt;b&gt;1000&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 20.4865%;&quot;&gt;530&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 18.1226%;&quot;&gt;498&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center; width: 22.7863%;&quot;&gt;+32&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 결과는&amp;nbsp;&lt;b&gt;좋아요 생성만 발생하는 테스트와는 다르게 생성과 취소 요청이 함께 수행된 시나리오&lt;/b&gt;이므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 stored_like_count와 actual_like_count가 동일해야 한다고 판단하기 어렵습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 시나리오 4. 북마크 혼합 테스트&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  테스트 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781246764305&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

const BASE_URL = __ENV.BASE_URL;
const POST_ID = __ENV.POST_ID;
const VUS = Number(__ENV.VUS || 100);
const DURATION = __ENV.DURATION || '30s';

const tokens = new SharedArray('tokens', function () {
  return open('./tokens.csv')
    .trim()
    .split('\n')
    .slice(1)
    .map((line) =&amp;gt; {
      const [userId, token] = line.split(',');
      return { userId, token };
    });
});

export const options = {
  scenarios: {
    bookmark_mixed: {
      executor: 'constant-vus',
      vus: VUS,
      duration: DURATION,
    },
  },
  thresholds: {
    http_req_failed: ['rate&amp;lt;0.05'],
    http_req_duration: ['p(95)&amp;lt;1500', 'p(99)&amp;lt;4000'],
  },
};

export default function () {
  const user = tokens[(__VU - 1) % tokens.length];
  const shouldCreate = (__VU + __ITER) % 2 === 0;

  const url = `${BASE_URL}/api/v1/posts/${POST_ID}/bookmarks`;
  const params = {
    headers: {
      Authorization: `Bearer ${user.token}`,
    },
    tags: {
      api: shouldCreate ? 'bookmark_create' : 'bookmark_delete',
    },
  };

  const res = shouldCreate
    ? http.post(url, null, params)
    : http.del(url, null, params);

  check(res, {
    'status is 200': (r) =&amp;gt; r.status === 200,
    'not 5xx': (r) =&amp;gt; r.status &amp;lt; 500,
  });

  sleep(0.1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 100 VU&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781246776878&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=100 `
  -e DURATION=&quot;30s&quot; `
  .\bookmark-mixed.js *&amp;gt; .\results\before\bookmark-mixed-100vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781246790279&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1500' p(95)=633.56ms
    ??'p(99)&amp;lt;4000' p(99)=854.01ms

    http_req_failed
    ??'rate&amp;lt;0.05' rate=0.00%


  ??TOTAL RESULTS 

    checks_total.......: 13988   459.087079/s
    checks_succeeded...: 100.00% 13988 out of 13988
    checks_failed......: 0.00%   0 out of 13988

    ??status is 200
    ??not 5xx

    HTTP
    http_req_duration..............: avg=321.59ms min=21.27ms  med=300.45ms max=1.6s  p(90)=519.27ms p(95)=633.56ms
      { expected_response:true }...: avg=321.59ms min=21.27ms  med=300.45ms max=1.6s  p(90)=519.27ms p(95)=633.56ms
    http_req_failed................: 0.00%  0 out of 6994
    http_reqs......................: 6994   229.543539/s

    EXECUTION
    iteration_duration.............: avg=432.3ms  min=122.01ms med=406.57ms max=1.73s p(90)=632.91ms p(95)=784.41ms
    iterations.....................: 6994   229.543539/s
    vus............................: 100    min=100       max=100
    vus_max........................: 100    min=100       max=100

    NETWORK
    data_received..................: 4.2 MB 139 kB/s
    data_sent......................: 2.3 MB 75 kB/s




running (0m30.5s), 000/100 VUs, 6994 complete and 0 interrupted iterations
bookmark_mixed ??[ 100% ] 100 VUs  30s&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;http_reqs: 6,994건&lt;/li&gt;
&lt;li&gt;http_req_failed: 0.00% (0건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 321.59ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 633.56ms&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 854.01ms&lt;/li&gt;
&lt;li&gt;checks: 100.00% (13,988 / 13,988)&lt;/li&gt;
&lt;li&gt;status 200: 6,994건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vl8qO/dJMcaicEcOH/JsTM0k2615ajKcbQ2T8tI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vl8qO/dJMcaicEcOH/JsTM0k2615ajKcbQ2T8tI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vl8qO/dJMcaicEcOH/JsTM0k2615ajKcbQ2T8tI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVl8qO%2FdJMcaicEcOH%2FJsTM0k2615ajKcbQ2T8tI1%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;1197&quot; height=&quot;72&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 578&lt;/li&gt;
&lt;li&gt;actual_like_count(기댓값): 444&lt;/li&gt;
&lt;li&gt;diff: 134&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 1000 VU 테스트&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781246834451&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k6 run `
  -e BASE_URL=&quot;https://34.64.197.181.nip.io&quot; `
  -e POST_ID=&quot;100001&quot; `
  -e VUS=1000 `
  -e DURATION=&quot;30s&quot; `
  .\bookmark-mixed.js *&amp;gt; .\results\before\bookmark-mixed-1000vu-before.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781246841776&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ??THRESHOLDS 

    http_req_duration
    ??'p(95)&amp;lt;1500' p(95)=2.86s
    ??'p(99)&amp;lt;4000' p(99)=12.61s

    http_req_failed
    ??'rate&amp;lt;0.05' rate=54.57%


  ??TOTAL RESULTS 

    checks_total.......: 16028  283.597949/s
    checks_succeeded...: 72.71% 11654 out of 16028
    checks_failed......: 27.28% 4374 out of 16028

    ??status is 200
      ?? 45% ????3640 / ??4374
    ??not 5xx

    HTTP
    http_req_duration..............: avg=800.99ms min=0s      med=55.29ms  max=36.16s p(90)=1.24s p(95)=2.86s 
      { expected_response:true }...: avg=1.47s    min=11.44ms med=523.97ms max=36.16s p(90)=2.85s p(95)=9.84s 
    http_req_failed................: 54.57% 4374 out of 8014
    http_reqs......................: 8014   141.798974/s

    EXECUTION
    iteration_duration.............: avg=3.93s    min=100.1ms med=2.13s    max=42.75s p(90)=12.9s p(95)=17.49s
    iterations.....................: 8014   141.798974/s
    vus............................: 2      min=2            max=1000
    vus_max........................: 1000   min=1000         max=1000

    NETWORK
    data_received..................: 20 MB  349 kB/s
    data_sent......................: 12 MB  206 kB/s




running (0m56.5s), 0000/1000 VUs, 8014 complete and 0 interrupted iterations
bookmark_mixed ??[ 100% ] 1000 VUs  30s
time=&quot;2026-06-11T15:41:36+09:00&quot; level=error msg=&quot;thresholds on metrics 'http_req_dur
ation, http_req_failed' have been crossed&quot;&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;http_reqs: 8,014건&lt;/li&gt;
&lt;li&gt;http_req_failed: 54.57% (4,374건)&lt;/li&gt;
&lt;li&gt;http_req_duration avg: 800.99ms&lt;/li&gt;
&lt;li&gt;http_req_duration p95: 2.86s&lt;/li&gt;
&lt;li&gt;http_req_duration p99: 12.61s&lt;/li&gt;
&lt;li&gt;checks: 72.71% (11,654 / 16,028)&lt;/li&gt;
&lt;li&gt;status 200: 3,640건&lt;/li&gt;
&lt;li&gt;status 5xx: 0건&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DB 정합성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2CVHA/dJMcajbplP6/dOS030a6OshiJkxWuUucCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2CVHA/dJMcajbplP6/dOS030a6OshiJkxWuUucCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2CVHA/dJMcajbplP6/dOS030a6OshiJkxWuUucCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2CVHA%2FdJMcajbplP6%2FdOS030a6OshiJkxWuUucCK%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;1202&quot; height=&quot;72&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stored_like_count(실제 저장된 수): 550&lt;/li&gt;
&lt;li&gt;actual_like_count(기대값): 492&lt;/li&gt;
&lt;li&gt;diff: 58&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  K6 부하테스트 (개선 전) 결과 비교 분석&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 69.7642%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;&lt;b&gt;100 VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 21.6423%;&quot;&gt;&lt;b&gt;1000 VU&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;총 요청 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;6,994&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 21.6423%;&quot;&gt;8,014&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;요청 실패율&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;0.00%&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 21.6423%;&quot;&gt;54.57%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;p95 응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;633.56ms&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 21.6423%;&quot;&gt;2.86s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;HTTP 200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;6,994&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 21.6423%;&quot;&gt;3,640&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;5xx 오류&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;0건&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 21.6423%;&quot;&gt;0건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;stored_bookmark_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;578&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 21.6423%;&quot;&gt;550&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;actual_bookmark_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;444&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 21.6423%;&quot;&gt;492&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;데이터 차이(diff)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;+134&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 21.6423%;&quot;&gt;+58&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣ 100 VU 환경에서 정상적인 요청 처리 확인&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;100명의 사용자가 동시에 북마크 등록/취소 요청을 수행한 결과 모든 HTTP 요청이 정상적으로 처리되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;K6에서 설정한 성능 기준인 http_req_failed &amp;lt; 5%와 p95 &amp;lt; 1.5초를 만족하여&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;낮은 동시성 환경에서는 안정적인 응답 성능을 보여줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2️⃣1000VU 환경에서 요청 실패율 증가&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1000명의 사용자가 동시에 북마크 등록/취소 요청을 수행한 결과,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;요청 실패율과 응답 시간이 크게 증가했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 실패율: &lt;b&gt;54.57%&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;p95 응답 시간: &lt;b&gt;2.86초&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;p99 응답 시간: &lt;b&gt;12.61초&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;HTTP 200 응답: &lt;b&gt;3,640건&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;K6 기준 p95 &amp;lt; 1.5, http_req_failed &amp;lt; 5%를 모두 만족하지 못했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 p99 응답 시간이 12초 이상까지 증가한 점은&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일부 요청이 매우 오랜 시간 대기하거나 처리 지연이 발생했을 가능성이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3️⃣&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;북마크 수 정합성 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;북마크 생성만 수행한 테스트와 달리 생성과 취소 요청이 함께 수행되는 혼합 시나리오에서는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최종 북마크 개수가 증가와 감소를 반복하기 때문에 stored_bookmark_count와 actual_bookmark_count가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반드시 동일한 방향으로 증가한다고 볼 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 값의 차이가 존재한다는 점은 bookmark_count 집계 과정에서 동시성 문제가 발생할 가능성을 보여줍니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 75.5814%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 13.1395%;&quot;&gt;&lt;b&gt;VU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 26.5116%;&quot;&gt;&lt;b&gt;저장된 bookmark_count&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 23.1395%;&quot;&gt;&lt;b&gt;실제 북마크 수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 12.7907%;&quot;&gt;&lt;b&gt;차이&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 13.1395%;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 26.5116%;&quot;&gt;578&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 23.1395%;&quot;&gt;444&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 12.7907%;&quot;&gt;+134&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 13.1395%;&quot;&gt;&lt;b&gt;1000&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 26.5116%;&quot;&gt;550&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 23.1395%;&quot;&gt;492&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 12.7907%;&quot;&gt;+58&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;middot; 마치며&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 개선 전 테스트 종합 결과&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 65.9286%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;&lt;b&gt;시나리오&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 48.3716%;&quot;&gt;&lt;b&gt;가장 큰 문제&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;&lt;b&gt;좋아요 등록&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 48.3716%;&quot;&gt;최대 376건의 count 손실 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;&lt;b&gt;북마크 등록&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 48.3716%;&quot;&gt;최대 416건의 count 손실 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;&lt;b&gt;좋아요 혼합&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 48.3716%;&quot;&gt;생성/삭제 경쟁으로 count 불일치 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 22.093%;&quot;&gt;&lt;b&gt;북마크 혼합&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 48.3716%;&quot;&gt;높은 동시성에서 응답 지연 및 count 불일치 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;2189&quot; data-start=&quot;2116&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 &lt;b&gt;HTTP 200 응답이 정상적으로 반환되더라도 데이터 정합성이 보장되는 것은 아니라는 점&lt;/b&gt;을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;2189&quot; data-start=&quot;2116&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2313&quot; data-start=&quot;2191&quot; data-ke-size=&quot;size16&quot;&gt;특히 exists 조회 &amp;rarr; count 변경 &amp;rarr; 관계 데이터 저장 구조에서는 동시 요청 상황에서&lt;/p&gt;
&lt;p data-end=&quot;2313&quot; data-start=&quot;2191&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;count 변경과 실제 row 변경이 원자적으로 보장되지 않았기 때문에 Lost Update 문제가 발생했습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;- 마무리&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 테스트를 통해 단순히 HTTP 요청이 성공하는 것과 데이터 정합성이 보장되는 것은 전혀 다른 문제임을 확인할 수 있었습니다.&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;특히 100 VU와 같이 비교적 낮은 동시성 환경에서도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;like_count, bookmark_count와 실제 관계 데이터 사이에 차이가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3814&quot; data-start=&quot;3757&quot; data-ke-size=&quot;size16&quot;&gt;그리고 동시 요청이 증가할수록 요청 실패율과 데이터 불일치가 더욱 증가하는 현상을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;3814&quot; data-start=&quot;3757&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3939&quot; data-start=&quot;3816&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 이러한 문제를 해결하기 위해 &lt;b&gt;기존 좋아요/북마크 로직을 어떻게 개선했는지&lt;/b&gt;, 그리고 왜 ON CONFLICT DO NOTHING과 Atomic Update 방식을 선택했는지 정리해 보겠습니다.&lt;/p&gt;</description>
      <category>Project/Moassam</category>
      <category>K6</category>
      <category>동시성</category>
      <category>부하 테스트</category>
      <category>성능 테스트</category>
      <category>정합성</category>
      <category>테스트</category>
      <author>myo-dev</author>
      <guid isPermaLink="true">https://myojun.tistory.com/58</guid>
      <comments>https://myojun.tistory.com/entry/Moassam-%EC%A2%8B%EC%95%84%EC%9A%94%EB%B6%81%EB%A7%88%ED%81%AC-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-%EB%B0%8F-K6-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8#entry58comment</comments>
      <pubDate>Fri, 12 Jun 2026 16:06:30 +0900</pubDate>
    </item>
  </channel>
</rss>