<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발하우스</title>
    <link>https://leejunkim.tistory.com/</link>
    <description>backend + cloud + ai에 관심이 많은 대학생</description>
    <language>ko</language>
    <pubDate>Wed, 5 Aug 2026 16:09:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>leejunkim</managingEditor>
    <image>
      <title>개발하우스</title>
      <url>https://tistory1.daumcdn.net/tistory/8007713/attach/aaec2e1d2f7544e2aaa922e51c6c4249</url>
      <link>https://leejunkim.tistory.com</link>
    </image>
    <item>
      <title>코딩 테스트 합격자 되기 - week 1</title>
      <link>https://leejunkim.tistory.com/78</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) 배열 정렬하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정수 배열의 길이는 2 이상 10^5 이하입니다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;제약조건
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정수 배열의 길이는 2 이상 10^5 이하&lt;/li&gt;
&lt;li&gt;정수 배열의 각 데이터 값은 -100,000 이상 100,000 이하&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static int[] solution(int[] arr) {
    int[] copy = arr;
    Arrays.sort(copy);
    return arr;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제 분석
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터의 최대 갯수는 10^5.&lt;/li&gt;
&lt;li&gt;컴퓨터가 1초당 하는 연산은 1억 (10^8).&lt;/li&gt;
&lt;li&gt;만약에 제한 시간이 3초면, 총 3억번의 연산 이하로 해야함..!
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;O(N^2) = O(10^5)^2 = O(10^10) -&amp;gt; 100억
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제한시간이 3초였으면 3억번의연산 이하로 해야하는데, O(N^2) 알고리즘을 사용하면 10^10 (100억)으로 최대 연산이 갈 수 있음! 그래서 O(N^2)는 제외.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;O(N log N) = O(10^5 log 10^5) -&amp;gt; O(10^5 * 17)-&amp;gt; 170만번&amp;nbsp;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;170만번은 3억번의 연산 이하여서 O(N log N)의 알고리즘(혹은 그 이하)를 사용할 수 있음!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;그래서 실제 문제를 풀 때는 문제지에 적힌 실제 제한 시간(Time Limit)을 확인하고, 그 시간 안에 N을 넣었을 때 연산 횟수가 1억~수억 번을 넘지 않는지 체크하는 습관을 들이는 것이 중요함&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) 배열 제어하기&lt;/h2&gt;
&lt;pre id=&quot;code_1769153827158&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static int[] solution(int[] arr) {
    return Arrays.stream(arr)
            .distinct() // 중복 제거
            .boxed()// 내림차순 하기 위해 reference type으로 변환
            .sorted(Collections.reverseOrder()) // 내림차순
            .mapToInt(Integer::intValue) // reference -&amp;gt; primitie
            .toArray();
}&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;자바에는 이런식으로 코테에 유용한 표준 API들이 많고 굳이 직접 적지 않아도 됨&lt;/li&gt;
&lt;li&gt;시간 복잡도
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복 제거 (distinct) -&amp;gt; O(N)&lt;/li&gt;
&lt;li&gt;정렬(sort) -&amp;gt; O(N log N)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;최종 시간 복잡도는 O(N log N)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 두개 뽑아서 더하기&lt;/h2&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static int[] solution(int[] arr) {
    Set&amp;lt;Integer&amp;gt; set = new HashSet&amp;lt;&amp;gt;();
    for (int i = 0; i &amp;lt; arr.length; i++) {
        for (int j = i + 1; j &amp;lt; arr.length; j++) {
            set.add(arr[i] + arr[j]);
        }
    }
    return set.stream()
            .sorted()
            .mapToInt(Integer::intValue)
            .toArray();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간 복잡도
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이중for문
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;총 실행 휫수는 (N(N-1))/2 = (N^2)/2 - N/2 -&amp;gt; 최종적으로 O(N^2)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;정렬
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정렬할 데이터는 N^2&lt;/li&gt;
&lt;li&gt;정렬 알고리즘 (.sorted)의 시간복잡도는 O(N log N)&lt;/li&gt;
&lt;li&gt;데이터는 N^2이므로 최종적으로 O(N^2 log N^2)가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그래서 이중for문 + 정렬은 O(N^2) + O(N^2 log N^2). 하지만 가장 큰 텀안 중요해서 최종적으로 O(N^2 log N^2)만 남음.&lt;/li&gt;
&lt;li&gt;최종 시간 복잡도는&lt;b&gt; O(N^2 log N^2)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>코딩테스트</category>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/78</guid>
      <comments>https://leejunkim.tistory.com/78#entry78comment</comments>
      <pubDate>Fri, 23 Jan 2026 17:22:18 +0900</pubDate>
    </item>
    <item>
      <title>[코딩 테스트 합격자 되기] 코딩테스트 사전 준비 (+ 알고리즘 효율 분석)</title>
      <link>https://leejunkim.tistory.com/77</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;코딩테스트&amp;nbsp;사전&amp;nbsp;준비&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그래머스 활용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 사람의 풀이 보기&lt;/li&gt;
&lt;li&gt;테스트 케이스 직접 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;충분한 시간을 들여 문제를 분석한 후, 코드 구현하기 전에 예외 상황을 충분히 확인할 수 있도록 나만의 테스트 케이스 만드는 연습을 해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공부 습관
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기록하기!
&lt;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;전 기록을 보면서 복기&lt;/li&gt;
&lt;li&gt;이 과정에서 무엇을 모르는지 알 수 있게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;시험 보듯 공부하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평소에 시간 배분하면서 코딩 테스트 공부하기&lt;/li&gt;
&lt;li&gt;시간 배분 연습 안함/함 차이가 나중에 결과가 많이 다를 것임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2-3개월은 걸릴거임
&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;나만의 언어로 요약&amp;nbsp;
&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;/li&gt;
&lt;li&gt;코딩테스트 효율적으로 공부하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;언어 선택 - 자바 (자바 공부 동행하기)&lt;/li&gt;
&lt;li&gt;문제 분석 연습하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제를 쪼개서 분석&lt;/li&gt;
&lt;li&gt;제약사항을 파악하고 테스트 케이스 추가&lt;/li&gt;
&lt;li&gt;입력값 분석
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 알고리즘 시간 복잡도는 입력값이 결정하는 경우가 많음&lt;/li&gt;
&lt;li&gt;입력값의 크기를 확인하면 문제를 제한시간 내에 풀 수 있는 알고리즘과 그렇지 않은 알고리즘을 미리 걸러낼 수 있음!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그리디하게 접근할 때는 근거를 명확하게&lt;/li&gt;
&lt;li&gt;데이터 흐름이나 구성을 파악&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pseudocode (의사 코드)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세부 구현 X, 동작 O&lt;/li&gt;
&lt;li&gt;문제 해결 순서 적기&lt;/li&gt;
&lt;li&gt;테스트하기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;알고리즘의 효율 분석&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;코딩테스트 문제에는 제한 시간이 있으므로 문제를 분석한 후에 빅오 표기법을 활용해서 해당 알고리즘을 적용할 때 제한 시간 내에 출력값이 나올 수 있을 지 확인할 수 있음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴퓨터가 초당 연산할 수 있는 최대 휫수는 1억 번 (10^8)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;연산 횟수는 1000~3000만 정도로 고려해서 시간 복잡도를 생각하면 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예) 제한 시간이 1초인 문제는 연산 횟수가 3000만이 넘는 알고리즘은 사용하면 안됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;제한 시간이 1초인 문제에 각 시간 복잡도별로 허용할 수 있는 N의 가용범위:&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;시간 복잡도&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;N의 가용 범위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;O(N!)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;O(2^N)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;20~25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;O(N^3)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;200 - 300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;O(N^2)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;3,000 - 5,000&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;O(N log N)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;100만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;O(N)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1,000 - 2,000만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;O(logn)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;10억 이상&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들면, 처음부터 끝자기 배열을 순회해서 숫자를 찾는 알고리즘은 O(N)이다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;O(N)이 허용하는 연산 횟수는 2,000만으로 데이터의 개수가 2,000만 이하면 이 알고리즘은 사용해도 괜찮다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이렇게 시간 복잡도를 활용하여 불필요한 알고리즘을 제외하면 된다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Books</category>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/77</guid>
      <comments>https://leejunkim.tistory.com/77#entry77comment</comments>
      <pubDate>Fri, 9 Jan 2026 19:38:31 +0900</pubDate>
    </item>
    <item>
      <title>[모두의 플리] JMeter로 OS 성능 테스</title>
      <link>https://leejunkim.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS과 원본 QueryDSL의 성능 차이를 재보고 싶어서 JMeter를 사용해봤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 DB에는 약 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;1. Thread Group 추가&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1515&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/odFmj/dJMcaa411XD/uS6zASRZQuhcKgcbNoEFI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/odFmj/dJMcaa411XD/uS6zASRZQuhcKgcbNoEFI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/odFmj/dJMcaa411XD/uS6zASRZQuhcKgcbNoEFI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FodFmj%2FdJMcaa411XD%2FuS6zASRZQuhcKgcbNoEFI1%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;1515&quot; height=&quot;496&quot; data-origin-width=&quot;1515&quot; data-origin-height=&quot;496&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;Number of Threads (user) - 유저 갯수&lt;/li&gt;
&lt;li&gt;Ramp up period (seconds) - 몇초 후에 모든 유저가 시작하는지&lt;/li&gt;
&lt;li&gt;Loop count (user) - 각 유저가 몇번 하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 테스트할때 이렇게 설정했다:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZwPWs/dJMcacu08CS/VKGBMlCSQgOixy0ANXQb40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZwPWs/dJMcacu08CS/VKGBMlCSQgOixy0ANXQb40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZwPWs/dJMcacu08CS/VKGBMlCSQgOixy0ANXQb40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZwPWs%2FdJMcacu08CS%2FVKGBMlCSQgOixy0ANXQb40%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;1206&quot; height=&quot;304&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 유저 수 - 100&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ramp up - 20&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- loop count - 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;2. Thread Group에서 HTTP Request 추가&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIcIYO/dJMcahC48BO/nfccksiUJvaRRw71BNI1L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIcIYO/dJMcahC48BO/nfccksiUJvaRRw71BNI1L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIcIYO/dJMcahC48BO/nfccksiUJvaRRw71BNI1L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIcIYO%2FdJMcahC48BO%2FnfccksiUJvaRRw71BNI1L0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;151&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mtLsy/dJMcaa412dz/EqDSCNu951N78ANTUGU9bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mtLsy/dJMcaa412dz/EqDSCNu951N78ANTUGU9bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mtLsy/dJMcaa412dz/EqDSCNu951N78ANTUGU9bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmtLsy%2FdJMcaa412dz%2FEqDSCNu951N78ANTUGU9bK%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;1392&quot; height=&quot;459&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Header Manager 추가&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1391&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o6eEt/dJMcaf6lpHO/cba7i3UKpxM7zexGlKAaT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o6eEt/dJMcaf6lpHO/cba7i3UKpxM7zexGlKAaT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o6eEt/dJMcaf6lpHO/cba7i3UKpxM7zexGlKAaT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo6eEt%2FdJMcaf6lpHO%2Fcba7i3UKpxM7zexGlKAaT1%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;1391&quot; height=&quot;182&quot; data-origin-width=&quot;1391&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- bearer 토큰을 넣어줬다.&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;QueryDSL&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btvy2g/dJMb99576vG/CaJDhEdUc57bkV7Z6TdtV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btvy2g/dJMb99576vG/CaJDhEdUc57bkV7Z6TdtV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btvy2g/dJMb99576vG/CaJDhEdUc57bkV7Z6TdtV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtvy2g%2FdJMb99576vG%2FCaJDhEdUc57bkV7Z6TdtV0%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;1207&quot; height=&quot;244&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1cJF2/dJMcaihHuQR/ew6judVN82uU6mbWz6JRX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1cJF2/dJMcaihHuQR/ew6judVN82uU6mbWz6JRX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1cJF2/dJMcaihHuQR/ew6judVN82uU6mbWz6JRX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1cJF2%2FdJMcaihHuQR%2Few6judVN82uU6mbWz6JRX0%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;1217&quot; height=&quot;254&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로... OpenSearch가 QueryDSL보다 약 &lt;b&gt;~15 정도 빨랐다&lt;/b&gt;!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블로 정리하자면:&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.9768%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.0698%;&quot;&gt;QueryDSL&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 38.9534%;&quot;&gt;OS (OpenSearch)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.9768%;&quot;&gt;Avg Latency&lt;/td&gt;
&lt;td style=&quot;width: 14.0698%;&quot;&gt;10631 ms (10.6 s)&lt;/td&gt;
&lt;td style=&quot;width: 38.9534%;&quot;&gt;690 ms (0.69 s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.9768%;&quot;&gt;Max Latency&lt;/td&gt;
&lt;td style=&quot;width: 14.0698%;&quot;&gt;30968 ms (30.9 s)&lt;/td&gt;
&lt;td style=&quot;width: 38.9534%;&quot;&gt;2640 ms (2.6 s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.9768%;&quot;&gt;Throughput&lt;/td&gt;
&lt;td style=&quot;width: 14.0698%;&quot;&gt;6.2 req/s&lt;/td&gt;
&lt;td style=&quot;width: 38.9534%;&quot;&gt;22.7 req/s&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;</description>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/76</guid>
      <comments>https://leejunkim.tistory.com/76#entry76comment</comments>
      <pubDate>Mon, 5 Jan 2026 15:17:06 +0900</pubDate>
    </item>
    <item>
      <title>[모두의 플리] watcherCount를 Redis로 리팩토링</title>
      <link>https://leejunkim.tistory.com/75</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;상황&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트에서 실시간 콘텐츠 시청 세션 기능을 구현했다. 사용자가 영상 페이지에 들어오면 시청자로 집계되고, 나가면 집계에서 빠져야 한다. 이 숫자는 웹소켓을 통해 모든 사용자에게 실시간으로 브로드캐스팅되어야 했다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;문제점&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;프론트에서 콘텐츠를 불러올 때, 요구하는 dto에 watcherCount가 들어있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;그래서 기존 코드는 콘텐츠 엔티티에 watcherCount 필드를 사용했다. 그래서 WatchingSessionService에서 watchingSession이 업데이트 될때마다 이 콘텐츠의 필드도 업데이트 하는 방식으로 했다:&lt;/p&gt;
&lt;pre id=&quot;code_1766463494160&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;contentRepository.incrementWatcherCount(content.getId());

contentRepository.decrementWatcherCount(content.getId());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766463932691&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ContentRepository extends JpaRepository&amp;lt;Content, UUID&amp;gt;, ContentRepositoryCustom {
  @Modifying
  @Query(&quot;UPDATE Content c SET c.watcherCount = c.watcherCount + 1 WHERE c.id = :contentId&quot;)
  void incrementWatcherCount(@Param(&quot;contentId&quot;) UUID contentId);

  @Modifying
  @Query(&quot;UPDATE Content c SET c.watcherCount = c.watcherCount - 1 &quot; +
      &quot;WHERE c.id = :contentId AND c.watcherCount &amp;gt; 0&quot;)
  void decrementWatcherCount(@Param(&quot;contentId&quot;) UUID contentId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;여기서 생각해보면, 사용자 한명한명 들어올 때마다 매번 디스크 기반의 DB write 작업을 수행해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;5&quot;&gt;현재 @Transactional을 메서드에 붙히긴 했지만, 트랜잭션의 원자성(성공 아니면 롤백)을 보장할 뿐, 여러 스레드가 동시에 같은 데이터에 접근하는 동시성 문제까지는 해결해주지 못한다.&lt;/li&gt;
&lt;li data-path-to-node=&quot;5&quot;&gt;만약에 시청자 A, B가 0.0001초 차이로 동시에 입장했으면 lost update가 생길 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;Transaction A:&lt;/b&gt; DB에서 현재 인원 조회 -&amp;gt; &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;6,0,0&quot;&gt;10명&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;Transaction B:&lt;/b&gt; DB에서 현재 인원 조회 -&amp;gt; &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;6,1,0&quot;&gt;10명&lt;/b&gt; (A가 아직 저장을 안 했으므로)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;Transaction A:&lt;/b&gt; 10 + 1 = 11명으로 저장 (UPDATE)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,3,0&quot;&gt;Transaction B:&lt;/b&gt; 10 + 1 = 11명으로 저장 (UPDATE)&lt;/li&gt;
&lt;li&gt;결과적으로 두 명이 들어왔으니 12명이 되어야 하는데, DB에는 11명만 남는다. A의 업데이트 내역이 B에 의해 덮어씌워져 사라진 것이다 (Lost Update)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-path-to-node=&quot;5&quot;&gt;이를 막으려면 비관적 락을 걸어야하는데, A가 끝날때까지 B는 대기하게 하는 것이다. 그러면 데이터는 정확해지지만 만약에 100명이 들어오게 되면 100명이 줄을 서게 되어서 성능이 급격하게 저하된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;해결 - Redis&lt;/h2&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;Redis는 구조적으로 이 문제를 완벽하게 회피한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;Single Thread:&lt;/b&gt; Redis는 한 번에 하나의 명령어만 처리한다. 아무리 많은 요청이 동시에 몰려도 내부적으로는 줄을 세워 하나씩 순서대로 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;Atomic Operation:&lt;/b&gt; Redis의 INCR(증가) 명령어는 &quot;값을 읽고 -&amp;gt; 더하고 -&amp;gt; 저장하는&quot; 과정이 분리되지 않고 한 방에 일어난다.&lt;/li&gt;
&lt;li&gt;이제 100명이 동시에 INCR을 날려도 1부터 100까지 순서대로 빠르게 처리해서 정확히 +100을 만들어낸다.&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;&amp;nbsp;이제 Content 테이블의 watcher_count 컬럼은 제거되고 실시간 용도로 쓰지 않는다.&lt;/li&gt;
&lt;li&gt;Redis는 실시간 조회수를 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1766463805255&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static final String COUNT_KEY_PREFIX = &quot;watching:count:&quot;;

public Long increaseWatcherCount(UUID contentId) {
    String key = COUNT_KEY_PREFIX + contentId;
    // DB 조회 없이 메모리에서 즉시 +1
    return redisTemplate.opsForValue().increment(key);
}

public Long decreaseWatcherCount(UUID contentId) {
    String key = COUNT_KEY_PREFIX + contentId;
    // 메모리에서 즉시 -1
    return redisTemplate.opsForValue().decrement(key);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB&amp;nbsp;부하&amp;nbsp;감소:&amp;nbsp;단순&amp;nbsp;조회수&amp;nbsp;갱신을&amp;nbsp;위한&amp;nbsp;UPDATE&amp;nbsp;쿼리가&amp;nbsp;완전히&amp;nbsp;사라졌다.&amp;nbsp;DB는&amp;nbsp;이제&amp;nbsp;중요한&amp;nbsp;비즈니스&amp;nbsp;데이터(결제,&amp;nbsp;유저&amp;nbsp;정보&amp;nbsp;등)&amp;nbsp;처리에만&amp;nbsp;집중할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;되었다.&lt;/li&gt;
&lt;li&gt;그 위워별도의 락(Lock) 구현 없이도 Redis의 원자적 연산 덕분에 100명이 동시에 들어와도 정확하게 +100이 카운팅된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Codeit/프로젝트</category>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/75</guid>
      <comments>https://leejunkim.tistory.com/75#entry75comment</comments>
      <pubDate>Tue, 23 Dec 2025 13:48:01 +0900</pubDate>
    </item>
    <item>
      <title>[코드잇 스프린트] Spring 백엔드 4기 수료 후기 및 회고</title>
      <link>https://leejunkim.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Sy8O/dJMcadN4eZx/Nk1mznrb1OUP6t7fV6gYAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Sy8O/dJMcadN4eZx/Nk1mznrb1OUP6t7fV6gYAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Sy8O/dJMcadN4eZx/Nk1mznrb1OUP6t7fV6gYAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Sy8O%2FdJMcadN4eZx%2FNk1mznrb1OUP6t7fV6gYAk%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;438&quot; height=&quot;246&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느새 7개월이 지나갔고...긴 시간처럼 느꼈던 부트캠프가 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5월부터 12월까지 수강했었고, 그 동안 정말 많은 일이 일어났다. 그래서 오늘은 반년동안 코드잇 스프린트 Spring 백엔트 4기 후기를 작성해보자 한다!&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;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;  왜 코드잇 부트캠프를 선택했을까?&amp;nbsp;&amp;nbsp;&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;그래서 4학년으로 올라가기 전에 휴학을 하기로 선택했고, 이 기간 동안 스스로가 무엇을 좋아하는지 탐색해보려고 했다.&lt;/p&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;혼자 유튜브 보면서 토이 프로젝트 만들어본 결과, 프론트보다 백엔드 쪽이 더 재미있다고 느꼈고, 복학하기 전에 깊게 공부해보고 싶었다. 다른 분야를 알아보기는 했지만, 취직의 길이 가장 열린 쪽이 백엔드라고 생각하기도 했었고, 많은 회사들이 Spring 사용하고 있어서 안정적으로 느껴지는 만큼 Spring을 공부해야겠다고 마음을 다짐했다.&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 style=&quot;letter-spacing: 0px;&quot;&gt;또한 혼자 공부하기보다는 제대로 된 커리큘럼과 멘토링, 잡혀진 환경 속에서 공부를 하고 싶었고, 특히 사람들과 협업하면서 프로젝트에 몰입할 수 있는 기간이 필요하다고 느꼈다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdEapW/dJMcagYnuTa/pnEMwH85PzazNCyrdvH1Ak/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdEapW/dJMcagYnuTa/pnEMwH85PzazNCyrdvH1Ak/img.jpg&quot; data-alt=&quot;계속 방황하는 나...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdEapW/dJMcagYnuTa/pnEMwH85PzazNCyrdvH1Ak/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdEapW%2FdJMcagYnuTa%2FpnEMwH85PzazNCyrdvH1Ak%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;246&quot; height=&quot;150&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;계속 방황하는 나...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그래서 부트캠프를 알아봤는데, 부트텐트라는 웹사이트를 이용한 기억이 있다:&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk5q9s/dJMcahCYQt9/5R9kR0cttggonXlOxLg1Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk5q9s/dJMcahCYQt9/5R9kR0cttggonXlOxLg1Uk/img.png&quot; data-alt=&quot;부트텐트 속 수많은 부트캠프들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk5q9s/dJMcahCYQt9/5R9kR0cttggonXlOxLg1Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk5q9s%2FdJMcahCYQt9%2F5R9kR0cttggonXlOxLg1Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;456&quot; height=&quot;239&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;471&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;부트텐트 속 수많은 부트캠프들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 여러 부트캠프를 알아보던 와중에, 코드잇 스프린트가 눈에 띄었다. 일단 가장 마음에 들었던 점을 나열해보자면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인턴십 기회 - 나는 복학하기 전에 무조건 백엔드 인턴십이 있었으면 좋겠다는 생각을 했다. 코드잇에 지원해주는 프로그램에 적극 참여해보기로 했다.&lt;/li&gt;
&lt;li&gt;코딩테스트 - 많은 부트캠프를 알아보면서 협업을 할때 잠수를 타거나 문제가 생기는 상황을 여럿 후기를 통해 접했었는데, 코딩테스트로 어느 정도 배울 의지가 있는 사람들을 골라내는 것 같아서 신뢰가 갔다.&lt;/li&gt;
&lt;li&gt;커리큘럼 - 스프링, AWS, 도커 등 실무에서 사용하는 기술을 배우는 커리큘럼이 마음에 들었다.&amp;nbsp;&lt;/li&gt;
&lt;li&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;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;배운 것들&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백엔드 개발의 기초
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Git과 Githb를 활용한 협업과 Java 기초 학습&lt;/li&gt;
&lt;li&gt;자료구조, 알고리즘, Big-O로 데이터 성능 최적화&lt;/li&gt;
&lt;li&gt;Spring과 Spring Boot로 애플리케이션 개발 및 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;중급 백엔드 개발
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RESTful API 설계와 SQL을 활용한 데이터 처리&lt;/li&gt;
&lt;li&gt;Spring Data JPA와 Transaction으로 데이터 처리&lt;/li&gt;
&lt;li&gt;Spring으로 RESTful API 설계 및 구현&lt;/li&gt;
&lt;li&gt;기초 프로젝트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고급 백엔드 개발과 배포
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TDD로 Spring 서비스의 안정성 및 품질 관리&lt;/li&gt;
&lt;li&gt;Docker로 컨테이너화 및 배포 자동화 학습&lt;/li&gt;
&lt;li&gt;AWS ECS, S3, RDS로 클라우드 애플리케이션 운영 학습&lt;/li&gt;
&lt;li&gt;중급 프로젝트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고급 기술과 안정적 시스템
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Security로 보안 기초와 인증/인가 학습&lt;/li&gt;
&lt;li&gt;비동기 처리와 Spring Cache로 대용량 트래픽 관리&lt;/li&gt;
&lt;li&gt;디자인 패턴으로 효율적인 네트워크 구조 설계&lt;/li&gt;
&lt;li&gt;고급&amp;nbsp;프로젝트&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;p data-ke-size=&quot;size16&quot;&gt;일단 수업 내용에 대해서는 정말 좋다고 느꼈다. 사실 고급 백엔드 개발과 배포 쪽까지만 해도 됐었을 텐데, 더 나아가서 spring security나, spring batch, redis, websocket, kafka 등 고급 심화 기술들을 많이 다뤘어서 실무가 어떤건지 조금 체험(?)하게 된것 같아서 좋았다.&lt;/p&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;참고로... 나는 스프링에 대해 진짜 거의 아무것도 모른 채 들어갔다. 전에 언급했던 토이 프로젝트는 사실 노드를 사용했었는데, 스프링으로 선택하게 된 계기가 안정성이라는 이유였다. 그래서 Spring Boot, Bean, MVC, Layered architecture, data jpa, TDD등을 배웠을 때 정말 무슨 신세계(?) 같았다. 내가 모르는 세계가 따로 있었다는 것을 알게 되고, 사실 한국은 기본적으로 스프링을 많이 사용하고 있고 커뮤니티가 꽤 커서 새삼 혼자 놀랐던 적이 있다. 이런 의미로 사실 운이 엄청 좋았다고 생각했다.&lt;/li&gt;
&lt;li&gt;자바에 대해서 굉장히 깊게 공부할 수 있었다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;DB를 좀 깊숙히 잘 알아야 한다고 생각했다. 이번 부캠동안 디비를 사용하긴 했지만 아직 디비에 잘 모르는 것 같다...&amp;nbsp;&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;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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코드잇 위클리 페이퍼&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;매주 2가지의 주제로 위클리페이퍼를 쓰고 팀원들과 공유하는 방식이었다. 먼지만 싸이던 티스토리에 글도 좀 생기고 배운것을 정리하니까 머릿속에 개념이 좀 더 잘 잡히는 느낌이었다. 하지만 9-7에 과제랑 노트정리랑 다같이 시간 관리 하는게 좀 어려웠던 것 같다. 그래도 마지막에 기록만 이렇게 남으니까.. 역시 기록을 꾸준히 하는건 중요하구나..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuT34j/dJMcadmZURd/L8X5UVDaOvSYXf5U7Hzyik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuT34j/dJMcadmZURd/L8X5UVDaOvSYXf5U7Hzyik/img.png&quot; data-alt=&quot;매주 쓴 위클리 페이퍼&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuT34j/dJMcadmZURd/L8X5UVDaOvSYXf5U7Hzyik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuT34j%2FdJMcadmZURd%2FL8X5UVDaOvSYXf5U7Hzyik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;339&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;561&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;매주 쓴 위클리 페이퍼&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드잇 과제&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;과제는 총 12개 진행했고, 과제를 진행할때마다 새 브랜치를 만들고 메인 리포로 PR을 날리는 방법으로 제출했었다. 리뷰는정해진 멘토님이 코드리뷰를 해주셨는데, 그 과정에서 코드를 수정하거나 바꾸기도 했다. 때로는 칭찬을 해주시거나 피드백을 꼼꼼히 주셔서 많이 배울 수 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;503&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6sohu/dJMcadAxrbu/E5oxZKM2YtzIaz3bHQYJGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6sohu/dJMcadAxrbu/E5oxZKM2YtzIaz3bHQYJGK/img.png&quot; data-alt=&quot;멘토님의 피드백&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6sohu/dJMcadAxrbu/E5oxZKM2YtzIaz3bHQYJGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6sohu%2FdJMcadAxrbu%2FE5oxZKM2YtzIaz3bHQYJGK%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;406&quot; height=&quot;335&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;멘토님의 피드백&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멘토링 시간
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀별로 멘토링 시간을 가졌는데, 다양한 경력의 현직자 분들을 만날 수 있었다. 부트캠프에만 찌들어 있으면 원가 세속과 단절(?)되는데, 매주 이런 시간을 가지면서 시야를 좀 넓힐 수 있었다 ㅎㅎ&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;깃과 깃허브 사용법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;드디어 좀 미스터리 같았던 깃이랑 깃허브를 좀 이해할 수 있었다. 매일은 채우지 못했지만.. 깃허브 잔디도 꽤 채울 수 있었다!&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qfiD8/dJMcaiu5up8/x9kGbpVp8KLfbKlJRiVbx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qfiD8/dJMcaiu5up8/x9kGbpVp8KLfbKlJRiVbx1/img.png&quot; data-alt=&quot;깃헙 잔디 ㅎㅎ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qfiD8/dJMcaiu5up8/x9kGbpVp8KLfbKlJRiVbx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqfiD8%2FdJMcaiu5up8%2Fx9kGbpVp8KLfbKlJRiVbx1%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;613&quot; height=&quot;225&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;깃헙 잔디 ㅎㅎ&lt;/figcaption&gt;
&lt;/figure&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;h2 data-ke-size=&quot;size26&quot;&gt;  프로젝트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뻔한 이야기지만.. 팀원들과 프로젝트를 했었던 것이 너무 좋았다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1️⃣ 기초 프로젝트 - Findex (핀덱스)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Sprint-team-3/sb4-findex-team3&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Sprint-team-3/sb4-findex-team3&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 프로젝트에 얼떨결에 사다리뽑기로 팀장을 맞게 되었다. 하지만 다들 협업이 생소하고, 아직 초반이여서 대체로 시행착오가 많았던 것 같다. 그래도 팀원분들은 다들 착하시고, 다들 최선을 다했다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 되돌아보면, 팀장으로서 부족한 부분이 많았던 것 같은데 잘 따라와주시는 팀한테 고마웠다. 또 기술적으로 아쉬운 부분이 많았는데, 지금 생각해보면 리팩토링하거나 고도화 하면 좋을 것 같은 부분들이 꽤 있었다. (예를 들면 쿼리문, 로깅, DB 배포, 모니터링 등등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 중간에 git이 꼬이기도 하였고, railway로 배포하려고 했을 때 ORM 에러때문에 꼬박 밤을 새운 기억이 났다..ㅋㅋ 그래서 이 프로젝트 이후 중급/고급 프로젝트도 이럴지 벌벌 떨면서(?) 생각했던 적도 있는데 보니까 사실 초급 프로젝트가 가장 힘들었던 것 같다 (허허) 하지만 그만큼 특별한 기억으로 남았고 마지막에 배로 뿌듯했던 기억이 있다. 다들 너무 고생이 많았던 것 같고 또 고맙기도 하다. 코드잇 부트캠프 중 가장 기억에 남는 순간이라고 하면 핀덱스 프로젝트를 떠올릴 것 같다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2️⃣ 중급 프로젝트 - MoNew (모두의 뉴스)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/sb04-team02/sb04-MoNew-team02&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/sb04-team02/sb04-MoNew-team02&lt;/a&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;이때는 몽고디비, 배치/스케쥴러, 그리고 AWS CICD 배포쪽을 맡게 되었는데 정말 한꺼번에 새로운 것을 많이 가져가는 기분이었다. 특히 AWS 콘솔을 만지면서 인스턴스를 만들고 프로젝트를 실행했었는데 디버깅은 배로 어려웠지만 굉장히 흥미로웠던 기억이 있다. 이때 정말 많이 배웠고, 이때부터 클라우드 엔지니어링 쪽에도 관심을 가지게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3️⃣ 고급 프로젝트 - Mopl (모두의 플리)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/sb04-mopl-team2/sb04-mopl-team2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/sb04-mopl-team2/sb04-mopl-team2&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고급 프로젝트 답게 심화 기능을 많이 담은 프로젝트이다 (며칠 전에 끝나서 아직 프레시하다). 이때 웹소켓을 사용한 실시간 채팅이랑 시청 세션을 관리하는 기능, 그리고 OpenSearch로 콘텐츠 서치 고도화하는 역할을 맡았었다. 아 그리고 TDD 기반으로 진행했는데 사실 전 프로젝트에서는 시간상 못했기 때문에 이번에는 적용할 수 있어서 뿌듯했다.&lt;/p&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;s&gt;사실 안웃김&lt;/s&gt; ㅜㅜ). 동기부여도 많이 되었고, 팀원분들과 라포트도 쌓을 수 있어서 재밌었다. 그리고 회의를 되게 꼼꼼하게 길게 하는 편이었는데, 중간에 많이 배우고 토론할 수 있어서 굉장히 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btSEnG/dJMcagYnvbN/uNwbwW6zV4eKkVHk12w7rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btSEnG/dJMcagYnvbN/uNwbwW6zV4eKkVHk12w7rK/img.png&quot; data-alt=&quot;모플 팀 노션 프로젝트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btSEnG/dJMcagYnvbN/uNwbwW6zV4eKkVHk12w7rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtSEnG%2FdJMcagYnvbN%2FuNwbwW6zV4eKkVHk12w7rK%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;733&quot; height=&quot;382&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모플 팀 노션 프로젝트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 기간 내에 완성할 수 있었고 블로그에도 트러블슈팅 대목을 정리해두어서 다행이라고 생각한다.&lt;/p&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;br /&gt;&lt;br /&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;/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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KHO0H/dJMcaiolTm5/S0ChwxqsIBmBuNRkm13hSk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KHO0H/dJMcaiolTm5/S0ChwxqsIBmBuNRkm13hSk/img.jpg&quot; data-alt=&quot;나는..할 수 있어..!!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KHO0H/dJMcaiolTm5/S0ChwxqsIBmBuNRkm13hSk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKHO0H%2FdJMcaiolTm5%2FS0ChwxqsIBmBuNRkm13hSk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;183&quot; height=&quot;183&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;나는..할 수 있어..!!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부트캠프가 12월에 딱 끝나니까 올해를 마무리하는 느낌도 들고 뭔가 기분이 조금 싱숭생숭하다.&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;7개월 전에 비해 나를 비교한다면:&lt;/p&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;학습하는 습관이 생겼고, 코딩이나 협업에 대해서 더 자신감이 생겼다.&amp;nbsp;&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;/li&gt;
&lt;li&gt;초급, 중급 프로젝트 리팩토링을 시간상 문제로 하지 못한게 아쉽다. (프로젝트들을 끝내면 바로 9-7 강의가 이어진다).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 이 모든 것을 종합해보면 아쉬운 부분도 있지만 그래도 결과적으로 만족스럽게 마칠 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아직 주니어 개발자로서 준비는 덜 된 것 같다. 그동안 못해왔던 기술 모의 면접, 코딩테스트 공부, 이력서/포폴 다듬기 등 해야할 일이 꽤 많이 남았다.&amp;nbsp;&lt;/p&gt;
&lt;p 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;/li&gt;
&lt;li&gt;면접 연습과 기본적인 기술 질문들 복습&lt;/li&gt;
&lt;li&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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IGWhg/dJMcahCYSyE/U22zlYbSVGxAldEwiQJ2sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IGWhg/dJMcahCYSyE/U22zlYbSVGxAldEwiQJ2sk/img.png&quot; data-alt=&quot;마지막 mopl 팀!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IGWhg/dJMcahCYSyE/U22zlYbSVGxAldEwiQJ2sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIGWhg%2FdJMcahCYSyE%2FU22zlYbSVGxAldEwiQJ2sk%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;191&quot; height=&quot;226&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마지막 mopl 팀!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이스브레이킹부터 퀴즈 세션, 수료식까지 마지막까지 재미있게 시간을 보낼 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1959&quot; data-origin-height=&quot;993&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YXCvS/dJMcagc1tyg/Xiu6N31mKTt2Y3dCyX3IKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YXCvS/dJMcagc1tyg/Xiu6N31mKTt2Y3dCyX3IKK/img.png&quot; data-alt=&quot;온라인 수료식 스크린샷&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YXCvS/dJMcagc1tyg/Xiu6N31mKTt2Y3dCyX3IKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYXCvS%2FdJMcagc1tyg%2FXiu6N31mKTt2Y3dCyX3IKK%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;1959&quot; height=&quot;993&quot; data-origin-width=&quot;1959&quot; data-origin-height=&quot;993&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;온라인 수료식 스크린샷&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m3yjA/dJMcajtZ7EO/jKcni9UCRB0qwiKSa75hQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m3yjA/dJMcajtZ7EO/jKcni9UCRB0qwiKSa75hQK/img.png&quot; data-alt=&quot;ㅋㅋ 중간에 웃겨서 스샷함&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m3yjA/dJMcajtZ7EO/jKcni9UCRB0qwiKSa75hQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm3yjA%2FdJMcajtZ7EO%2FjKcni9UCRB0qwiKSa75hQK%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;316&quot; height=&quot;137&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ㅋㅋ 중간에 웃겨서 스샷함&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/396ld/dJMcadHjUgb/t9qek5SliRsLKiDwokdws0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/396ld/dJMcadHjUgb/t9qek5SliRsLKiDwokdws0/img.png&quot; data-alt=&quot;퀴즈부분 상품을 타서 받은 기프티콘 ㅋㅋ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/396ld/dJMcadHjUgb/t9qek5SliRsLKiDwokdws0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F396ld%2FdJMcadHjUgb%2Ft9qek5SliRsLKiDwokdws0%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;479&quot; height=&quot;259&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;퀴즈부분 상품을 타서 받은 기프티콘 ㅋㅋ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에는 진짜 뿌듯했다ㅎㅎ.&lt;/p&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;</description>
      <category>Codeit</category>
      <category>JAVA개발자</category>
      <category>spring부트캠프</category>
      <category>개발자부트캠프</category>
      <category>백엔드개발자취업</category>
      <category>백엔드부트캠프</category>
      <category>코드잇스프린트</category>
      <category>코드잇스프린트후기</category>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/74</guid>
      <comments>https://leejunkim.tistory.com/74#entry74comment</comments>
      <pubDate>Fri, 19 Dec 2025 20:32:16 +0900</pubDate>
    </item>
    <item>
      <title>[모두의 플리] ES 성능 테스트!</title>
      <link>https://leejunkim.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ES 검색기능을 구현 후, 성능 테스트를 해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 TimeConfiguration을 빈드로 수동 등록해줘야 했다:&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class TimerConfiguration {
  @Bean
  public TimedAspect timedAspect(MeterRegistry meterRegistry) {
    return new TimedAspect(meterRegistry);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ContentController:&lt;/p&gt;
&lt;pre id=&quot;code_1765269879386&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Timed(&quot;search.sql&quot;)
  @GetMapping(&quot;/test/sql&quot;)
  public CursorResponseContentDto findContentsRepository(@Valid @ModelAttribute ContentSearchRequest request) {
    log.info(&quot;[콘텐츠 목록 조회 요청] contentRepository - request={}&quot;, request);

    CursorResponseContentDto response = contentService.findContents(request);

    log.info(&quot;[콘텐츠 목록 조회 응답] contentRepository - resultCount={}&quot;,
        response.data() != null ? response.data().size() : 0);
    return response;
  }

  @Timed(&quot;search.request&quot;)
  @GetMapping(&quot;/test/es&quot;)
  public CursorResponseContentDto findContentsEsRepository(@Valid @ModelAttribute ContentSearchRequest request) {
    log.info(&quot;[콘텐츠 목록 조회 요청] contentEsRepository - request={}&quot;, request);

    CursorResponseContentDto response = contentService.findContentsEsRepository(request);

    log.info(&quot;[콘텐츠 목록 조회 응답] contentEsRepository - resultCount={}&quot;,
        response.data() != null ? response.data().size() : 0);
    return response;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트용 컨트롤러 엔드포인트 2개를 만들었다. 하나는 sql db를 조회하고, 하나는 es db를 조회한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 환경이었는데, ddl-auto: update로 해놔서 데이터를 새로 불러올 필요가 없었고, sql db와 es db는 동기화를 마친 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 actuator에 metrics를 추가했다&lt;/p&gt;
&lt;pre id=&quot;code_1765270010408&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics, prometheus
  endpoint:
    health:
      show-details: always&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;테스트를 해야하므로 Postman으로 각 엔드포인트에 리퀘스트를 3번 보내봤다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://localhost:8080/api/contents/test/sql?keywordLike=안녕&amp;amp;limit=10&amp;amp;sortBy=createdAt&amp;amp;sortDirection=DESCENDING&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://localhost:8080/api/contents/test/es?keywordLike=안녕&amp;amp;limit=10&amp;amp;sortBy=createdAt&amp;amp;sortDirection=DESCENDING&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL (http://localhost:8080/actuator/metrics/search.sql)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg4RPK/dJMcafrz1ys/wfbqDIFNgtEpG0U05U1KA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg4RPK/dJMcafrz1ys/wfbqDIFNgtEpG0U05U1KA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg4RPK/dJMcafrz1ys/wfbqDIFNgtEpG0U05U1KA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg4RPK%2FdJMcafrz1ys%2FwfbqDIFNgtEpG0U05U1KA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;606&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES (http://localhost:8080/actuator/metrics/search.request)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8iWIM/dJMcacax4o8/oDVLSChkNKvFkKNjecC44K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8iWIM/dJMcacax4o8/oDVLSChkNKvFkKNjecC44K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8iWIM/dJMcacax4o8/oDVLSChkNKvFkKNjecC44K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8iWIM%2FdJMcacax4o8%2FoDVLSChkNKvFkKNjecC44K%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;553&quot; height=&quot;663&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 3번 리퀘스트를 각 엔드포인트로 보냈는데, 평균 시간을 계산해보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL: 평균 = TOTAL_TIME / COUNT = 1.841346299/3 = 0.61378209966 초 (613 ms)&lt;/li&gt;
&lt;li&gt;ES: 평균 = TOTAL_TIME / COUNT = 0.529007001/3 = 0.176335667 초 (176 ms)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 리퀘스트 기준으로 보니까 ES가 약 3.5배 정도 빠르다는 것을 확인했다!&lt;/p&gt;</description>
      <category>Codeit/프로젝트</category>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/73</guid>
      <comments>https://leejunkim.tistory.com/73#entry73comment</comments>
      <pubDate>Tue, 9 Dec 2025 17:53:12 +0900</pubDate>
    </item>
    <item>
      <title>[모두의 플리] 트러블슈팅 - 실시간 시청 세션 동기화와 Race condition</title>
      <link>https://leejunkim.tistory.com/72</link>
      <description>&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;내가 짰던 코드 흐름&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WatchingSessionController
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 시청자 목록을 조회(GET)하여 화면에 렌더링하는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;WebSocketEventListener
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SessionSubscribeEvent 발생시 여기서 시청 세션 (WatchingSession 엔티티)를 생성하고, 그 정보를 가져다 payload로 보내서 실시간 입장 브로드캐스트&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;SessionUnsubscribeEvent&lt;span&gt; / &lt;/span&gt;&lt;/span&gt;SessionDisconnectEvent 발생 시 삭제된 WatchingSession 의 정보를 가져다 payload로 보내서 실시간 퇴장 브로드캐스트&lt;/li&gt;
&lt;li&gt;결국 화면에 보이는 실시간 참여자 수를 실시간으로 업데이트가 되게 해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컨트롤러의 조회 API는 이미 들어와 있던 사람들 정보를 볼 수 있게 해주고, WebSocketEventListener는 새로 들어온 &quot;나&quot;의 정보를 목록조회에 더한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지 진입 시, DB에는 데이터가 있는데 화면에는 시청자 수가 잘못 표시되는 현상이 발생했다. 보니까 watchingSession을 GET할때 빈 데이터를 받아오지만, DB를 확인해보면 또 데이터는 정상적으로 저장되어 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;원인 분석&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 요청(Controller)과 WebSocket 연결(Listener) 간의 실행 순서 불일치(Race Condition)가 원인이었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 진입 시 - WebSocket 핸드셰이크 비용으로 인해 연결이 늦게 맺어지고 SessionSubscribeEvent도 늦게 발행된다.그래서 HTTP GET 요청이 먼저 실행될때 아직 DB 저장 전이므로, 빈 목록을 조회했던 것이다.&lt;/li&gt;
&lt;li&gt;이후 WebSocketEventListener의 SessionSubscribeEvent가 실행됨 - &amp;nbsp;뒤늦게 DB에 저장되지만, 클라이언트는 이미 빈 목록을 받은 상태.&lt;/li&gt;
&lt;li&gt;로그를 확인해보니까 순서가 이렇게 되어있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025-12-01T13:59:56.790+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-4]&amp;nbsp;.w.r.CustomWatchingSessionRepositoryImpl&amp;nbsp;:&amp;nbsp;[Repository]&amp;nbsp;findWatchingSessions&amp;nbsp;called&amp;nbsp;with:&amp;nbsp;userId=1e084ee4-95aa-4346-b8c5-1fab1ea9c2d5,&amp;nbsp;contentId=0d07f1d0-e2d2-45c3-a964-1f525b32d7a4,&amp;nbsp;watcherNameLike=null,&amp;nbsp;cursor=null,&amp;nbsp;idAfter=null,&amp;nbsp;limit=51 &lt;br /&gt;2025-12-01T13:59:56.803+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-4]&amp;nbsp;.w.r.CustomWatchingSessionRepositoryImpl&amp;nbsp;:&amp;nbsp;[Repository]&amp;nbsp;Total&amp;nbsp;sessions&amp;nbsp;in&amp;nbsp;DB&amp;nbsp;for&amp;nbsp;contentId&amp;nbsp;0d07f1d0-e2d2-45c3-a964-1f525b32d7a4:&amp;nbsp;0 &lt;br /&gt;...&lt;br /&gt;2025-12-01T13:59:57.161+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-5]&amp;nbsp;c.c.m.e.w.WebSocketEventListener&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;[WebsocketEventListener]&amp;nbsp;handleSessionSubscribe&amp;nbsp;시작&amp;nbsp;-&amp;nbsp;sessionId:&amp;nbsp;nzlculvh,&amp;nbsp;destination:&amp;nbsp;/sub/contents/0d07f1d0-e2d2-45c3-a964-1f525b32d7a4/watch &lt;br /&gt;2025-12-01T13:59:57.161+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-5]&amp;nbsp;c.c.m.e.w.WebSocketEventListener&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;[WebsocketEventListener]&amp;nbsp;getContentId&amp;nbsp;시작&amp;nbsp;-&amp;nbsp;destination:&amp;nbsp;/sub/contents/0d07f1d0-e2d2-45c3-a964-1f525b32d7a4/watch &lt;br /&gt;2025-12-01T13:59:57.161+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-5]&amp;nbsp;c.c.m.e.w.WebSocketEventListener&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;[WebSocketEventListener]&amp;nbsp;컨텐트&amp;nbsp;아이디&amp;nbsp;파싱&amp;nbsp;완료:&amp;nbsp;contentId=0d07f1d0-e2d2-45c3-a964-1f525b32d7a4 &lt;br /&gt;2025-12-01T13:59:57.161+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-5]&amp;nbsp;c.c.m.e.w.WebSocketEventListener&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;[WebsocketEventListener]&amp;nbsp;getUser&amp;nbsp;시작&amp;nbsp;-&amp;nbsp;sessionId:&amp;nbsp;nzlculvh &lt;br /&gt;2025-12-01T13:59:57.169+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-5]&amp;nbsp;c.c.m.e.w.WebSocketEventListener&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;[WebsocketEventListener]&amp;nbsp;getUser&amp;nbsp;완료&amp;nbsp;-&amp;nbsp;userId:&amp;nbsp;1e084ee4-95aa-4346-b8c5-1fab1ea9c2d5 &lt;br /&gt;2025-12-01T13:59:57.169+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-5]&amp;nbsp;c.c.m.e.w.WebSocketEventListener&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;[WebsocketEventListener]&amp;nbsp;서비스&amp;nbsp;-&amp;nbsp;DB&amp;nbsp;저장&amp;nbsp;시작:&amp;nbsp;sessionId=nzlculvh &lt;br /&gt;2025-12-01T13:59:57.289+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-5]&amp;nbsp;c.c.m.e.w.WebSocketEventListener&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;[WebsocketEventListener]&amp;nbsp;서비스&amp;nbsp;-&amp;nbsp;DB&amp;nbsp;저장&amp;nbsp;성공:&amp;nbsp;sessionId=nzlculvh &lt;br /&gt;2025-12-01T13:59:57.331+09:00&amp;nbsp;&amp;nbsp;INFO&amp;nbsp;17648&amp;nbsp;---&amp;nbsp;[mopl]&amp;nbsp;[nio-8080-exec-5]&amp;nbsp;c.c.m.e.w.WebSocketEventListener&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;[WebsocketEventListener]&amp;nbsp;handleSessionSubscribe&amp;nbsp;완료&amp;nbsp;-&amp;nbsp;userId:&amp;nbsp;1e084ee4-95aa-4346-b8c5-1fab1ea9c2d5,&amp;nbsp;contentId:&amp;nbsp;0d07f1d0-e2d2-45c3-a964-1f525b32d7a4,&amp;nbsp;watcherCount:&amp;nbsp;1 &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;시도 1&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시도 1 (책임 분리): 세션 생성을 HTTP 요청 시점으로 이동하고, WebSocket은 SubscribeEvent가 발행할때 세션을 조회하는 로직으로 바꿨다 (여기서 이제 생성+저장은 안함).
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;결과:&lt;/i&gt; 첫 진입은 해결되었지만, &lt;b&gt;그 이후&lt;/b&gt; 다른 컨텐츠 클릭 시 WebSocket 이벤트가 HTTP 요청보다 먼저 터졌다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 SubscribeEvent시 DB에 데이터가 없어 실시간 시청자 수와 정보가 제대로 업데이트가 안됐었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QIJhL/dJMcaa4Ptup/fQSqFN1j6aMeDBRHVevja0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QIJhL/dJMcaa4Ptup/fQSqFN1j6aMeDBRHVevja0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QIJhL/dJMcaa4Ptup/fQSqFN1j6aMeDBRHVevja0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQIJhL%2FdJMcaa4Ptup%2FfQSqFN1j6aMeDBRHVevja0%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;1269&quot; height=&quot;559&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그 이유는 웹소켓은 최초 연결 시에만 느리고, 그 이후에는 GET보다 빠르게 실행됐었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최초 진입때는 브라우저가 서버와 TCP 연결을 맺고, 다시 HTTP 업그레이드 요청을 보내서 웹소켓으로 전환하는 과정이 필요하고&amp;nbsp; 단순 HTTP 요청보다 무겁다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시도 2 (해결)&amp;nbsp;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최종적으로, HTTP와 WebSocket 중 누가 먼저 도착할지 보장할 수 없다는 점을 인지하고 로직을 변경했다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;양쪽 진입점(Controller, EventListener) 모두에서 joinSession() 메서드를 호출하게 했다.&lt;/li&gt;
&lt;li&gt;joinSession 메서드
&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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tXprE/dJMcajglUWC/d0Iw13lQUmtdEr1lGlKde0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tXprE/dJMcajglUWC/d0Iw13lQUmtdEr1lGlKde0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tXprE/dJMcajglUWC/d0Iw13lQUmtdEr1lGlKde0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtXprE%2FdJMcajglUWC%2Fd0Iw13lQUmtdEr1lGlKde0%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;674&quot; height=&quot;299&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blJ6ef/dJMcagKKoF4/4NJo9QpIiK4RohZpFHfxck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blJ6ef/dJMcagKKoF4/4NJo9QpIiK4RohZpFHfxck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blJ6ef/dJMcagKKoF4/4NJo9QpIiK4RohZpFHfxck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblJ6ef%2FdJMcagKKoF4%2F4NJo9QpIiK4RohZpFHfxck%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;777&quot; height=&quot;674&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초반에 최초 연결할 때는 컨트롤러로 GET 메서드가 먼저 불려서 joinSession을 호출하면 새로운 WatchingSession이 생성+저장된다. 그리고 이제 최초 웹소켓 연결이 맺어질때 SessionSubscribeEvent가 발행하고 마찬가지로 joinSession을 부른다. 이때 이미 컨트롤러가 데이터를 저장했으므로 그냥 있는 데이터를 가져온다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;그 이후에는, 이미 웹소켓 연결이 됀 상태라 SessionSubscribeEvent가 GET 보다 더 빨리 호출된다. 그래서 이떄는 웹소켓 쪽에서 먼저 joinSession을 호출하고 데이터를 생성+저장하고, 그 이후 컨트롤러의 GET이 호출되서 이미 저장된 데이터를 조회한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결과적으로, 새로고침(Cold Start)과 페이지 이동(Warm Connection) 모든 상황에서 시청자 목록 조회 및 실시간 입장 알림이 정확하게 동작함을 확인했다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Codeit/프로젝트</category>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/72</guid>
      <comments>https://leejunkim.tistory.com/72#entry72comment</comments>
      <pubDate>Tue, 2 Dec 2025 14:33:27 +0900</pubDate>
    </item>
    <item>
      <title>Elastisearch 토이 프로젝트 만들어보기 (프로젝트를 위한 준비..)</title>
      <link>https://leejunkim.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/@liliumbosniacumcode/videos&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/@liliumbosniacumcode/videos&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1764222196024&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;profile&quot; data-og-title=&quot;Lilium Code&quot; data-og-description=&quot;Trying to share my knowledge as best as I can. All code is available on github https://github.com/liliumbosniacum&quot; data-og-host=&quot;www.youtube.com&quot; data-og-source-url=&quot;https://www.youtube.com/@liliumbosniacumcode/videos&quot; data-og-url=&quot;https://www.youtube.com/channel/UCHu8fdv3oIxYOVr9e92AxYQ&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LCLwt/hyZOsB5fYh/V9rWfI16Gx90snjW7EgPF0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/cKsNJb/hyZOOqP2Wr/QIikOmdTj23msdkH0CdUNK/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900&quot;&gt;&lt;a href=&quot;https://www.youtube.com/@liliumbosniacumcode/videos&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.youtube.com/@liliumbosniacumcode/videos&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LCLwt/hyZOsB5fYh/V9rWfI16Gx90snjW7EgPF0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/cKsNJb/hyZOOqP2Wr/QIikOmdTj23msdkH0CdUNK/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900');&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;Lilium Code&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Trying to share my knowledge as best as I can. All code is available on github https://github.com/liliumbosniacum&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.youtube.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;elasticache를 사용하기 위해 일단 docker compose를 만들었다:&lt;/p&gt;
&lt;pre id=&quot;code_1764223703824&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.7'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.10
    container_name: elasticsearch
    environment:
      - node.name=elasticsearch
      - discovery.type=single-node
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - &quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;
      # 보안 설정 (비밀번호 설정하고 싶으면 true, 귀찮으면 false)
      # 공부용/로컬용은 보통 끄고 하거나, 켜더라도 간단하게 설정
      - xpack.security.enabled=false
    ports:
      - &quot;9200:9200&quot;
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es_data:/usr/share/elasticsearch/data

# http://localhost:5601
  kibana:
    image: docker.elastic.co/kibana/kibana:7.17.10
    container_name: kibana
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - &quot;5601:5601&quot;
    depends_on:
      - elasticsearch

volumes:
  es_data:
    driver: local&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;RegisterConfig:&lt;/p&gt;
&lt;pre id=&quot;code_1764223769206&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableElasticsearchRepositories(basePackages = &quot;personal.estoyproject.repository&quot;)
public class RegisterConfig extends ElasticsearchConfiguration {

  @Value(&quot;${register.elasticsearch.user}&quot;)
  private String username;

  @Value(&quot;${register.elasticsearch.password}&quot;)
  private String password;

  /*
  where u put all things u need to connect to elastisearch
  - includes address, port, authorization, etc
  - use builder for client config
   */
  @Override
  public ClientConfiguration clientConfiguration() {

      return ClientConfiguration.builder()
          .connectedToLocalhost() // in production use smth else
          .build();
  }
}&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;그리고 이런 식으로 간단하게 Person이라는 Document와 인덱스를 설정할 수 있다:&lt;/p&gt;
&lt;pre id=&quot;code_1764223739312&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Document(indexName = &quot;person&quot;)
@Getter
@Setter
public class PersonDocument {
  private String id;
  private String name;
}&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;그리고 이어서 간단하게 PersonDocument를 저장하는 컨트롤러, 서비스, 리포지토리를 만들어줬다.&lt;/p&gt;
&lt;pre id=&quot;code_1764223859467&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
  Controller
*/
@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api/person&quot;)
public class PersonController {

  private final PersonService personService;

  @PostMapping
  public void save(@RequestBody final PersonDto personDto) {
    personService.save(personDto);
  }
}

/*
  Service
*/
@Service
@RequiredArgsConstructor
public class PersonService {

  private final PersonRepository personRepository;
  private final PersonMapper personMapper;

  public void save(final PersonDto dto) {
    final PersonDocument document = personMapper.convertToDocument(dto);
    if (document == null) return;
    personRepository.save(document);
  }
}


/*
  Repository
*/
public interface PersonRepository extends ElasticsearchRepository&amp;lt;PersonDocument, String&amp;gt; {
}&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;이제 docker compose up 명령 + 어플리케이션을 실행을 하고 &amp;nbsp;http://localhost:5601 (키바나) -&amp;gt; console에 들어갔다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1117&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF20za/dJMcabilrLC/1UJlD1yVXKt12hyb0anKD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF20za/dJMcabilrLC/1UJlD1yVXKt12hyb0anKD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF20za/dJMcabilrLC/1UJlD1yVXKt12hyb0anKD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF20za%2FdJMcabilrLC%2F1UJlD1yVXKt12hyb0anKD1%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;1117&quot; height=&quot;309&quot; data-origin-width=&quot;1117&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 GET _cat/indeces를 하면 아직 INSERT해서 document를 넣지 않았는데도 person index가 존재하는 것을 볼 수 있다.&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;person만 Yellow다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Green - 모든 데이터(primary)와 복제본 (replica)이 안전하게 저장됨&lt;/li&gt;
&lt;li&gt;Yellow - 데이터는 있는데 복제본을 저장할 곳이 없다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;person 만 Yellow인 이유: ES는 기본적으로 인덱스를 만들 때 백업본을 1개 만든다. 하지만 현재 토이 프로젝트에서는 docker compose로 노드 1대만 띄우고 있는데, ES의 원칙상 &lt;b&gt;원본과 복제본은 절대 같은 노드에 두지 않는다&lt;/b&gt;. 그래서 원본은 저장했는데 복제본을 저장할 다른 노드가 없어서 yellow 상태로 대기중인 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Red - 데이터 유실&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Postman으로 PersonDto를 테스트로 생성해봤다:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCYH45/dJMcaf563dg/wVjTwtwt15HXG57DgZlmg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCYH45/dJMcaf563dg/wVjTwtwt15HXG57DgZlmg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCYH45/dJMcaf563dg/wVjTwtwt15HXG57DgZlmg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCYH45%2FdJMcaf563dg%2FwVjTwtwt15HXG57DgZlmg1%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;930&quot; height=&quot;381&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnR14P/dJMcabWWppl/kHeKJQk8gXMAiBwlSpvIKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnR14P/dJMcabWWppl/kHeKJQk8gXMAiBwlSpvIKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnR14P/dJMcabWWppl/kHeKJQk8gXMAiBwlSpvIKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnR14P%2FdJMcabWWppl%2FkHeKJQk8gXMAiBwlSpvIKK%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;1115&quot; height=&quot;574&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;/figure&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;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 @Document를 사용하는건 명확한 한계가 있다. 이건 자동 방식 (automatic)쪽에 속한다.&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;tokenizer, filter, analyzer (분석기) 설정을 정교하게 넣기가 어렵다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수동 방식 (client를 사용)은 settings.json 파일을 따로 만들어서 분석기 설정을 우너하는 만큼 복잡하게 넣을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;운영 안정성도 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 팀원 A가 실수로 @Document 설정을 바꾸고 배포했다가, 기존 데이터 매핑이 꼬여버리면 그냥 대형 사고가 일어나는 것이다.&lt;/li&gt;
&lt;li&gt;그래서 실무에는 인덱스 생성은 배포 전에 관리자가 따로 (kibana나 script로) 하고, 스프링 앱은 이미 만들어진 인덱스를 쓰기만 하는 원칙을 많이 따른다.&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;새로 만든 IndexService&lt;/p&gt;
&lt;pre id=&quot;code_1764227094699&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Service
@RequiredArgsConstructor
public class IndexService {

  private final ElasticsearchClient client;

  public void createIndices() {
    List&amp;lt;IndexInfo&amp;gt; indexInfos = getIndexInformation();

    if (CollectionUtils.isEmpty(indexInfos)) { // exception or no docs
      return;
    }

    for (IndexInfo indexInfo : indexInfos) {
        deleteIndex(indexInfo);
        createIndex(indexInfo);
      }
  }


  // ======================

  private void deleteIndex(IndexInfo indexInfo) {
    try {
      BooleanResponse exists = client.indices().exists(e -&amp;gt; e.index(indexInfo.indexName()));
      if (!exists.value()) {
        return;
      }

      client.indices().delete(d -&amp;gt; d.index(indexInfo.indexName()));
    } catch (Exception e) {
      log.error(&quot;{}&quot;, e.getMessage(), e);
    }
  }

  private void createIndex(IndexInfo indexInfo) {
    try {
      // can add mapping here
      client.indices().create(c -&amp;gt; c.index(indexInfo.indexName()));
    } catch (Exception e) {
      log.error(&quot;{}&quot;, e.getMessage(), e);
    }
  }
  private List&amp;lt;IndexInfo&amp;gt; getIndexInformation()  {
    final var scanner = new ClassPathScanningCandidateComponentProvider(false);

    // ======== include filter ========
    // class of annotation we want to scan
    scanner.addIncludeFilter(new AnnotationTypeFilter(Document.class));

    // tells where to scan
    final Set&amp;lt;BeanDefinition&amp;gt; beanDefinitionSet = scanner.findCandidateComponents(&quot;personal.estoyproject&quot;);

    // iterate over bean definitions
    // - extract info from bean def, like index name
    return beanDefinitionSet.stream()
        .map(IndexService::getIndexName)
        .filter(Objects::nonNull)
        .map(IndexInfo::new)
        .toList();
  }


  private static String getIndexName(BeanDefinition definition)  {
    try {
      final Class&amp;lt;?&amp;gt; documentClass = Class.forName(definition.getBeanClassName());
      Document annotation = documentClass.getAnnotation(Document.class);
      return annotation.indexName();
    } catch (ClassNotFoundException e) {
      log.error(&quot;{}&quot;, e.getMessage(), e);
      return null;
    }
  }
}&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;강의를 따라하면서 IndexSerivice를 구현하게 되었다. 메인 함수는 createIndices()다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼서 @Document라고 붙은 class를 다 찾아내서 List&amp;lt;IndexInfo&amp;gt; 형태로 받아온다&lt;/li&gt;
&lt;li&gt;그 리스트를 순회하면서, 이미 같은 이름의 인덱스가 있으면 삭제하고 새로 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짧은 IndexController&lt;/p&gt;
&lt;pre id=&quot;code_1764227341784&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api/index&quot;)
@RequiredArgsConstructor
public class IndexController {

  private final IndexService indexService;

  @PostMapping
  public void create() {
    indexService.createIndices();
  }

}&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;gt; Delete person (키바나) -&amp;gt; 포스트맨으로 POST http://localhost:8080/api/index를 부름 -&amp;gt; 키바나에서 GET _cat/indices을 실행할때 person이 보인다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;269&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pDvMv/dJMcaajqPXd/UKaI4LlSBzrKMkOK6HVsvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pDvMv/dJMcaajqPXd/UKaI4LlSBzrKMkOK6HVsvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pDvMv/dJMcaajqPXd/UKaI4LlSBzrKMkOK6HVsvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpDvMv%2FdJMcaajqPXd%2FUKaI4LlSBzrKMkOK6HVsvk%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;948&quot; height=&quot;269&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;269&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하는 이유는 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;깨끗한 상태 보장 -&amp;gt; 테스트 데이터를 다시 초기화 하고 싶을때 유용한 것 같다. (중간에 Document 설정이 바뀌었을 수도 있고, 스키마 변경이 까다로워서 그냥 밀고 다시 만드는게 편함).&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&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;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;PersonDocument&lt;/p&gt;
&lt;pre id=&quot;code_1764229674020&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Document(indexName = &quot;person&quot;)
@Getter
@Setter
@Mapping(mappingPath = &quot;static/person.json&quot;)
// if we use repos to create documents, this is enough
// but if we're using indexservice (like creating our own)
public class PersonDocument {
  private String id;
  private String name;
}&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;이제 @Document를 단 모든 클래스를 뒤질 때 @Mapping이 static/person.json이라고 되어있는 걸 인식한다.&lt;/li&gt;
&lt;li&gt;이제 person이라는 index를 만들때 person.json파일을 사용한다&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;IndexInfo&lt;/p&gt;
&lt;pre id=&quot;code_1764229474634&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public record IndexInfo(
    String indexName,
    String mappingPath
) {

}&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;/static/person.json&lt;/p&gt;
&lt;pre id=&quot;code_1764229526533&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;properties&quot;: {
    &quot;id&quot;: {
      &quot;type&quot; : &quot;keyword&quot;
    },
    &quot;name&quot; : {
      &quot;type&quot; : &quot;text&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;수정된 IndexService&lt;/p&gt;
&lt;pre id=&quot;code_1764229495065&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Service
@RequiredArgsConstructor
public class IndexService {

  private final ElasticsearchClient client;
  private final ResourceLoader resourceLoader;

  public void createIndices() {
    List&amp;lt;IndexInfo&amp;gt; indexInfos = getIndexInformation();

    if (CollectionUtils.isEmpty(indexInfos)) { // exception or no docs
      return;
    }

    for (IndexInfo indexInfo : indexInfos) {
        deleteIndex(indexInfo);
        createIndex(indexInfo);
      }
  }


  // ======================

  private void deleteIndex(IndexInfo indexInfo) {
    try {
      BooleanResponse exists = client.indices().exists(e -&amp;gt; e.index(indexInfo.indexName()));
      if (!exists.value()) {
        return;
      }

      client.indices().delete(d -&amp;gt; d.index(indexInfo.indexName()));
    } catch (Exception e) {
      log.error(&quot;{}&quot;, e.getMessage(), e);
    }
  }

  private void createIndex(IndexInfo indexInfo) {
    try {
      // can add mapping here
      client.indices()
          .create(c -&amp;gt; c.index(indexInfo.indexName())
            .mappings(t -&amp;gt; t.withJson(getMappings(indexInfo.mappingPath())))
          );
    } catch (Exception e) {
      log.error(&quot;{}&quot;, e.getMessage(), e);
    }
  }

  private List&amp;lt;IndexInfo&amp;gt; getIndexInformation()  {
    final var scanner = new ClassPathScanningCandidateComponentProvider(false);

    // ======== include filter ========
    // class of annotation we want to scan
    scanner.addIncludeFilter(new AnnotationTypeFilter(Document.class));

    // tells where to scan
    final Set&amp;lt;BeanDefinition&amp;gt; beanDefinitionSet = scanner.findCandidateComponents(&quot;personal.estoyproject&quot;);

    // iterate over bean definitions
    // - extract info from bean def, like index name
    return beanDefinitionSet.stream()
        .map(IndexService::getIndexInfo)
        .filter(Objects::nonNull)
        .toList();
  }


  private static IndexInfo getIndexInfo(BeanDefinition definition)  {
    try {
      final Class&amp;lt;?&amp;gt; documentClass = Class.forName(definition.getBeanClassName());
      Document document = documentClass.getAnnotation(Document.class);
      Mapping mapping= documentClass.getAnnotation(Mapping.class);
      return new IndexInfo(
          document.indexName(),
          mapping.mappingPath()
      );
    } catch (ClassNotFoundException e) {
      log.error(&quot;{}&quot;, e.getMessage(), e);
      return null;
    }
  }
  
  private InputStream getMappings(final String mappingPath) {
    Resource resource = resourceLoader.getResource(&quot;classpath:&quot; + mappingPath);
    try {
      return resource.getInputStream();
    } catch (IOException e) {
      log.error(&quot;{}&quot;, e.getMessage(), e);
      return null;
    }
  }
}&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;이제 키바나로 가서 GET person/_mappings를 해주면 아까 static/person.json 파일이 나온다:&lt;/p&gt;
&lt;figure data-ke-type=&quot;image&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMXsdM/dJMcaacE5Ys/s1ntPTtLWwAEP1jKPaa0A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMXsdM/dJMcaacE5Ys/s1ntPTtLWwAEP1jKPaa0A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMXsdM/dJMcaacE5Ys/s1ntPTtLWwAEP1jKPaa0A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMXsdM%2FdJMcaacE5Ys%2Fs1ntPTtLWwAEP1jKPaa0A1%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;948&quot; height=&quot;296&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Codeit/프로젝트</category>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/71</guid>
      <comments>https://leejunkim.tistory.com/71#entry71comment</comments>
      <pubDate>Thu, 27 Nov 2025 17:00:50 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Elastisearch Analyzer- 데이터가 검색되는 원리(텍스트 분석/처리)</title>
      <link>https://leejunkim.tistory.com/69</link>
      <description>&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;문서는 ES DB에 저장되기 전에, 분석기(Analyzer)라는 과정을 한번 거친다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3kFs0/dJMcah3Vtq4/0zQ3XOkdHR7qSpKeHefS4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3kFs0/dJMcah3Vtq4/0zQ3XOkdHR7qSpKeHefS4K/img.png&quot; data-alt=&quot;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3kFs0/dJMcah3Vtq4/0zQ3XOkdHR7qSpKeHefS4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3kFs0%2FdJMcah3Vtq4%2F0zQ3XOkdHR7qSpKeHefS4K%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;379&quot; height=&quot;310&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정확하게 말하면, 문서의 필드 타입 중에 TEXT라는 문장들은 분석기를 거치고 DB에 저장된다!&lt;/li&gt;
&lt;li&gt;분석기에는 Character Filter, Tokenizer, Token Filter이 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;여기서 잠깐 - TEXT vs KEYWORD&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ES에서 문자열을 저장하는 타입은 크게 두가지다 - 그냥 분석기를 거치냐 안거치냐의 차이다.&lt;/li&gt;
&lt;li&gt;TEXT
&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;전문 검색 (full text search)에 쓰이는데, 예를 들면 &quot;애플 맥북 프로&quot;라고 저장해두고 &quot;맥북&quot;만 검색해도 찾고 싶을 때 사용된다.&lt;/li&gt;
&lt;li&gt;이 블로그 글은 TEXT 기반의 예시로 작성됐다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;KEYWORD
&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;/li&gt;
&lt;li&gt;보통 카테고리(&quot;가전&quot;), 브랜드명(&quot;Apple&quot;), 상태(&quot;판매중&quot;) 같은 데이터에 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;보통 제목이나 내용은 text로, 카테고리나 태그는 keyword로 설정한다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Analyzer (분석기)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Analyzer(분석기)
&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;Character Filter, Tokenizer, Token Filter로 이 3가지의 과정을 거친다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다양한 분석기가 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 분석기는 위의 파이프라인이 포함되고, 분석기마다 다른 filter/tokenizer를 사용함&lt;/li&gt;
&lt;li&gt;Standard analyzer (기본, 영어에 최적)&lt;/li&gt;
&lt;li&gt;Nori Analyzer (한국어)&lt;/li&gt;
&lt;li&gt;Whitespace&amp;nbsp;Analyzer
&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;Keyword Analyzer
&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;Custom Analyzer (커스텀)
&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;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계 - Character Filter&amp;nbsp;&lt;/h2&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ngX0c/dJMcafLN9oE/fvx3qmUg7Imgq5i8ICrNdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ngX0c/dJMcafLN9oE/fvx3qmUg7Imgq5i8ICrNdk/img.png&quot; data-alt=&quot;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ngX0c/dJMcafLN9oE/fvx3qmUg7Imgq5i8ICrNdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FngX0c%2FdJMcafLN9oE%2Ffvx3qmUg7Imgq5i8ICrNdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;328&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2단계 -Tokenizer&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Character Filter가 전처리한 문자 스트림을 받아 검색의 기본 단위인 &lt;b&gt;토큰&lt;/b&gt;으로 분리한다. 텍스트&amp;nbsp;분석&amp;nbsp;과정&amp;nbsp;중&amp;nbsp;&lt;b&gt;가장&amp;nbsp;큰&amp;nbsp;영향을&amp;nbsp;끼치는&amp;nbsp;단계&lt;/b&gt;이다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crhQmg/dJMcaiaHjpT/UaAWOxADSx6xAEzkbmC8mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crhQmg/dJMcaiaHjpT/UaAWOxADSx6xAEzkbmC8mK/img.png&quot; data-alt=&quot;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crhQmg/dJMcaiaHjpT/UaAWOxADSx6xAEzkbmC8mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrhQmg%2FdJMcaiaHjpT%2FUaAWOxADSx6xAEzkbmC8mK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;408&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분석 과정 중에서 토크나이저는 딱 한 개만 사용 가능하다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;standard (공백 기준, 특수 문자 제거), letter (공백,기호,숫자), whitespace(오직 공백), 한글 형태소 분석기(nori) 등&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 토크나이저 형태는 이 블로그에 잘 정리되어있다: &lt;a href=&quot;https://m.blog.naver.com/shino1025/222313469941&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://m.blog.naver.com/shino1025/222313469941&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;커스텀 토크나이저를 만드는 것도 가능하다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3단계 -Token Filter&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tokenizer가 생성한 토큰 스트림을 입력받아 토큰 자체를 수정/추가/제거하는 후처리 (post processing)을 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/82Nzy/dJMcacBysv9/r03b9cnkkv1KWpuXVIVgF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/82Nzy/dJMcacBysv9/r03b9cnkkv1KWpuXVIVgF0/img.png&quot; data-alt=&quot;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/82Nzy/dJMcacBysv9/r03b9cnkkv1KWpuXVIVgF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F82Nzy%2FdJMcacBysv9%2Fr03b9cnkkv1KWpuXVIVgF0%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;573&quot; height=&quot;406&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약에 사용하가 &quot;셔츠&quot;라고 검색했지만 판매자는 상품명을 &quot;shirt&quot;라고 영어로 등록했을 수도 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 개발자가 직접 사전을 넣어주어야 한다. 현재 Token Filter 단계에서 synonym 필터를 끼워 넣고, 개발자가 설정 파일에 셔츠, shirt라고 적어둔다.&lt;/li&gt;
&lt;li&gt;그러면 만약에 [&quot;블루&quot;, &quot;셔츠&quot;] 라는 토크나이저 데이터가 들어올 때, 토큰 필터는 &quot;셔츠&quot;랑 &quot;shirt&quot;가 동의어로 인식해서 [&quot;블루&quot;, &quot;셔츠&quot;, &quot;shirt&quot;] 로 토큰을 추가한다. 그 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;결과 인덱스에 &quot;shirt'도 같이 저장된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;이제 사용자가 &quot;shirt&quot;로 검색해도 &quot;블루 셔츠&quot;가 검색된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 결과를 저장하는 것만 남았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;역색인 저장&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;낱말이나 숫자와 같은 내용물로부터의 매핑 정보를 데이터베이스 파일의 특정 지점이나 문서 또는 문서 집합 안에 저장하는 색인 데이터 구조다. (포스팅 1에서 잠깐 다뤘다 - &lt;a href=&quot;https://leejunkim.tistory.com/68&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leejunkim.tistory.com/68&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;토큰들을 역색인을 하면, 단어를 기준으로 문서 아이디를 배열 형태로 가지고 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXGadG/dJMcacVQKqV/IjDNZJLemxHvDGMLdqzLZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXGadG/dJMcacVQKqV/IjDNZJLemxHvDGMLdqzLZK/img.png&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;97&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.5318%; margin-right: 10px;&quot; data-widthpercent=&quot;50.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXGadG/dJMcacVQKqV/IjDNZJLemxHvDGMLdqzLZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXGadG%2FdJMcacVQKqV%2FIjDNZJLemxHvDGMLdqzLZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;97&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpewPR/dJMcaacEZib/Fvk0OxYt0UDeqLeYpCiw1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpewPR/dJMcaacEZib/Fvk0OxYt0UDeqLeYpCiw1k/img.png&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;98&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.3054%;&quot; data-widthpercent=&quot;49.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpewPR/dJMcaacEZib/Fvk0OxYt0UDeqLeYpCiw1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpewPR%2FdJMcaacEZib%2FFvk0OxYt0UDeqLeYpCiw1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l26Zs/dJMcagDXBMW/0AyGXkz1QUI7oTYm7DEky1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l26Zs/dJMcagDXBMW/0AyGXkz1QUI7oTYm7DEky1/img.png&quot; data-alt=&quot;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l26Zs/dJMcagDXBMW/0AyGXkz1QUI7oTYm7DEky1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl26Zs%2FdJMcagDXBMW%2F0AyGXkz1QUI7oTYm7DEky1%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;525&quot; height=&quot;322&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 보이는 Term | Document 구조는 한 샤드 내부의 데이터 구조라고 생각하면 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검색이 이루어 지는 과정&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;영어 단어 &quot;shirts&quot;로 검색을 한다&lt;/li&gt;
&lt;li&gt;Term에서 shirts를 찾고, 문서 아이디 1,2를 찾는다&lt;/li&gt;
&lt;li&gt;아이디 1,2를 가진 문서들을 찾아서 반환한다&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;더 큰 시나리오로 이해하기&lt;/h2&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;데이터가 100만 개가 있다고 치고, 이 데이터를 한번에 관리하기 힘들어서 이걸 5개로 쪼갰다고 치자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&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-path-to-node=&quot;19,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문서 1~20만 -&amp;gt; &lt;b&gt;1번 샤드&lt;/b&gt; 안에 작은 역색인 테이블로 저장&lt;/li&gt;
&lt;li&gt;문서 21~40만 -&amp;gt; &lt;b&gt;2번 샤드&lt;/b&gt; 안에 작은 역색인 테이블로 저장&lt;/li&gt;
&lt;li&gt;... (이렇게 분산됨)&lt;/li&gt;
&lt;li&gt;그리고 이 샤드들은 여러 노드(컴퓨터)에 분산되어 저장된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검색 과정 (분산 처리):&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;19,1,1&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 &quot;shirts&quot;를 검색함&lt;/li&gt;
&lt;li&gt;요청을 받은 노드는 &lt;b&gt;모든 샤드&lt;/b&gt;에게 각자 가지고 있는 테이블 (역색인) 안에서 &quot;shirts&quot;찾으라는 명령을 내림
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1번 샤드의 테이블에는 1, 5번 문서에 있고&amp;nbsp;2번 샤드의 테이블엔 25번 문서에 있다는 걸 발견&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결과를 취합해서 사용자에게 1, 5, 25번 문서를 반환함&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/0r0-R8NhINs?si=EOxfe2-CEHH-J425&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[10분 테코톡] 다로의 Elasticsearch&lt;/a&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;a href=&quot;https://m.blog.naver.com/shino1025/222313469941&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Elasticsearch&amp;nbsp;입문]&amp;nbsp;토크나이저&amp;nbsp;-&amp;nbsp;Tokenizer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Cloud</category>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/69</guid>
      <comments>https://leejunkim.tistory.com/69#entry69comment</comments>
      <pubDate>Thu, 27 Nov 2025 11:00:37 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Elastisearch - 인프라 기본 개념 정리</title>
      <link>https://leejunkim.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;모두의 플리 프로젝트를 하는 도중 Elastisearch를 사용해서 고도화하는 부분을 맡게 되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨텐츠 검색 리팩토링 - Elastisearch의 Full Text Search 기능을 활용하여 개선&lt;/li&gt;
&lt;li&gt;Elastisearch 배포 - Docker을 사용하여 로컬 환경에 구축하거나 AWS OpenSearch 서비스를 활용&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;h2 data-ke-size=&quot;size26&quot;&gt;Elastisearch&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터가 json 문서 (document) 형태로 저장되는 분산형 NoSQL 데이터베이스&lt;/li&gt;
&lt;li&gt;수평적 확장이 가능하고 데이터베이스를 여러 &lt;b&gt;서버 (노드)&lt;/b&gt;에서 실행할 수 있음&lt;/li&gt;
&lt;li&gt;텍스트,&amp;nbsp;숫자,&amp;nbsp;지리&amp;nbsp;공간(Geo-spatial)&amp;nbsp;데이터,&amp;nbsp;IP&amp;nbsp;주소&amp;nbsp;등&amp;nbsp;다양한&amp;nbsp;데이터&amp;nbsp;타입을&amp;nbsp;지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스(Indexes), 문서 (Document)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 언급했듯이, Elasticsearch는 문서형 데이터 베이스다 (NoSql).&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;611&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cODo6T/dJMcaf56Joj/znkjf7QTPD8ecEkEe6IiSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cODo6T/dJMcaf56Joj/znkjf7QTPD8ecEkEe6IiSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cODo6T/dJMcaf56Joj/znkjf7QTPD8ecEkEe6IiSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcODo6T%2FdJMcaf56Joj%2Fznkjf7QTPD8ecEkEe6IiSK%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;995&quot; height=&quot;611&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;611&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인덱스 (Index)&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;nbsp;&lt;/li&gt;
&lt;li&gt;하나 이상의 물리적 &lt;b&gt;샤드&lt;/b&gt;들의 논리적 그룹&lt;/li&gt;
&lt;li&gt;예) Employee라는 전체 데이터&lt;/li&gt;
&lt;li&gt;테이블 같은 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문서 (Document)&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;예) {&quot;name&quot;:&quot;John&quot;, &quot;location&quot;:&quot;Mumbai&quot;, &quot;Age&quot;:24, &quot;Email&quot;:&quot;abc@gmail.com&quot;}&lt;/li&gt;
&lt;li&gt;레코드 같은 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;장점: JSON을 색인(삽입) 하면 ES는 문서를 &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;gt; 불변이여서 Lock 메커니즘이 없고 높은 수준의 동시 읽기 성능 제공 + 여러 스레드가 동기에 동일한 문서를 읽을 때 데이터 충돌 발생할 위험도 없음&lt;/li&gt;
&lt;li&gt;효율적인 시스템 캐시 활용 -&amp;gt; 오랫동안 데이터가 변하지 않아 OS 캐시와 ES 내부 데이터 구조 캐시를 오랫동안 재활용하는 것이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단잠: 높은 쓰기 비용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UPDATE시 필드 하나만 바꾸는 것이 아닌 문서 전체를 삭제하고 새로운 문서로 재색인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Forward VS Inverted Index&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4Ltvk/dJMb99LAse4/pVtjQl5BkOaqeW2EogRnK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4Ltvk/dJMb99LAse4/pVtjQl5BkOaqeW2EogRnK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4Ltvk/dJMb99LAse4/pVtjQl5BkOaqeW2EogRnK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4Ltvk%2FdJMb99LAse4%2FpVtjQl5BkOaqeW2EogRnK0%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;576&quot; height=&quot;324&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 문자가 그대로 검색을 위해 저장되는 형태다 -&amp;gt; 그래서 쿼리 속도가 매우 빠르다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;샤드 (Shard)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC2fCK/dJMcafE2nnH/Wg006uZNkJOI8b87GtTKj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC2fCK/dJMcafE2nnH/Wg006uZNkJOI8b87GtTKj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC2fCK/dJMcafE2nnH/Wg006uZNkJOI8b87GtTKj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC2fCK%2FdJMcafE2nnH%2FWg006uZNkJOI8b87GtTKj0%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;614&quot; height=&quot;202&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 시스템에서 데이터를 수평으로 분할 (partitioning)하여 저장하는 가장 작은 물리적 단위
&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;/li&gt;
&lt;li&gt;위에서 언급했듯이, Index는 하나 이상 샤드로 이루어져 있음&lt;/li&gt;
&lt;li&gt;샤드의 종류
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프라이머리(Primary) 샤드 - &lt;/b&gt;원본 데이터가 저장되는 샤드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;레플리카(Replica) 샤드 - &lt;/b&gt;데이터 이중화(Redundancy)와 검색 쿼리 처리를 위한 복제본&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;샤드 하나는 그 자체로 완전한 기능을 갖춘 루씬(Lucene) 검색 엔진임!
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Elasticsearch는 Java로 작성된 Apache Lucene 라이브러리를 사용&lt;/li&gt;
&lt;li&gt;하나의 샤드 안에는 독자적인 &lt;b&gt;역색인(Inverted Index)&lt;/b&gt; 구조, 문서 저장소, 검색 로직이 모두 들어있음&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;li&gt;Elasticsearch가 클러스터 환경에서 데이터를 관리할 때 이동시키거나 복제하는 단위가 바로 샤드다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문서(Document)가&amp;nbsp;저장될&amp;nbsp;때,&amp;nbsp;Elasticsearch는&amp;nbsp;문서&amp;nbsp;ID의&amp;nbsp;해시값을&amp;nbsp;계산하여&amp;nbsp;어떤&amp;nbsp;샤드에&amp;nbsp;저장할지&amp;nbsp;결정(Routing)함&amp;nbsp;(예:&amp;nbsp;shard_num&amp;nbsp;=&amp;nbsp;hash(_id)&amp;nbsp;%&amp;nbsp;total_primary_shards)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;사용자가 검색 요청을 보내면, Elasticsearch는 이 요청을 해당 인덱스를 구성하는 모든 샤드에 병렬로 전송한다. 각 샤드는 자신이 가진 데이터 내에서 독립적으로 검색을 수행하고, 그 결과를 반환한다.&lt;/b&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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qAAxe/dJMcagYfeWW/GozMP0eoxOvwPTNZkNKhak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qAAxe/dJMcagYfeWW/GozMP0eoxOvwPTNZkNKhak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qAAxe/dJMcagYfeWW/GozMP0eoxOvwPTNZkNKhak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqAAxe%2FdJMcagYfeWW%2FGozMP0eoxOvwPTNZkNKhak%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;682&quot; height=&quot;317&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;위 예시처럼 노드가 2대 있고 하나의 인덱스에 샤드게 3개 있다면, Elasticsearch는 이 샤드들을 각 노드에 분배해준다.&lt;/li&gt;
&lt;li&gt;이때 유저가 이 cluster에 query를 날린다면, 두 노드 다 이 쿼리를 병행으로 받고 결과를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;수평 확장과 Cluster&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cluster
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ES에서 사용되는 가장 큰 단위, 여러대의 서버를 가지고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Node
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ES를 실행하는 컴퓨터(서버) 한 대&lt;/li&gt;
&lt;li&gt;샤드들을 가지고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시 1: Product 레코드가 90개가 있고 샤드를 3으로 나눌 때, 인덱스가 저장될 때 3등분 해서 저장이 됨&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cffAcw/dJMb99Y7Nd8/AhZXkFxGkvBU1tVqDFPLkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cffAcw/dJMb99Y7Nd8/AhZXkFxGkvBU1tVqDFPLkK/img.png&quot; data-alt=&quot;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cffAcw/dJMb99Y7Nd8/AhZXkFxGkvBU1tVqDFPLkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcffAcw%2FdJMb99Y7Nd8%2FAhZXkFxGkvBU1tVqDFPLkK%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;603&quot; height=&quot;317&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 서버가 여러개이기 때문에, 검색을 할 때 한번에 90건을 검색하는게 아니라 각각 30건을 검색하기 때문에 검색 속도가 빠르다!&lt;/li&gt;
&lt;li&gt;여기서 P는 원본 데이터를 저장하는 Primary shard다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;예시 2: Product 레코드 90개와 샤드 3, 그리고 User&lt;/h4&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxMPln/dJMcabCDHyB/5esXB6nNEQGqbprIhDDSWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxMPln/dJMcabCDHyB/5esXB6nNEQGqbprIhDDSWK/img.png&quot; data-alt=&quot;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxMPln/dJMcabCDHyB/5esXB6nNEQGqbprIhDDSWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxMPln%2FdJMcabCDHyB%2F5esXB6nNEQGqbprIhDDSWK%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;760&quot; height=&quot;394&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[10분 테크톡] 다로의 Elasticsearch - https://youtu.be/0r0-R8NhINs?si=g5YVVlwOVGTQOUXa&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Product의 index부터 샤드에 저장되기 시작
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시 1와 같이 P1-P3 (primary shard)가 각각 A-C 노드에 저장됨&amp;nbsp;&lt;/li&gt;
&lt;li&gt;그 이후에 R1-R3 (replica shard)가 각각 A-C 노드에 저장됨
&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;/li&gt;
&lt;li&gt;Users도 저장하기 시작
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;User 같은 경우 노드B에 primary shard, 노드 C에 replica shard가 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스 템플릿 (Index&amp;nbsp;Template)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스가 생성될 때 적용되는 설정값이다 (blueprint)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;샤드/레플리카의 갯수, 우선순위 등 설정 포함&lt;/li&gt;
&lt;li&gt;&lt;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;동적 매핑 (dynamic) - 데이터가 수집되는 시점에 스키마를 유추하고 생성 (Schema on Write)&lt;/li&gt;
&lt;li&gt;엄격 매핑 (strict) - RDBMS처럼 미리 정의된 매핑 속성을 따르지 않는 문서가 들어오면 거부(reject)하도록 설정 가능&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;pre id=&quot;code_1764145080238&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PUT _index_template/template_1
{
  &quot;index_patterns&quot;: [&quot;te*&quot;, &quot;bar*&quot;], // 이 패턴에 맞는 인덱스 생성 시 템플릿 적용
  &quot;template&quot;: {
    &quot;settings&quot;: {
      &quot;number_of_shards&quot;: 1
    },
    &quot;mappings&quot;: {
      &quot;_source&quot;: {
        &quot;enabled&quot;: true
      },
      &quot;properties&quot;: {
        &quot;host_name&quot;: {
          &quot;type&quot;: &quot;keyword&quot;
        },
        &quot;created_at&quot;: {
          &quot;type&quot;: &quot;date&quot;,
          &quot;format&quot;: &quot;EEE MMM dd HH:mm:ss Z yyyy&quot;
        }
      }
    },
    &quot;aliases&quot;: {
      &quot;mydata&quot;: { }
    }
  },
  &quot;priority&quot;: 500, // 우선순위
  &quot;composed_of&quot;: [&quot;component_template1&quot;, &quot;runtime_component_template&quot;], 
  &quot;version&quot;: 3,
  &quot;_meta&quot;: {
    &quot;description&quot;: &quot;my custom&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스&amp;nbsp;별칭&amp;nbsp;(Index&amp;nbsp;Alias)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M71Ua/dJMcaacEKbk/QABMz5wnMovwjGZgyZ2uOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M71Ua/dJMcaacEKbk/QABMz5wnMovwjGZgyZ2uOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M71Ua/dJMcaacEKbk/QABMz5wnMovwjGZgyZ2uOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM71Ua%2FdJMcaacEKbk%2FQABMz5wnMovwjGZgyZ2uOk%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;680&quot; height=&quot;253&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/figure&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;별칭을 사용하여 인덱스 그룹에 문서를 삽입할 수 있음 (쓰기 인덱스 (Write Index)로 지정된 인덱스에만 문서가 저장됨&lt;/li&gt;
&lt;li&gt;패턴 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 인덱스 패턴 (예: logs-*)를 따르는 모든 인덱스를 포함하도록 별칭을 지정할 수 있음&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;pre id=&quot;code_1764145176947&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST _aliases
{
  &quot;actions&quot;: [
    {
      &quot;add&quot;: {
        &quot;index&quot;: &quot;logs-*&quot;,
        &quot;alias&quot;: &quot;logs&quot;
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;활용법 - 인덱스 수명 주기 관리(ILM)와 함께 사용하여, 특정 기간이나 크기 임계값에 도달하면 데이터를 새로운 인덱스로 롤오버(Rollover) 하도록 자동화할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LqXj1oC1FH0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=LqXj1oC1FH0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nagasudhir.blogspot.com/2023/08/elasticsearch-database-concepts.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nagasudhir.blogspot.com/2023/08/elasticsearch-database-concepts.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;제미나이 참고&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/0r0-R8NhINs?si=Ihd-YIqDlB6Z4UJ5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;테코톡 - 다로의 Elasticsearch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Cloud</category>
      <author>leejunkim</author>
      <guid isPermaLink="true">https://leejunkim.tistory.com/68</guid>
      <comments>https://leejunkim.tistory.com/68#entry68comment</comments>
      <pubDate>Wed, 26 Nov 2025 17:41:56 +0900</pubDate>
    </item>
  </channel>
</rss>