<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>mopil devlog</title>
    <link>https://mopil.tistory.com/</link>
    <description>어제의 나보다 성장하기</description>
    <language>ko</language>
    <pubDate>Tue, 28 Jul 2026 18:33:34 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>mopil</managingEditor>
    <image>
      <title>mopil devlog</title>
      <url>https://tistory1.daumcdn.net/tistory/4812788/attach/471674414ad046e59121038ebcced2eb</url>
      <link>https://mopil.tistory.com</link>
    </image>
    <item>
      <title>2025년 회고</title>
      <link>https://mopil.tistory.com/entry/2025%EB%85%84-%ED%9A%8C%EA%B3%A0</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;2024년 회고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mopil.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mopil.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1764349192563&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;2024년 회고&quot; data-og-description=&quot;2023년도 회고https://mopil.tistory.com/174&amp;nbsp;2023년 회고# 1월 친구들과 해커톤에 참여했다. https://mopil.tistory.com/132 SPARCS 2023 StartUp Hackathon 참여 후기 https://hackathon.sparcs.org/ SPARCS 2023 StartUp Hackathon 우리의 생&quot; data-og-host=&quot;mopil.tistory.com&quot; data-og-source-url=&quot;https://mopil.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0&quot; data-og-url=&quot;https://mopil.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/f5Tsy/hyZO3H2LGU/kXe1b8J9CHKSoQ5PnKKko0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c6IY2s/hyZO5FRmgl/L7jTKxEliCkChHLC1YFka1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/6udkJ/hyZOF8AYoq/1THoxsgKkqn80DMAduwEZ0/img.png?width=901&amp;amp;height=520&amp;amp;face=0_0_901_520&quot;&gt;&lt;a href=&quot;https://mopil.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mopil.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/f5Tsy/hyZO3H2LGU/kXe1b8J9CHKSoQ5PnKKko0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c6IY2s/hyZO5FRmgl/L7jTKxEliCkChHLC1YFka1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/6udkJ/hyZOF8AYoq/1THoxsgKkqn80DMAduwEZ0/img.png?width=901&amp;amp;height=520&amp;amp;face=0_0_901_520');&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;2024년 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2023년도 회고https://mopil.tistory.com/174&amp;nbsp;2023년 회고# 1월 친구들과 해커톤에 참여했다. https://mopil.tistory.com/132 SPARCS 2023 StartUp Hackathon 참여 후기 https://hackathon.sparcs.org/ SPARCS 2023 StartUp Hackathon 우리의 생&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mopil.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;# 2025년도 목표 달성?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;423&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DDZEm/dJMcafLOLZ4/NAyiJm9rRhHw1cMUwLmA5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DDZEm/dJMcafLOLZ4/NAyiJm9rRhHw1cMUwLmA5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DDZEm/dJMcafLOLZ4/NAyiJm9rRhHw1cMUwLmA5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDDZEm%2FdJMcafLOLZ4%2FNAyiJm9rRhHw1cMUwLmA5K%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;824&quot; height=&quot;423&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;423&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;1. 책읽기 (7권) =&amp;gt; 책은 몇 권 샀지만 다 읽지 못했다... 실패&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 해외여행 =&amp;gt; 가족, 친구 일본여행 성공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 더 프로답게 일하기 =&amp;gt; 목표를 너무 추상적으로 설정한 것 같은데 절반정도 잘 준수한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;# 일년을 되돌아보며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 1월~2월&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;/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;# 3월&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어드민 기능 성능개선을 경험&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mopil.tistory.com/entry/API-%EC%84%B1%EB%8A%A5-50%EB%B0%B0-%EA%B0%9C%EC%84%A0%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mopil.tistory.com/entry/API-%EC%84%B1%EB%8A%A5-50%EB%B0%B0-%EA%B0%9C%EC%84%A0%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1764349559816&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;API 성능 50배 개선기&quot; data-og-description=&quot;오늘은 내부 임직원이 사용하는 어드민 제품의 목록조회 기능을 평균 5~8초에서 100ms 미만 응답속도를 가지도록 성능 개선한 이야기를 공유해볼까 한다.&amp;nbsp;이야기에 앞서, 먼저 해당 제품으로 이&quot; data-og-host=&quot;mopil.tistory.com&quot; data-og-source-url=&quot;https://mopil.tistory.com/entry/API-%EC%84%B1%EB%8A%A5-50%EB%B0%B0-%EA%B0%9C%EC%84%A0%EA%B8%B0&quot; data-og-url=&quot;https://mopil.tistory.com/entry/API-%EC%84%B1%EB%8A%A5-50%EB%B0%B0-%EA%B0%9C%EC%84%A0%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cfySDJ/hyZOthfxU1/PyxOnkceGZPXbPkkfP5Pkk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cqnlhD/hyZOkdADbo/cvS2KDK1RGkZRnq0qpJcA1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cANBMV/hyZOORZvyp/h6QxhnoBm0nBNDxp523V71/img.png?width=790&amp;amp;height=825&amp;amp;face=0_0_790_825&quot;&gt;&lt;a href=&quot;https://mopil.tistory.com/entry/API-%EC%84%B1%EB%8A%A5-50%EB%B0%B0-%EA%B0%9C%EC%84%A0%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mopil.tistory.com/entry/API-%EC%84%B1%EB%8A%A5-50%EB%B0%B0-%EA%B0%9C%EC%84%A0%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cfySDJ/hyZOthfxU1/PyxOnkceGZPXbPkkfP5Pkk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cqnlhD/hyZOkdADbo/cvS2KDK1RGkZRnq0qpJcA1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cANBMV/hyZOORZvyp/h6QxhnoBm0nBNDxp523V71/img.png?width=790&amp;amp;height=825&amp;amp;face=0_0_790_825');&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;API 성능 50배 개선기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 내부 임직원이 사용하는 어드민 제품의 목록조회 기능을 평균 5~8초에서 100ms 미만 응답속도를 가지도록 성능 개선한 이야기를 공유해볼까 한다.&amp;nbsp;이야기에 앞서, 먼저 해당 제품으로 이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mopil.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로 기억에 남는건 성능개선기 정도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;# 4월~8월&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갑자기 무슨 바람이 불었는지 장롱면허 탈출하고 차를사고 싶어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 운전연수 시작하고 렌트하면서... 차까지 계약했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 뭔가 운동 취미를 하나 가지고 싶어서 스쿼시 시작했다. 2개월정도 다니다가 테니스도 찍먹했는데 테니스가 더 재밌어서 테니스로 갈아탔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 9월&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 구의 자취방이 전세 2년 만료되어가서 회사와 가까운 선릉 근처로 이사했다. 구의집은 주차장이 없어서 멀리 월주차권을 사서 이용했었는데 주차가 되는 집으로 가고싶어서 이사했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;# 10월&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테니스 레슨 시작. 확실히 스쿼시보다 테니스가 재밌는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으론 파편화 되어있는 계좌개설 과정에 필요한 어떤 부분을 모듈화하는 작업을 하게 되었는데, 새로운 상품이 추가될 때 마다 유연하게 대처할 수 있도록 정책+전략 기반 아키텍처를 도입했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;# 11~12월&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접 참관만 들어가다가 인터뷰어 단계로 가는 프로그램을 신청해서 수련(?)을 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 서류검토 부터 의견 남기는걸 연습중인데 이게 생각보다 자극도 많이되고 공부도 많이 되는 것 같아서 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;# 회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년이 정말 빠르게 지나가는것 같다.&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;25년도는 개발자로써 성장보다는 여가생활 위주로 조금 집중했었던 것 같다. 26년도는 다시 역량강화를 목표로 집중해볼려고 한다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;# 26년도 목표&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년이라는 시간이 너무 빨리 지나가는 것 같았다. 그래서 매월 내가 무엇을 했는지 기록하고 before, after, TIL 등을 정리해보는 노력을 시작해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적인 의식적 수련또한 꾸준히 하면서 27년에는 새로운 회사를 도전할 수 있는 발판을 만들어보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>정리, 기록</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/197</guid>
      <comments>https://mopil.tistory.com/entry/2025%EB%85%84-%ED%9A%8C%EA%B3%A0#entry197comment</comments>
      <pubDate>Wed, 31 Dec 2025 03:30:17 +0900</pubDate>
    </item>
    <item>
      <title>JVM old gen이 우상향 하는 이슈 트러블슈팅</title>
      <link>https://mopil.tistory.com/entry/JVM-old-gen%EC%9D%B4-%EC%9A%B0%EC%83%81%ED%96%A5-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%8A%88-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어느 날 어드민 서버에서 Major GC가 발생했다는 소식을 접했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그라파나를 확인해 보니 JVM old gen 영역이 끊임없이 우상향 하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251219_002511854.jpg&quot; data-origin-width=&quot;4458&quot; data-origin-height=&quot;1638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD0Un6/dJMcabbHiyR/NYlVQ9xE29I0B45ZI1VYBk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD0Un6/dJMcabbHiyR/NYlVQ9xE29I0B45ZI1VYBk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD0Un6/dJMcabbHiyR/NYlVQ9xE29I0B45ZI1VYBk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbD0Un6%2FdJMcabbHiyR%2FNYlVQ9xE29I0B45ZI1VYBk%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;4458&quot; height=&quot;1638&quot; data-filename=&quot;KakaoTalk_20251219_002511854.jpg&quot; data-origin-width=&quot;4458&quot; data-origin-height=&quot;1638&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;서버의 max heap size는 2gb였고 rps 4~5정도 나오는 어드민 서버였는데 old gen이 이렇게 우상향 하고 있다는 것은 어딘가 메모리 누수가 있다는 것을 의미했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Major GC가 발생해도 힙을 많이 확보하지 못 하고 있어서 바로 힙덤프를 떠봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 힙덤프&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙덤프를 뜨면 hprof 확장자 바이너리 파일이 만들어지는데 eclipse MAT나 intelliJ로 이를 열어볼 수 있다.&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-filename=&quot;2.jpg&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dn30Jp/dJMcahpsy23/tEJnIbuvfQk7FYiIGo4Cwk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dn30Jp/dJMcahpsy23/tEJnIbuvfQk7FYiIGo4Cwk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dn30Jp/dJMcahpsy23/tEJnIbuvfQk7FYiIGo4Cwk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdn30Jp%2FdJMcahpsy23%2FtEJnIbuvfQk7FYiIGo4Cwk%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;2340&quot; height=&quot;626&quot; data-filename=&quot;2.jpg&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.jpg&quot; data-origin-width=&quot;3416&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cggEu3/dJMcah33cfJ/ftuBcOmZghJjwery3ZEmsk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cggEu3/dJMcah33cfJ/ftuBcOmZghJjwery3ZEmsk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cggEu3/dJMcah33cfJ/ftuBcOmZghJjwery3ZEmsk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcggEu3%2FdJMcah33cfJ%2FftuBcOmZghJjwery3ZEmsk%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;3416&quot; height=&quot;774&quot; data-filename=&quot;3.jpg&quot; data-origin-width=&quot;3416&quot; data-origin-height=&quot;774&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;중간에 배포를 해서 많이 차있는 상태에서 관찰하진 못 했지만, hibernate의 QueryPlanCache가 굉장히 많이 메모리를 잡아먹고 있음을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# QueryPlanCache&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리플랜캐시는 하이버네이트가 한 번 생성한 SQL을 다시 생성하지 않고 재활용할 수 있도록 내부적으로 hash-map에 저장해두는데, 그게 쿼리플랜캐시다.&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;queryPlanCache는 내부적으로 segments라는 버킷(해시맵)을 두고 그 안에 table이라는 또 다른 해시맵을 둬서 (2중 버킷 구조) table의 key로 어떤 쿼리를 캐싱해 뒀는지 확인 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.jpg&quot; data-origin-width=&quot;2744&quot; data-origin-height=&quot;1540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1AQVC/dJMcaaKCPny/MCjAyVKVg8EJVV67XH4Fkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1AQVC/dJMcaaKCPny/MCjAyVKVg8EJVV67XH4Fkk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1AQVC/dJMcaaKCPny/MCjAyVKVg8EJVV67XH4Fkk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1AQVC%2FdJMcaaKCPny%2FMCjAyVKVg8EJVV67XH4Fkk%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;2744&quot; height=&quot;1540&quot; data-filename=&quot;4.jpg&quot; data-origin-width=&quot;2744&quot; data-origin-height=&quot;1540&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;잘려서 잘 안보일 때는 value의 바이트를 열어보면 가로줄로 쭉 나오는데 이걸 보고 대충 추론할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 되는 부분은 query-dsl로 작성된 어드민 벌크 조회 쿼리였는데, where 절에 동적 쿼리로 인해 너무 많은 쿼리플랜캐시가 생성되는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766072131231&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select *
from table
where (a = ? and b = ? and c = ? and d = ?) 
 or (a = ? and b = ? and c = ? and d = ?)
 
 ...
 
 select *
from table
where (a = ? and b = ? and c = ? and d = ?) 
 or (a = ? and b = ? and c = ? and d = ?)
 or (a = ? and b = ? and c = ? and d = ?)
 
...
=&amp;gt; or 동적 조건에 따라서 계속 쿼리가 생성 됨&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;join과 추가 조건으로 실로는 더 복잡했지만 대강 이런 형식이었다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;# 접근&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리적으로 동일한 쿼리에 where 절에 따라 쿼리플랜캐시가 무분별하게 많이 생성되는 게 원인이므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 플랜캐시를 생성 안 하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 플랜캐시를 적당히 생성하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순으로 접근해 보았다.&lt;/p&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;0. 쿼리플랜캐시 전역 off?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 rps 낮은 어드민 서버라고 하지만 이건 너무 무식한 방법이라고 생각해서 기각&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷하게 query-dsl로 이미 잘 짜인 로직을 native-query로 바꾸는 것도 고려하지 않았다.&lt;/p&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;1. 특정 쿼리만 off 할 수 있나?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리플랜캐시 옵션을 특정 쿼리만 off 할 수 있는지 조사해 보았다. 하이버네이트 6.3부터 지원하는 기능을 활용하면 쿼리별로 off가 가능하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/hibernate/hibernate-orm/blob/77a53bb28a4d38a72cdc96788299e4910be1425e/hibernate-core/src/main/java/org/hibernate/jpa/HibernateHints.java#L264&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/hibernate/hibernate-orm/blob/77a53bb28a4d38a72cdc96788299e4910be1425e/hibernate-core/src/main/java/org/hibernate/jpa/HibernateHints.java#L264&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766072437087&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;hibernate-orm/hibernate-core/src/main/java/org/hibernate/jpa/HibernateHints.java at 77a53bb28a4d38a72cdc96788299e4910be1425e &amp;middot; &quot; data-og-description=&quot;Idiomatic persistence for Java and relational databases - hibernate/hibernate-orm&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/hibernate/hibernate-orm/blob/77a53bb28a4d38a72cdc96788299e4910be1425e/hibernate-core/src/main/java/org/hibernate/jpa/HibernateHints.java#L264&quot; data-og-url=&quot;https://github.com/hibernate/hibernate-orm/blob/77a53bb28a4d38a72cdc96788299e4910be1425e/hibernate-core/src/main/java/org/hibernate/jpa/HibernateHints.java&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cVvtwe/hyZOGnPmfN/lg6GarS3g51FcN6cz72PG0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/uVjL9/hyZP83RSfw/63bheGoc3tkdvsxLwbMrY0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/hibernate/hibernate-orm/blob/77a53bb28a4d38a72cdc96788299e4910be1425e/hibernate-core/src/main/java/org/hibernate/jpa/HibernateHints.java#L264&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/hibernate/hibernate-orm/blob/77a53bb28a4d38a72cdc96788299e4910be1425e/hibernate-core/src/main/java/org/hibernate/jpa/HibernateHints.java#L264&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cVvtwe/hyZOGnPmfN/lg6GarS3g51FcN6cz72PG0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/uVjL9/hyZP83RSfw/63bheGoc3tkdvsxLwbMrY0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;hibernate-orm/hibernate-core/src/main/java/org/hibernate/jpa/HibernateHints.java at 77a53bb28a4d38a72cdc96788299e4910be1425e &amp;middot;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Idiomatic persistence for Java and relational databases - hibernate/hibernate-orm&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766072530642&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hibernate.query.plan.cacheable&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;우리가 쓰는 하이버네이트 버전은 6.1이라서 당장 적용은 힘들어 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 로직 바꾸기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 쿼리가 복잡하게 join이 되어있고 임시테이블이나 비즈니스 로직으로 해결하는 건 제약이 커 보였다.&lt;/p&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;4. 그럼 적당히 만들자! - in_clause_parameter_padding&amp;nbsp;설정 및 in 절로 리팩토링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이버네이트는 in_cluase_parameter_padding: true/false 옵션을 제공하는데, where in 절에 들어오는 파라미터 수를 2의 제곱수로 고정시켜 쿼리플랜캐시 히트율을 높이는 튜닝 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 해당 옵션이 false일 경우&lt;/p&gt;
&lt;pre id=&quot;code_1766073002949&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select *
from table
where a in (?, ?)

이것과

select *
from table
where a in (?, ?, ?)

이것은 서로 다른 쿼리로 취급함. 즉 in 절 파라미터 수에 따라서 n개 쿼리플랜 생성&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;옵션을 true로 설정하면&lt;/p&gt;
&lt;pre id=&quot;code_1766073071733&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select *
from table
where a in (?, ?)
혹은
a in (?, ?, ?, ?)
a in (?, ?, ?, ?, ?, ?, ?, ?)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 2의 제곱수만큼 만든다. 만약 파라미터가 3개인 경우는? &lt;i&gt;&lt;u&gt;마지막 파라미터를 동일하게 붙여 넣어 4개로 만들어서 교묘히 재사용할 수 있도록 한다.&lt;/u&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766073133597&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select *
from table
where a in (1,2,3,3)
=&amp;gt; 사실은 1,2,3 조회&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;우리 회사는 해당 옵션이 cloud config 서버에 공통으로 true로 적용되어 있음을 확인하였고, in 절로 리팩토링을 진행하였다.&lt;/p&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;5. 기타 옵션 조정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in_clause_padding_parameter 외에도 튜닝할 수 있는 옵션이 2개 더 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;hibernate.query.plan_parameter_metadata_max_size (default: 128) - 해시맵에 들어갈 수 있는 사이즈 수&lt;/span&gt; &lt;span&gt;hibernate.query.plan_cache_max_size (default: 2048) - 해시맵에 들어갈 수 있는 사이즈 수&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타데이터 옵션은 &lt;span&gt;ParameterMetadataImpl 해당 객체의 retianed size가 너무 크면 조정이 필요하고, QueryPlanCache는 hibernate.query.plan_cache_max_size 요 옵션으로 조절 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;힙덤프 분석 결과 쿼리플랜캐시가 문제였으므로 메타데이터는 조절하지 않고 플랜캐시 max size만 조절했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;최종적으로 2048 -&amp;gt; 48로 조절했는데, 근거는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 먼저 1024 절반으로 줄여봤는데 유의미한 효과는 보지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 트래픽도 적은 어드민 서버고 다른 복잡한 동적 쿼리들도 많아서 플랜 히트율이 생각보다 낮았다. 그래서 더 많이 줄여도 될 것으로 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. p95기준 쿼리플랜캐시 사이즈는 5mb였고 서버 힙사이즈는 2gb이므로 쿼리플랜캐시로 인한 old gen 점유율을 10% 정도 유지하려는 목표를 잡았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 결과 5mb * x = 200mb = 40 (2의 배수 48)로 설정&lt;/p&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;# 해결?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Major GC도 발생하지 않게 되었고 무지막지하게 우상향 하는 건 해결했으나, 그래도 어느 정도 증가하다가 수렴하는 현상이 다시 관찰 됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6.jpg&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NrY7k/dJMcaaw5YD3/5L4y19ryE3YxpN3zQwZulk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NrY7k/dJMcaaw5YD3/5L4y19ryE3YxpN3zQwZulk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NrY7k/dJMcaaw5YD3/5L4y19ryE3YxpN3zQwZulk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNrY7k%2FdJMcaaw5YD3%2F5L4y19ryE3YxpN3zQwZulk%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;1298&quot; height=&quot;614&quot; data-filename=&quot;6.jpg&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;614&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;힙덤프를 다시 해보니 역시 지난번 쿼리와 쿼리플랜캐시가 문제였고, 상한선은 많이 내려왔지만 그래도 1.25gb 정도라서 뭔가 더 줄여야 할 것 같았다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;# 접근 2&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙덤프를 분석하다 보니 이상하게 쿼리들의 padding 옵션이 적용되지 않음을 발견했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 재현해 보니 padding 옵션이 true임에도 2의 배수로 생성되고 있지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;# 또 다른 문제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in 절로 리팩토링을 진행할 때 단일 칼럼 in 절이 아닌, multi-column in 절로 변경했는데, 알고 보니 이 문법은 하이버네이트 padding 옵션이 적용이 되지 않는다고 한다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 in 절처럼 보이지만 내부적으로는 아예 다르게 동작하고, 하이버네이트는 이를 미지원한 다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(ansi 정식 명칭은 row value constructor syntax라고 한다고 한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기본적으로 하이버네이트가 제공하는 padding기능을 multi-column도 가능하게끔 커스텀으로 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 차이점은 2의 배수가 아닌 고정된 멀티칼럼(튜플이라고 칭함)으로 반드시 패딩 되도록 로직을 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766074500567&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select *
from table
where (a,b,c,d) in (?,?,?,?), (?,?,?,?)... (?,?,?,?) &amp;lt;- 항상 고정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 값은 별도 로그를 통해 평균 몇 개의 파라미터가 들어오는지 집계하여 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 값 보다 적은 파라미터면 항상 고정수치로 패딩 하였고, 그 이상이면 청킹 하여 호출될 수 있도록 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 적용하니 이제 쿼리플랜캐시가 old gen에서 많이 잡아먹는 이슈는 해결할 수 있었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 후기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 재발방지를 위해 팀원들과 일단은 멀티칼럼 in 절 로직을 구성할 때 잘 리뷰하자는 컨벤션을 싱크 하였고, 나중에 버전업을 하게 되면 그냥 옵션을 끄는 방향으로 일단 정리했다.&lt;/p&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;</description>
      <category>정리, 기록</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/198</guid>
      <comments>https://mopil.tistory.com/entry/JVM-old-gen%EC%9D%B4-%EC%9A%B0%EC%83%81%ED%96%A5-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%8A%88-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85#entry198comment</comments>
      <pubDate>Thu, 25 Dec 2025 03:11:40 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] TTL 동작 원리 살펴보기</title>
      <link>https://mopil.tistory.com/entry/Redis-TTL-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Redis에는 TTL (Time-To-Leave)을 expire 명령어를 통해 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 원리로 이를 가능케 하는지 궁금해서 좀 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 홈페이지 (&lt;a href=&quot;https://redis.io/docs/latest/commands/expire/#how-redis-expires-keys&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://redis.io/docs/latest/commands/expire/#how-redis-expires-keys&lt;/a&gt;)에 의하면 Passive way, Active way 2가지 방식으로 동작한다고 한다.&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;1132&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cD8SGi/dJMcadtEMui/kjCpppx6k8LDyZHNVhGwb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cD8SGi/dJMcadtEMui/kjCpppx6k8LDyZHNVhGwb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cD8SGi/dJMcadtEMui/kjCpppx6k8LDyZHNVhGwb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcD8SGi%2FdJMcadtEMui%2FkjCpppx6k8LDyZHNVhGwb1%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;1132&quot; height=&quot;217&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;217&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;Passive Way&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;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;Active Way&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;gt; 키를 full-scan 하면 메모리 부하가 올 수 있으니 그러지 않고 샘플링해서 제거함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 접근 시 체크 후 삭제 (Lazy Deletion) + GC 같은 스케줄링 클린업을 병행하는 구조였다.&lt;/p&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;a href=&quot;https://github.com/redis/redis/blob/f56e0115fbce73b012fe4ccce7368a67017b911c/src/expire.c#L287&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/redis/redis/blob/f56e0115fbce73b012fe4ccce7368a67017b911c/src/expire.c#L287&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1764347368359&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;redis/src/expire.c at f56e0115fbce73b012fe4ccce7368a67017b911c &amp;middot; redis/redis&quot; data-og-description=&quot;For developers, who are building real-time data-driven applications, Redis is the preferred, fastest, and most feature-rich cache, data structure server, and document and vector query engine. - red...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/redis/redis/blob/f56e0115fbce73b012fe4ccce7368a67017b911c/src/expire.c#L287&quot; data-og-url=&quot;https://github.com/redis/redis/blob/f56e0115fbce73b012fe4ccce7368a67017b911c/src/expire.c&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lqcWP/hyZO1i9Mmn/KJp7UX7sbD7eCNi8fDiKlK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/lAIYq/hyZOvznXN3/qFjTTIFhzmNSQfwj1W2hdk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/redis/redis/blob/f56e0115fbce73b012fe4ccce7368a67017b911c/src/expire.c#L287&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/redis/redis/blob/f56e0115fbce73b012fe4ccce7368a67017b911c/src/expire.c#L287&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lqcWP/hyZO1i9Mmn/KJp7UX7sbD7eCNi8fDiKlK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/lAIYq/hyZOvznXN3/qFjTTIFhzmNSQfwj1W2hdk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;redis/src/expire.c at f56e0115fbce73b012fe4ccce7368a67017b911c &amp;middot; redis/redis&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;For developers, who are building real-time data-driven applications, Redis is the preferred, fastest, and most feature-rich cache, data structure server, and document and vector query engine. - red...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p 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;632&quot; data-origin-height=&quot;353&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQezRS/dJMcafZlQFq/Pn48t1xFIcNBpnMt1Dkfy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQezRS/dJMcafZlQFq/Pn48t1xFIcNBpnMt1Dkfy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQezRS/dJMcafZlQFq/Pn48t1xFIcNBpnMt1Dkfy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQezRS%2FdJMcafZlQFq%2FPn48t1xFIcNBpnMt1Dkfy1%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;632&quot; height=&quot;353&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;353&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;activeExpireCycle 함수가 TTL 처리를 구현하는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자로 type을 받는데 이는 LAZY (passive) SLOW (active) 방식을 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 코드라 읽기 어려워서 ChatGPT로 수도코드로 변경해 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764347475133&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for each DB in server:
  if DB.expires dict is empty: continue

  loop up to max_cycles:
    sample = randomSample(DB.expires, SAMPLE_LIMIT)
    expired_keys = []
    for key in sample:
      if currentTime &amp;gt; DB.expires[key]:
        expired_keys.append(key)

    for key in expired_keys:
      deleteKey(DB, key)      // internal deletion
      propagateDelete(key)    // AOF / replication

    if ( expired_keys.count / sample.count ) &amp;lt; REDUCTION_THRESHOLD:
      break&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;레디스는 싱글스레드 기반이라 중간에 active way가 실행되면 gc처럼 stop-the-world가 걸릴 거라, 랜덤 샘플링하고 삭제하는 (즉 대기시간을 최소화하려는) 설계 철학을 엿볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis는 내부적으로 serverCron()이라는 함수를 통해서 주기적으로 수행해야 하는 작업들을 쓱 실행하는데, TTL 만료도 여기에 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본설정은 100ms마다 호출되는 거고 실행되는 작업(함수)마다 최대 수행시간이 정해져 있다. (이 주기는 하드코딩된 값은 아니고 변경할 수 있다고 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764347803013&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */&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;해당 코드를 보면 25로 하드코딩 되어있는데 이는 serverCron()을 실행하는 시간 동안 25%만 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;activeExpireCycle()을 사용할 수 있음을 의미한다. 디폴트 설정이면 100ms의 25%인 25ms동안만 최대 동작한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글스레드 기반에서 가장 최적의 메커니즘 같은데 확률적으로 샘플링해서 청소하기 때문에 정말 정확하게 TTL 만료 처리가 안 될 수도 있다.&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;567&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mirrO/dJMcacO5ENI/6MxXYP08Ln5YwykpaidIbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mirrO/dJMcacO5ENI/6MxXYP08Ln5YwykpaidIbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mirrO/dJMcacO5ENI/6MxXYP08Ln5YwykpaidIbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmirrO%2FdJMcacO5ENI%2F6MxXYP08Ln5YwykpaidIbk%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;567&quot; height=&quot;644&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;644&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>Backend/Database</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/196</guid>
      <comments>https://mopil.tistory.com/entry/Redis-TTL-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0#entry196comment</comments>
      <pubDate>Sat, 29 Nov 2025 01:49:01 +0900</pubDate>
    </item>
    <item>
      <title>API 성능 50배 개선기</title>
      <link>https://mopil.tistory.com/entry/API-%EC%84%B1%EB%8A%A5-50%EB%B0%B0-%EA%B0%9C%EC%84%A0%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 내부 임직원이 사용하는 어드민 제품의 목록조회 기능을 평균 5~8초에서 100ms 미만 응답속도를 가지도록 성능 개선한 이야기를 공유해볼까 한다.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 절차 중 일부 고객은 어드민에서 승인을 해야 계좌를 개설할 수 있는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 개선기는 계좌개설 과정에서 승인이 필요한 대상들을 불러오는 목록 리스트 성능 개선 이야기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 업무를 앞으로 '검토 업무'라고 부르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검토업무는 고객의 계좌개설 대기 시간을 최소화하기 위해 빠르게 검토가 이뤄져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검토가 필요하게 되면 검토건이 생성되고, 검토건은 검토대기, 검토 중, 검토완료 3가지 상태가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무의 핵심은 &lt;u&gt;검토대기의 있는 건들을 빠르게 검토완료하는 것이었다.&lt;/u&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1KVuM/btsMQCxm6B4/eX8PlaDIgsu0YTnoXkvG71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1KVuM/btsMQCxm6B4/eX8PlaDIgsu0YTnoXkvG71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1KVuM/btsMQCxm6B4/eX8PlaDIgsu0YTnoXkvG71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1KVuM%2FbtsMQCxm6B4%2FeX8PlaDIgsu0YTnoXkvG71%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;1075&quot; height=&quot;312&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;312&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;h3 data-ke-size=&quot;size23&quot;&gt;# 문제 파악&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 1초 이상 소요되는 API들은 slow alert을 보내도록 슬랙 설정을 해둔 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 날 모니터링 중, 특정 API들에서 지연이 발생하는 걸 감지했고 살펴보니 이 검토 업무 관련 API들이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검토업무는 속도가 중요하기 때문에, 그냥 지나칠 수 없었다.&lt;/p&gt;
&lt;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;보다 자세한 원인분석을 위해 메트릭을 상세히 수집하기로 결정했다. Spring actuator에서 제공하는 메트릭 어노테이션을 통해 의심 가는 API들에 쉽게 수행시간 메트릭을 심을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집한 메트릭은 프로메테우스를 통해 집계되어 그라파나를 통해 시각화하여 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3일 정도 수집하며 확인해 본 결과, 가장 중요한 &lt;u&gt;검토대기의 목록조회가 5~8초 정도 소요되고 있음을 파악할 수 있었다.&lt;/u&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;h3 data-ke-size=&quot;size23&quot;&gt;# 원인 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 되는 API들을 찾았으니, 원인 분석에 들어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 설명에 앞서 이해를 위해 검토건의 테이블 설계를 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRSPVH/btsMQHSNpyq/sY00QIt7VVJrjIogU5IzCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRSPVH/btsMQHSNpyq/sY00QIt7VVJrjIogU5IzCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRSPVH/btsMQHSNpyq/sY00QIt7VVJrjIogU5IzCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRSPVH%2FbtsMQHSNpyq%2FsY00QIt7VVJrjIogU5IzCk%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;560&quot; height=&quot;238&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객제출정보 테이블은 모든 계좌개설을 시도하는 고객에 대하여 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검토정보는 검토가 필요한 고객에 대해서만 생성되고 검토 중, 검토완료 상태정보를 저장하는 1:1 구조였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 검토정보 테이블은 검토를 진행했을 때만 생성이 됐었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;즉, 검토대기 건들을 불러오기 위해서는 고객제출정보와 검토정보를 left join 해서 불러오고 있었다.&lt;/u&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;left join을 하다 보니 인덱스를 이용하지 못하고 풀스캔을 하고 있는 걸 발견하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 1차 성능 개선 시도 - 쿼리튜닝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀스캔 하는 부분을 CTE랑 서브쿼리로 인덱스를 잘 태우도록 바꿔봤지만... 눈에 띄는 성능 개선효과를 볼 수 없었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KCCNo/btsMQDXbl1E/95Oz1hIVMOHr1vc00Orl9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KCCNo/btsMQDXbl1E/95Oz1hIVMOHr1vc00Orl9K/img.png&quot; data-alt=&quot;인덱스를 태워도 비용이 높은 걸 확인할 수 있었다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KCCNo/btsMQDXbl1E/95Oz1hIVMOHr1vc00Orl9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKCCNo%2FbtsMQDXbl1E%2F95Oz1hIVMOHr1vc00Orl9K%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;410&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;410&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;그래서 구조적으로 서브쿼리를 사용하지 않을 순 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(가장 비용이 높은 materialize view를 만드는 부분을 쿼리로 해소하기는 한계가 있었다.)&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;# 2차 개선 시도 - 구조 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 left join으로 인한 view생성 부분에서 모든 검토정보를 불러오는 게 비용이 크다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 인덱스를 보다 효율적으로 태울 수 있도록 left join 자체를 없애는 방식으로 구조를 개선해보고자 했다.&lt;/p&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;left join을 통해 검토대기건을 불러오는 이유는, 검토대기 상태일 때는 검토정보가 존재하지 않았고, 목록을 보여주기 위해선 검토정보 테이블 칼럼 정보들이 필요한 상태였기 때문이다.&lt;/p&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;u&gt;따라서 검토대기 상태에서도 검토정보가 생성되도록 구조를 변경하면 큰 개선효과를 가져올 수 있을 것 같았다.&lt;/u&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;그래서 우선 리스트 조회 쿼리를 inner join으로 바꾸고 실행계획을 확인했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcca2T/btsMQn1n15V/GIZrgpQGQJNntlhedZeeJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcca2T/btsMQn1n15V/GIZrgpQGQJNntlhedZeeJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcca2T/btsMQn1n15V/GIZrgpQGQJNntlhedZeeJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdcca2T%2FbtsMQn1n15V%2FGIZrgpQGQJNntlhedZeeJk%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;775&quot; height=&quot;324&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;775&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;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;하지만 inner join으로 바꾸는 작업은 기존 설계 구조를 많이 변경해야 하기 때문에, 변경범위가 대고객 부분까지 광범위하게 수정작업이 필요했다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;# 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 5~8초 걸리던 목록조회가 100ms 이내로 개선되었다!!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OO7d2/btsMOR38u8w/USyZK30PBTGDA2J3C2KM6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OO7d2/btsMOR38u8w/USyZK30PBTGDA2J3C2KM6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OO7d2/btsMOR38u8w/USyZK30PBTGDA2J3C2KM6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOO7d2%2FbtsMOR38u8w%2FUSyZK30PBTGDA2J3C2KM6K%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;790&quot; height=&quot;825&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제품을 사용하시는 어드민 분들에게도 좋은 피드백을 받을 수 있었다 ㅎㅎㅎ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;637&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o2fMg/btsMPrDJAWk/HcK6l3fkKjMySbsg2JAeGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o2fMg/btsMPrDJAWk/HcK6l3fkKjMySbsg2JAeGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o2fMg/btsMPrDJAWk/HcK6l3fkKjMySbsg2JAeGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo2fMg%2FbtsMPrDJAWk%2FHcK6l3fkKjMySbsg2JAeGk%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;1029&quot; height=&quot;637&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;637&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제품을 만드는 서버 개발자로서 이상징후를 간과하지 않고 빠르게 캐치, 개선하여 사용자에게 좋은 경험을 줄 수 있었던 보람 있는 경험이었다.&lt;/p&gt;</description>
      <category>정리, 기록</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/195</guid>
      <comments>https://mopil.tistory.com/entry/API-%EC%84%B1%EB%8A%A5-50%EB%B0%B0-%EA%B0%9C%EC%84%A0%EA%B8%B0#entry195comment</comments>
      <pubDate>Wed, 19 Mar 2025 23:33:38 +0900</pubDate>
    </item>
    <item>
      <title>2024년 회고</title>
      <link>https://mopil.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2023년도 회고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mopil.tistory.com/174&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mopil.tistory.com/174&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728786889474&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;2023년 회고&quot; data-og-description=&quot;# 1월 친구들과 해커톤에 참여했다. https://mopil.tistory.com/132 SPARCS 2023 StartUp Hackathon 참여 후기 https://hackathon.sparcs.org/ SPARCS 2023 StartUp Hackathon 우리의 생각이 한계까지 함께하는 곳, SPARCS 2023 StartUp Hack&quot; data-og-host=&quot;mopil.tistory.com&quot; data-og-source-url=&quot;https://mopil.tistory.com/174&quot; data-og-url=&quot;https://mopil.tistory.com/174&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bl0OWb/hyXeiiL0J4/7R9mCguEssrkqURZJVfgJk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://mopil.tistory.com/174&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mopil.tistory.com/174&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bl0OWb/hyXeiiL0J4/7R9mCguEssrkqURZJVfgJk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;2023년 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;# 1월 친구들과 해커톤에 참여했다. https://mopil.tistory.com/132 SPARCS 2023 StartUp Hackathon 참여 후기 https://hackathon.sparcs.org/ SPARCS 2023 StartUp Hackathon 우리의 생각이 한계까지 함께하는 곳, SPARCS 2023 StartUp Hack&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mopil.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 2023년도 목표 달성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;술술 사이드 프로젝트 출시 (3월 목표)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;7월 출시 완료&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;새로운 사이드 프로젝트...?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;청년모아 mini 사이드 프로젝트 합류&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;컨퍼런스, 해커톤 1회 이상 씩 참여하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;여럿 시도했지만 결국 둘 다 미달성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;오픈소스 기여 1회 이상 해보기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;미달성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각했던 것 보다 시간이 빠르게 흘렀던 한 해였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h3 data-ke-size=&quot;size23&quot;&gt;# 기술적 성장&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pCaIq/btsJ2Q7TJ6e/Xe9j61PL6NukrWZkrJxScK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pCaIq/btsJ2Q7TJ6e/Xe9j61PL6NukrWZkrJxScK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pCaIq/btsJ2Q7TJ6e/Xe9j61PL6NukrWZkrJxScK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpCaIq%2FbtsJ2Q7TJ6e%2FXe9j61PL6NukrWZkrJxScK%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;901&quot; height=&quot;520&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;520&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;내가 예상했던 성장 흐름은 hope 선 처럼 선형적 성장이었는데, 실제로 돌이켜보면 로그함수처럼 성장했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 23년도 처음 회사에 와서 마구마구 흡수했던 시절은 기술적으로 가장 크게 성장했다고 느껴졌고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무가 어느정도 익숙해진 24년도는 그 기울기가 원만해졌다고 느낀 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23년도는 내 기술적 천장을 가늠했고, 24년도는 천장을 찍기위해 살짝 놓쳤던 부분들을 채우는, 그런 과정이라고 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한건 기울기를 유지하면서도 디테일을 챙기는 것이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림으로 표현하자면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFFfqx/btsLxhvkx2b/wzwfpZL577etEHqv7jpVXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFFfqx/btsLxhvkx2b/wzwfpZL577etEHqv7jpVXk/img.png&quot; data-alt=&quot;2023년도는 매우 빠르게 기술적 경험과 습득, 성장 천장 만들기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFFfqx/btsLxhvkx2b/wzwfpZL577etEHqv7jpVXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFFfqx%2FbtsLxhvkx2b%2FwzwfpZL577etEHqv7jpVXk%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;435&quot; height=&quot;442&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2023년도는 매우 빠르게 기술적 경험과 습득, 성장 천장 만들기&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buutyb/btsLwLcpUEL/Aqk2BRXHzdk2MgrZkIkLT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buutyb/btsLwLcpUEL/Aqk2BRXHzdk2MgrZkIkLT1/img.png&quot; data-alt=&quot;2024년도는 천장을 기준으로 중간중간 디테일한 부분을 채우는&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buutyb/btsLwLcpUEL/Aqk2BRXHzdk2MgrZkIkLT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbuutyb%2FbtsLwLcpUEL%2FAqk2BRXHzdk2MgrZkIkLT1%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;435&quot; height=&quot;442&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2024년도는 천장을 기준으로 중간중간 디테일한 부분을 채우는&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;25년도에는 천장과 디테일을 조금씩 죽순마냥 같이 키워나가보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;---&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;24년도 하반기에는 라인에서 오신 새로운 서버 개발자분이 합류하셨는데, 항상 독특한 아이디어로 문제 해결 방안을 제시하시는게 인상깊다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 우리팀 개발문화도 더 활성화 된 것 같고 기술적으로 자극을 받고 있음을 느껴서 굉장히 즐겁다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 배울 사람이 곁에 있는게 성장에 참 많은 도움이 되는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 기술 이야기 이모저모&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;## 분산 트랜잭션 관리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSA 환경에서의 이종간 디비의 트랜잭션을 적절하게 핸들하지 못 해서 오류가나 트러블슈팅했던 경험이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에도 보상 트랜잭션이나 SAGA 패턴에 관심이 있어서 훑어본적이 있지만 실제로 겪어보니 더 주의있게 다뤄야할 부분이란걸 느낄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;## Redis로 이것저것 해보기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무를 하면서 올해는 특히 redis를 많이 활용했던 것 같다. 캐시나 분산락으로 많이 활용했고, redis 해시 자료구조를 활용한 노션의 같이보는 기능을 만들었던게 기억에 남는다.&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;138&quot; data-origin-height=&quot;55&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehcWGp/btsLw0N9cla/KkM5vg8lvFRkeG5sBmdi61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehcWGp/btsLw0N9cla/KkM5vg8lvFRkeG5sBmdi61/img.png&quot; data-alt=&quot;요런 비슷한 기능을 어드민에 만들었다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehcWGp/btsLw0N9cla/KkM5vg8lvFRkeG5sBmdi61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FehcWGp%2FbtsLw0N9cla%2FKkM5vg8lvFRkeG5sBmdi61%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;138&quot; height=&quot;55&quot; data-origin-width=&quot;138&quot; data-origin-height=&quot;55&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;TTL을 설정할 수 있고, 다양한 자료구조를 지원한다는 점이 참 매력적인 저장소인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;## 배포전략과 점진적 MIG&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 기능을 배포할 때 롤백 전략이나 실패시 대응방안을 고려하는 능력이 많이 길러진 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(하도 장애내고 쳐맞아서 생존적으로 배운것일 수도 있다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어찌보면 좀 더 보수적이게 된 것일 수도 있는데 안정적인 서비스를 위해서라면 속도보다는 안정성이 더 중요하니까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;## 새로운 기술들 찍먹&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀에 새로 합류하신 서버 개발자분이 기술적인 조예가 상당하셔서 많은 인사이트와 영감을 받고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Cloud 계열 기술들의 분산 추적, 메트릭 컨피그등에 대한 개념을 처음 접해서 적용해보았다.&lt;/p&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;QueryDSL 라이브러리가 유지보수가 되지 않아서 새로운 쿼리빌더 라이브러리를 알아보던 도중 라인에서 만든 kotlin-jdsl이라는 라이브러리를 알게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/line/kotlin-jdsl&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/line/kotlin-jdsl&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730013999564&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - line/kotlin-jdsl: Kotlin library that makes it easy to build and execute queries without generated metamodel&quot; data-og-description=&quot;Kotlin library that makes it easy to build and execute queries without generated metamodel - line/kotlin-jdsl&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/line/kotlin-jdsl&quot; data-og-url=&quot;https://github.com/line/kotlin-jdsl&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhYdpr/hyXlMKRbcX/uZ1Z9N6ykmKY2ivVplksv0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/beWNDF/hyXpxeaM5D/EJLqw15S3UTKK9lOHl5dXk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/line/kotlin-jdsl&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/line/kotlin-jdsl&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhYdpr/hyXlMKRbcX/uZ1Z9N6ykmKY2ivVplksv0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/beWNDF/hyXpxeaM5D/EJLqw15S3UTKK9lOHl5dXk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - line/kotlin-jdsl: Kotlin library that makes it easy to build and execute queries without generated metamodel&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin library that makes it easy to build and execute queries without generated metamodel - line/kotlin-jdsl&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QClass를 미리 빌드시점에 만들지 않아서 좋은 점 빼고는 아직 잘 모르겠다. 회사에서도 도입해서 쓰는 팀이 있던데 아직까지 QueryDSL과 차이점을 잘 모르겠다.&lt;/p&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.0버전도 새로나오고 업데이트도 꾸준히 하고 있는 점이 장점아닐까&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(QueryDSL은 페이징에 group by을 같이 못 쓰는 버그를 아직도 안 고치는 건지.. 못 고치는건지 방치하고 있다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여담으로 CTE를 자주 쓸 때가 있는데 이를 JPA나 dsl 라이브러리에서 지원을 안 해줘서 언넝 해줬으면 하는 바람이 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보니까 하이버네이트 최신 버전에서는 지원을 시작했다고 하는 것 같더라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;올해는 그래도 next로 간단한 퀴즈 앱이랑 어드민 페이지등을 만들어보면서 찍먹해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 css는 어렵다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;# 소프트 스킬과 마인드 셋&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사생활을 하면서 기술적 역량만큼 커뮤니케이션 능력(소프트스킬)이 매우 중요하다고 다시금 느끼고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 요구사항 분석은 매우 꼼꼼하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알더라도 더블체크 하고, 세부사항을 면밀히 파악해서 일을 2번 안 하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 요구사항이 불명확 할 땐 잠시 기다리기 or 제안하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발먼저 와다다 시작하지 말고 잠시 flow 정리나 요건 정리를 먼저하고 하는게 능률이 훨씬 좋았다.&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;개발자 입장에서의 관점을 제시하거나 커뮤니케이션으로 풀어서 안 해도 될 일이 됐던 적이 꽤 있었다. 먼저 소통해보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 부드럽게 피드백 하기&lt;/p&gt;
&lt;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;5. 항상 겸손하기&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;아는 것 보다 모르는 것에 더 적극적으로 임하고 &quot;가르치기&quot;보단 &quot;제안하기&quot; 화법으로 대화를 하도록 노력하자고 마음먹었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 조급해하지 않기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cs가 몰려오고 요청도 몰려오고 기한도 촉박할 때 항상 조급함때문에 스트레스를 많이 받았던 것 같다.&lt;/p&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;# 사이드 프로젝트에 관하여&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 꼭 회사일이 여유가 생기면 여가시간에 개발을 하는 이상한 병이 있다(...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;2023년도에 시작했던 사이드 프로젝트인 '술술'은 결과적으로 실패한 사이드 프로젝트라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패라고 생각하는 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. mvp가 너무 컸고, 인원수도 너무 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직장인들로 구성되다보니 시간투자가 절대적으로 많을 수가 없었고, 여가시간에 하는 '사이드'라는 생각에 취준생 때 하는 그 '사이드'와 절박함(?) 정도도 다들 달랐다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 각 팀원들의 목적이 sync되지 않았다.&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;3. 기획과 디자인이 완성되지 않은 상태에서 팀원을 모집했다.&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;너무 들뜬나머지(?) 각 분야 팀원들을 모아놓고 기획을 시작했다.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 사이드 프로젝트로 깨닫게 된 것들은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기획과 디자인이 명확해지면 개발자를 모으자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 순수 개발기간은 맥시멈 1달. (mvp는 진짜 제발 작고작고 제발 작게)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 정말 최소 인원으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;(여담이지만, 한 2~3번정도 해커톤 신청을 하고 팀도 꾸렸었는데 모두 잘 되지 못 했다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;# 나의 장점과 단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2년정도 실무 개발을 하면서 나의 개발적 장점과 단점을 어느정도 파악하였는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 개발을 매우 스피디하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 꼼꼼함이 부족하다. (특히 테스트)&lt;/p&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면 마냥 '빠르게 개발하기'가 장점인지도 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;1. cs 인입, 외부 변수를 충분히 고려하여 내가 가능한 일정 * 1.3정도로 개발일정 산정하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 요건확립을 최우선 적으로. 요건이 확립되더라도 2~3일정도 상호피드백을 거치면서 엣지케이스 체크하기. 개발은 천천히 꼼꼼하게&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 꼭 실제 호출(실행)테스트 해보고 데이터 확인하기!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 금융감독원 종합검사&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스뱅크가 첫 금융감독원 정기 검사를 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://it.chosun.com/news/articleView.html?idxno=2023092126302&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://it.chosun.com/news/articleView.html?idxno=2023092126302&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732286414998&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;금감원&amp;middot;한은, 토스뱅크 첫 정기 검사&amp;hellip; 4주간 진행&quot; data-og-description=&quot;금융감독원과 한국은행이 인터넷전문은행 토스뱅크에 대한 공동검사에 착수한다.4일 금융업계에 따르면 금감원과 한은은 이날부터 4주간 토스뱅크를 대상으로 정기검사를 진행한다. 지난 2021&quot; data-og-host=&quot;it.chosun.com&quot; data-og-source-url=&quot;https://it.chosun.com/news/articleView.html?idxno=2023092126302&quot; data-og-url=&quot;https://it.chosun.com/news/articleView.html?idxno=2023092126302&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/UN9JC/hyXDmXIkWu/BUCzkcYj0WV9nPdpWaVoDk/img.jpg?width=601&amp;amp;height=376&amp;amp;face=168_135_220_187,https://scrap.kakaocdn.net/dn/dPJPbo/hyXDi8RyUC/fb6DICU563Zvund40Ae5K1/img.jpg?width=601&amp;amp;height=376&amp;amp;face=168_135_220_187,https://scrap.kakaocdn.net/dn/cufGq0/hyXzOBCYpY/n8KKRkFcyrQ0KVNuzDJirk/img.jpg?width=601&amp;amp;height=376&amp;amp;face=168_135_220_187&quot;&gt;&lt;a href=&quot;https://it.chosun.com/news/articleView.html?idxno=2023092126302&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://it.chosun.com/news/articleView.html?idxno=2023092126302&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/UN9JC/hyXDmXIkWu/BUCzkcYj0WV9nPdpWaVoDk/img.jpg?width=601&amp;amp;height=376&amp;amp;face=168_135_220_187,https://scrap.kakaocdn.net/dn/dPJPbo/hyXDi8RyUC/fb6DICU563Zvund40Ae5K1/img.jpg?width=601&amp;amp;height=376&amp;amp;face=168_135_220_187,https://scrap.kakaocdn.net/dn/cufGq0/hyXzOBCYpY/n8KKRkFcyrQ0KVNuzDJirk/img.jpg?width=601&amp;amp;height=376&amp;amp;face=168_135_220_187');&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;금감원&amp;middot;한은, 토스뱅크 첫 정기 검사&amp;hellip; 4주간 진행&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;금융감독원과 한국은행이 인터넷전문은행 토스뱅크에 대한 공동검사에 착수한다.4일 금융업계에 따르면 금감원과 한은은 이날부터 4주간 토스뱅크를 대상으로 정기검사를 진행한다. 지난 2021&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;it.chosun.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난 AML 파트 개발자로써, 검사역과 인터뷰까지 해보는 경험을 해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료추출하느라 하루 15시간씩 근무한 적이 많았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 힘들었지만 또 언제 이런 경험 해보겠나.. 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 검사를 통해 엔지니어링 적 리스크 보다 비즈니스 적 리스크를 더 크게 고려해야하는 경우도 있음을 깨달았고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사소할 것 같았던 이슈가 큰 비즈니스적 임팩트를 불러올 수 있음도 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그간 너무 겁없이(?) 내재화를 속도감 있게 했던 것은 아닐까 하며 되돌아볼 수 있는 시간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 별탈 없이 잘 마무리 되었다! &lt;s&gt;(다행히 짤리지 않았습니다..)&lt;/s&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;h3 data-ke-size=&quot;size23&quot;&gt;# 독서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인 주도 개발 시작하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001810495&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://product.kyobobook.co.kr/detail/S000001810495&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730015147861&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지 | 최범균 - 교보문고&quot; data-og-description=&quot;도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지 | 가장 쉽게 배우는 도메인 주도 설계 입문서!이 책은 도메인 주도 설계(DDD)를 처음 배우는 개발자를 위한 책이다. 실제 업무에 DDD를&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000001810495&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000001810495&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eloWSe/hyXppHc091/mE9K22y3hprIslxWNtgRbK/img.jpg?width=458&amp;amp;height=593&amp;amp;face=0_0_458_593,https://scrap.kakaocdn.net/dn/uMNVJ/hyXlJtPk6h/XUSQLK6OWfKs7UvcXTL34k/img.jpg?width=458&amp;amp;height=593&amp;amp;face=0_0_458_593,https://scrap.kakaocdn.net/dn/frHMM/hyXppmUsE8/Eu7rjcP01bIS89YDmtZFm1/img.jpg?width=814&amp;amp;height=2890&amp;amp;face=0_0_814_2890&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001810495&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000001810495&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eloWSe/hyXppHc091/mE9K22y3hprIslxWNtgRbK/img.jpg?width=458&amp;amp;height=593&amp;amp;face=0_0_458_593,https://scrap.kakaocdn.net/dn/uMNVJ/hyXlJtPk6h/XUSQLK6OWfKs7UvcXTL34k/img.jpg?width=458&amp;amp;height=593&amp;amp;face=0_0_458_593,https://scrap.kakaocdn.net/dn/frHMM/hyXppmUsE8/Eu7rjcP01bIS89YDmtZFm1/img.jpg?width=814&amp;amp;height=2890&amp;amp;face=0_0_814_2890');&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;도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지 | 최범균 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지 | 가장 쉽게 배우는 도메인 주도 설계 입문서!이 책은 도메인 주도 설계(DDD)를 처음 배우는 개발자를 위한 책이다. 실제 업무에 DDD를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 동료 추천으로 읽게된 책인데 DDD에 대한 전반적인 개념을 잡을 수 있어서 좋았다.&lt;/p&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;b&gt;좋은 코드, 나쁜 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/109366833&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.yes24.com/Product/Goods/109366833&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730015485264&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;좋은 코드, 나쁜 코드 - 예스24&quot; data-og-description=&quot;구글 엔지니어가 말하는 좋은 코드 작성법 좋은 코드를 작성하기 위한 이론과 실전을 소개한다. 단순히 해야 할 일과 하지 말아야 할 일을 나열하기보다, 여섯 가지 원칙을 바탕으로 각 개념과 &quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;https://www.yes24.com/Product/Goods/109366833&quot; data-og-url=&quot;https://www.yes24.com/Product/Goods/109366833&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JHQII/hyXpCNkzhl/9ZiOoqhgcNlp4EIIUQnyTk/img.jpg?width=920&amp;amp;height=1200&amp;amp;face=0_0_920_1200,https://scrap.kakaocdn.net/dn/ckCr6m/hyXlLSHafg/C2JVoZ6Okkx4mTq2K3lUt0/img.jpg?width=920&amp;amp;height=1200&amp;amp;face=0_0_920_1200,https://scrap.kakaocdn.net/dn/MPwzo/hyXpuhrb99/fUsNQL5suvV8YgUYVkOOmk/img.jpg?width=920&amp;amp;height=1200&amp;amp;face=0_0_920_1200&quot;&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/109366833&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.yes24.com/Product/Goods/109366833&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JHQII/hyXpCNkzhl/9ZiOoqhgcNlp4EIIUQnyTk/img.jpg?width=920&amp;amp;height=1200&amp;amp;face=0_0_920_1200,https://scrap.kakaocdn.net/dn/ckCr6m/hyXlLSHafg/C2JVoZ6Okkx4mTq2K3lUt0/img.jpg?width=920&amp;amp;height=1200&amp;amp;face=0_0_920_1200,https://scrap.kakaocdn.net/dn/MPwzo/hyXpuhrb99/fUsNQL5suvV8YgUYVkOOmk/img.jpg?width=920&amp;amp;height=1200&amp;amp;face=0_0_920_1200');&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;좋은 코드, 나쁜 코드 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;구글 엔지니어가 말하는 좋은 코드 작성법 좋은 코드를 작성하기 위한 이론과 실전을 소개한다. 단순히 해야 할 일과 하지 말아야 할 일을 나열하기보다, 여섯 가지 원칙을 바탕으로 각 개념과&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내 스터디 1번째로 했던 책이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 당연한 이야기를 조금 장황하게 하고 있는 것 같은 느낌이 들어서 별로였다. 비추천&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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 data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/105138479&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.yes24.com/Product/Goods/105138479&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730015448426&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;만들면서 배우는 클린 아키텍처 - 예스24&quot; data-og-description=&quot;우리 모두는 낮은 개발 비용으로 유연하고 적응이 쉬운 소프트웨어 아키텍처를 구축하고자 한다. 그러나 불합리한 기한과 쉬워보이는 지름길은 이러한 아키텍처를 구축하는 것을 매우 어렵게 &quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;https://www.yes24.com/Product/Goods/105138479&quot; data-og-url=&quot;https://www.yes24.com/Product/Goods/105138479&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cmfDBu/hyXptW8ykz/hcDw6oYMNz10vkSAUwzrok/img.jpg?width=900&amp;amp;height=1200&amp;amp;face=0_0_900_1200,https://scrap.kakaocdn.net/dn/blguef/hyXlNbV39V/k3RkYKB0QWlpZPDCd9HGKk/img.jpg?width=900&amp;amp;height=1200&amp;amp;face=0_0_900_1200,https://scrap.kakaocdn.net/dn/cTX7Ey/hyXlPOkiju/3O0jkLK9SyXVrxkXgxtIyK/img.jpg?width=900&amp;amp;height=1200&amp;amp;face=0_0_900_1200&quot;&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/105138479&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.yes24.com/Product/Goods/105138479&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cmfDBu/hyXptW8ykz/hcDw6oYMNz10vkSAUwzrok/img.jpg?width=900&amp;amp;height=1200&amp;amp;face=0_0_900_1200,https://scrap.kakaocdn.net/dn/blguef/hyXlNbV39V/k3RkYKB0QWlpZPDCd9HGKk/img.jpg?width=900&amp;amp;height=1200&amp;amp;face=0_0_900_1200,https://scrap.kakaocdn.net/dn/cTX7Ey/hyXlPOkiju/3O0jkLK9SyXVrxkXgxtIyK/img.jpg?width=900&amp;amp;height=1200&amp;amp;face=0_0_900_1200');&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;만들면서 배우는 클린 아키텍처 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;우리 모두는 낮은 개발 비용으로 유연하고 적응이 쉬운 소프트웨어 아키텍처를 구축하고자 한다. 그러나 불합리한 기한과 쉬워보이는 지름길은 이러한 아키텍처를 구축하는 것을 매우 어렵게&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내 스터디로 2번째로 했던 책이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 내용은 헥사고날 아키텍처에 관한 이야기인데 평소 고민하던 것들을 책에서 명시적으로 정리해주는 것들이 종종 있어서 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책도 책인데 읽고 토론했던 스터디 방식이 인상깊었어서 더 기억에 남는다.&lt;/p&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;b&gt;파이토치 딥 러닝 프로그래밍&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/111704966&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.yes24.com/Product/Goods/111704966&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730015669980&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;차근차근 실습하며 배우는 파이토치 딥러닝 프로그래밍 - 예스24&quot; data-og-description=&quot;딥러닝 원리와 파이토치의 구현을 빠르게 이해하자!이 책은 최근에 특히 주목받는 딥러닝 프레임워크인 &amp;lsquo;파이토치(PyTorch)&amp;rsquo;에 관한 입문 서적이다. 이미지 인식을 주 소재로 삼아 파이토치의 &quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;https://www.yes24.com/Product/Goods/111704966&quot; data-og-url=&quot;https://www.yes24.com/Product/Goods/111704966&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bBBFJN/hyXlO2VZD1/1Kmy64W9Qrt7J3yNMDU4aK/img.jpg?width=975&amp;amp;height=1200&amp;amp;face=0_0_975_1200,https://scrap.kakaocdn.net/dn/91upD/hyXlUa1qGu/z9F55dW6N9RYtoiJsrc7K1/img.jpg?width=975&amp;amp;height=1200&amp;amp;face=0_0_975_1200,https://scrap.kakaocdn.net/dn/fNw3X/hyXlXewnf1/9TK9foZrXka8ATNebNhNd0/img.jpg?width=975&amp;amp;height=1200&amp;amp;face=0_0_975_1200&quot;&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/111704966&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.yes24.com/Product/Goods/111704966&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bBBFJN/hyXlO2VZD1/1Kmy64W9Qrt7J3yNMDU4aK/img.jpg?width=975&amp;amp;height=1200&amp;amp;face=0_0_975_1200,https://scrap.kakaocdn.net/dn/91upD/hyXlUa1qGu/z9F55dW6N9RYtoiJsrc7K1/img.jpg?width=975&amp;amp;height=1200&amp;amp;face=0_0_975_1200,https://scrap.kakaocdn.net/dn/fNw3X/hyXlXewnf1/9TK9foZrXka8ATNebNhNd0/img.jpg?width=975&amp;amp;height=1200&amp;amp;face=0_0_975_1200');&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;차근차근 실습하며 배우는 파이토치 딥러닝 프로그래밍 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;딥러닝 원리와 파이토치의 구현을 빠르게 이해하자!이 책은 최근에 특히 주목받는 딥러닝 프레임워크인 &amp;lsquo;파이토치(PyTorch)&amp;rsquo;에 관한 입문 서적이다. 이미지 인식을 주 소재로 삼아 파이토치의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;술술 프로젝트를 하면서 딥러닝이 궁금해져서 무작정 구매해서 읽어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학과 시절에 배웠던 것들이 코드로 구현되어 모델이 어떻게 학습이 되는지 원리를 코드로 알 수 있어서 너무 재밌게 읽었었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딥러닝 실습을 찍먹해보고 싶다면 추천&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 2025년에는?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느덧 토스뱅크에 입사한지 2년이 지났고 곧 3년차 개발자가 되어간다 (시간이 참 빠르다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 기술을 접목시켜보고 학습하는것도 즐겁지만, '더 프로다운' 모습은 저거에 더불어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어떻게 안정성있는 운영을 할 것인지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 영향도 파악과 위험요소 식별을 어떻게 더 잘할 것인지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어떻게 장애를 안 낼 것인지 (사실 장애를 안 내는건 서비스가 살아있으면 불가능하다고 생각한다.. 그치만 은행으로써 갖춰야할 보장된 안정성과 개발자로써 추구해야할 혁신의 중간에서 잘 조율을 하는게 내년 과제로 삼고싶다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 고민해보면서 성장해가고 싶다.&lt;/p&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. 책을 더 많이 읽자 (목표 7권)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 해외여행 다녀오기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 더 프로답게 일하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>정리, 기록</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/192</guid>
      <comments>https://mopil.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0#entry192comment</comments>
      <pubDate>Tue, 31 Dec 2024 13:00:36 +0900</pubDate>
    </item>
    <item>
      <title>리눅스 80 -&amp;gt; 8080 포트포워딩</title>
      <link>https://mopil.tistory.com/entry/%EB%A6%AC%EB%88%85%EC%8A%A4-80-8080-%ED%8F%AC%ED%8A%B8%ED%8F%AC%EC%9B%8C%EB%94%A9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;80포트로 들어오는 요청을 8080포트로 포워딩 시키는 방법&lt;/p&gt;
&lt;pre id=&quot;code_1732802268843&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080&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;</description>
      <category>Backend/DevOps</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/194</guid>
      <comments>https://mopil.tistory.com/entry/%EB%A6%AC%EB%88%85%EC%8A%A4-80-8080-%ED%8F%AC%ED%8A%B8%ED%8F%AC%EC%9B%8C%EB%94%A9#entry194comment</comments>
      <pubDate>Thu, 28 Nov 2024 22:59:27 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Caffeine 로컬 캐시로 Rate Limiter를 구현해보자</title>
      <link>https://mopil.tistory.com/entry/Spring-Boot-Caffeine-%EB%A1%9C%EC%BB%AC-%EC%BA%90%EC%8B%9C%EB%A1%9C-Rate-Limiter%EB%A5%BC-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;# Rate Limit이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정 시간 동안 호출될 수 있는 API 횟수를 제한하는 걸 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 파일 업로드는 10분에 최대 3번만 가능 이런 걸 구현할 때 사용하는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 건 아래글 참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gngsn.tistory.com/224&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://gngsn.tistory.com/224&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729355410393&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Rate Limiter, 제대로 이해하기&quot; data-og-description=&quot;Rate limiter의 역할과 강단점을 살펴보고, 구현 알고리즘 5가지를 이해하는 것이 해당 포스팅의 목표입니다. 본 포스팅의 모든 그림은 필자가 직접 그린 것으로 무단 사용을 금하며, 사용 시 출처&quot; data-og-host=&quot;gngsn.tistory.com&quot; data-og-source-url=&quot;https://gngsn.tistory.com/224&quot; data-og-url=&quot;https://gngsn.tistory.com/224&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c9EA7V/hyXhP1Rnn7/eEAF7SXip8272KedOKV2Z1/img.png?width=800&amp;amp;height=485&amp;amp;face=0_0_800_485,https://scrap.kakaocdn.net/dn/dSTC7G/hyXhUPCFlB/L98kqg83YIRtYK6UdBlNh1/img.png?width=800&amp;amp;height=485&amp;amp;face=0_0_800_485,https://scrap.kakaocdn.net/dn/ebpgfl/hyXhOhzW7r/lO7YoQBhZ587ERSo0i4Y2k/img.png?width=4878&amp;amp;height=1878&amp;amp;face=0_0_4878_1878&quot;&gt;&lt;a href=&quot;https://gngsn.tistory.com/224&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gngsn.tistory.com/224&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c9EA7V/hyXhP1Rnn7/eEAF7SXip8272KedOKV2Z1/img.png?width=800&amp;amp;height=485&amp;amp;face=0_0_800_485,https://scrap.kakaocdn.net/dn/dSTC7G/hyXhUPCFlB/L98kqg83YIRtYK6UdBlNh1/img.png?width=800&amp;amp;height=485&amp;amp;face=0_0_800_485,https://scrap.kakaocdn.net/dn/ebpgfl/hyXhOhzW7r/lO7YoQBhZ587ERSo0i4Y2k/img.png?width=4878&amp;amp;height=1878&amp;amp;face=0_0_4878_1878');&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;Rate Limiter, 제대로 이해하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Rate limiter의 역할과 강단점을 살펴보고, 구현 알고리즘 5가지를 이해하는 것이 해당 포스팅의 목표입니다. 본 포스팅의 모든 그림은 필자가 직접 그린 것으로 무단 사용을 금하며, 사용 시 출처&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gngsn.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 로컬 캐시 라이브러리로 유명한 Caffeine으로 Rate Limiter 기능을 구현해 볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 왜 Caffeine인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Cloud Gateway나 Bucket4j를 활용해서 손쉽게 구현할 수도 있다.&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;다만, 이번에는 외부 저장소 (ex. Redis) 없이 로컬 캐시로 라이트하게 구현하기 위해서 Caffeine을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 InMemory 저장을 위함이면 Collections를 써도되지 않나 싶었지만, TTL을 설정할 수 없다.&lt;/p&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;여담으로 당연하겠지만 Caffeine은 thread-safe 하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/ben-manes/caffeine/issues/392&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/ben-manes/caffeine/issues/392&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729355791914&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Question: Thread safety? &amp;middot; Issue #392 &amp;middot; ben-manes/caffeine&quot; data-og-description=&quot;Is caffeine thread safe? Can I use the same instance of the cache from multiple threads/coroutines/futures?&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/ben-manes/caffeine/issues/392&quot; data-og-url=&quot;https://github.com/ben-manes/caffeine/issues/392&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Vx53L/hyXlOAdpJy/V6b7xz03rHgYTiKJlTWKbK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ETsIx/hyXhQsXQnB/Vyqvp2FcgzC2NFcW0KC8f0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/ben-manes/caffeine/issues/392&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/ben-manes/caffeine/issues/392&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Vx53L/hyXlOAdpJy/V6b7xz03rHgYTiKJlTWKbK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ETsIx/hyXhQsXQnB/Vyqvp2FcgzC2NFcW0KC8f0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;Question: Thread safety? &amp;middot; Issue #392 &amp;middot; ben-manes/caffeine&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Is caffeine thread safe? Can I use the same instance of the cache from multiple threads/coroutines/futures?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 가상 요구 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 업로드 API에 Rate Limit을 구현해볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증된 사용자마다 10초동안 최대 5번만 업로드가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle.kts&lt;/p&gt;
&lt;pre id=&quot;code_1729356013096&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    implementation(&quot;com.github.ben-manes.caffeine:caffeine:3.1.8&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rate Limiter&lt;/p&gt;
&lt;pre id=&quot;code_1729356033839&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface RateLimiter {
    fun checkLimit(userId: Long)

    fun increaseCount(userId: Long)

    class CaffeineLocalCacheImpl(
        val limit: Int,
        val duration: Long,
        val timeUnit: TimeUnit,
        val maxSize: Long = 100,
    ) : RateLimiter {
        private val cache =
            Caffeine
                .newBuilder()
                .maximumSize(maxSize)
                .expireAfterWrite(duration, timeUnit)
                .build&amp;lt;String, Any&amp;gt;()

        override fun checkLimit(userId: Long) {
            val uploadCount = cache.getIfPresent(userId.toString()) as Int?
            if (uploadCount != null &amp;amp;&amp;amp; uploadCount &amp;gt;= limit) {
                throw RateLimitExceededException()
            }
        }

        override fun increaseCount(userId: Long) {
            val uploadCount = cache.getIfPresent(userId.toString()) as Int?
            if (uploadCount == null) {
                cache.put(userId.toString(), 1)
            } else {
                cache.put(userId.toString(), uploadCount + 1)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rate Limiter 인터페이스를 만들고 각 구현체는 Bean으로 등록하여 필요한 곳에서 Qualifier로 주입받아 사용할 수 있게끔 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Caffeine의 key는 userId: Long이고 value는 현재까지의 카운트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729356374168&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class RateLimitExceededException(
    message: String = ErrorType.TOO_MANY_REQUEST.defaultMessage,
) : RuntimeException(message)&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;커스텀 예외를 하나 만들어준다. 그리고 이를 핸들 할 ControllerAdvice도 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729356410952&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @ExceptionHandler(RateLimitExceededException::class)
    fun handleRateLimitException(e: RateLimitExceededException): ResponseEntity&amp;lt;ErrorResponse&amp;gt; {
        return ResponseEntity
            .status(HttpStatus.TOO_MANY_REQUESTS)
            .body(
                ErrorResponse.from(ErrorType.TOO_MANY_REQUEST),
            )
    }&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;ErrorResponse과 ErrorType은 커스텀 객체로 적당히 구현해 준다.&lt;/p&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;RateLimiterConfig&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
class RateLimiterConfig() {
    @Bean
    fun fileUploadRateLimiter(): RateLimiter {
        return RateLimiter.CaffeineLocalCacheImpl(
            limit = 5,
            duration = 10,
            timeUnit = TimeUnit.SECONDS,
            maxSize = 100,
        )
    }

	// 추후 다른 기능에 필요하다면 추가할 수 있도록 만들었다
    @Bean
    fun programApplicationRateLimiter(): RateLimiter {
        return RateLimiter.CaffeineLocalCacheImpl(
            limit = 5,
            duration = 24,
            timeUnit = TimeUnit.HOURS,
            maxSize = 100,
        )
    }
}&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;별도 Config 파일을 만들어서 필요한 리미터를 빈으로 등록한다.&lt;/p&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;pre id=&quot;code_1729356182303&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; rateLimit(rateLimiter: RateLimiter, userId: Long, block: () -&amp;gt; T): T {
    rateLimiter.checkLimit(userId)
    val result = block()
    rateLimiter.increaseCount(userId)
    return result
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 간결하게 하기 위해서 리미터 호출 부분은 별도의 최상위 함수로, 람다를 통해 수행 블록을 받도록 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FileController&lt;/p&gt;
&lt;pre id=&quot;code_1729356231048&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Tag(name = &quot;파일&quot;)
@RestController
@RequestMapping(&quot;/common/files&quot;)
class FileController(
    private val fileManager: FileManager,
    @Qualifier(&quot;fileUploadRateLimiter&quot;) private val rateLimiter: RateLimiter
) : FileApiSpec {
    @AuthenticationRequired
    @PostMapping(
        &quot;/upload&quot;,
        consumes = [MediaType.MULTIPART_FORM_DATA_VALUE],
        produces = [MediaType.APPLICATION_JSON_VALUE],
    )
    override fun uploadFile(
        @RequestPart file: MultipartFile,
    ): FileMetaResponse {
        val loginUser = AuthenticationUtils.getCurrentLoginUser()
        return rateLimit(rateLimiter, loginUser.id) {
            fileManager.uploadFile(loginUser.id, file)
        }
    }
}&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;컨트롤러에서 앞서 정의한 fileUploadRateLimiter를 주입받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rate limit 체크는 비즈니스 로직과는 거리가 멀다고 생각해서 컨트롤러단 로직으로 분리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 정의한 rateLimit 최상위 함수를 통해 리미터와 userId를 넘겨서 체크한다.&lt;/p&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-filename=&quot;1.png&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cD1qw3/btsKbJmL8ZM/7PC4xr3RpX2tXSf5Wpf9tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cD1qw3/btsKbJmL8ZM/7PC4xr3RpX2tXSf5Wpf9tK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cD1qw3/btsKbJmL8ZM/7PC4xr3RpX2tXSf5Wpf9tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcD1qw3%2FbtsKbJmL8ZM%2F7PC4xr3RpX2tXSf5Wpf9tK%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;982&quot; height=&quot;300&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 동작하는 걸 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 해당 커밋을 보면 확인 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/mopil/youth-moa-server/commit/ea49f8edf67cf7401d569ff050768af8fbffd2db&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/mopil/youth-moa-server/commit/ea49f8edf67cf7401d569ff050768af8fbffd2db&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729357250415&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;feat: file upload rate limit 추가 &amp;middot; mopil/youth-moa-server@ea49f8e&quot; data-og-description=&quot;mopil committed Oct 19, 2024&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/mopil/youth-moa-server/commit/ea49f8edf67cf7401d569ff050768af8fbffd2db&quot; data-og-url=&quot;https://github.com/mopil/youth-moa-server/commit/ea49f8edf67cf7401d569ff050768af8fbffd2db&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dgRx0x/hyXlSCCzsf/lqbzX4dKYjTyhnQTonheT0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/otEaS/hyXlLwIJqI/xJSxknXC5k6P7xYxBH92Ak/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/mopil/youth-moa-server/commit/ea49f8edf67cf7401d569ff050768af8fbffd2db&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/mopil/youth-moa-server/commit/ea49f8edf67cf7401d569ff050768af8fbffd2db&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dgRx0x/hyXlSCCzsf/lqbzX4dKYjTyhnQTonheT0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/otEaS/hyXlLwIJqI/xJSxknXC5k6P7xYxBH92Ak/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;feat: file upload rate limit 추가 &amp;middot; mopil/youth-moa-server@ea49f8e&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;mopil committed Oct 19, 2024&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p 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;h3 data-ke-size=&quot;size23&quot;&gt;# 한계 &amp;amp; 후기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복수 인스턴스의 서버 환경에서는 userId별 5회 제한을 할 수 없는 구조여서 그런 경우는 외부 중앙 저장소 (ex. Redis)가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 로컬 캐시로 관리하기 때문에 트래픽을 많이 받는 환경이면 캐시로 인한 OOM에 신경 써야 한다.&lt;/p&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;토큰 버킷처럼 특정 시간이 지날 때마다 초기화가 아닌 토큰을 1개 추가하는 기능 구현이 제약된다.&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 과금을 방지용으로, 혹은 단순 rate limit 기능을 빠르게 구현하기 위해서라면 해당 방법도 나쁘지 않은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 더 많은 기능이 있는 Bucket4j로 한 번 만들어봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Spring Framework</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/193</guid>
      <comments>https://mopil.tistory.com/entry/Spring-Boot-Caffeine-%EB%A1%9C%EC%BB%AC-%EC%BA%90%EC%8B%9C%EB%A1%9C-Rate-Limiter%EB%A5%BC-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90#entry193comment</comments>
      <pubDate>Sun, 20 Oct 2024 02:03:01 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 1차 캐싱을 직접 확인해보자</title>
      <link>https://mopil.tistory.com/entry/JPA-1%EC%B0%A8-%EC%BA%90%EC%8B%B1%EC%9D%84-%EC%A7%81%EC%A0%91-%ED%99%95%EC%9D%B8%ED%95%B4%EB%B3%B4%EC%9E%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 영속성 컨텍스트가 제공하는 기능 중 하나인 '1차 캐싱' 기능을 눈으로 직접 확인해 보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728720262227&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
class FirstService(
    private val userRepository: UserRepository,
) {

    @Transactional
    fun callFirst() {
        var user = userRepository.findById(1).get()
        println(&quot;first call user = $user&quot;)
        user = userRepository.findById(1).get()
        println(&quot;second call user = $user&quot;)
        user = userRepository.findById(1).get()
        println(&quot;third call user = $user&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;리포지토리에서 동일한 User를 총 3번 호출했다.&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;656&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJYi0c/btsJ22myHbs/NUsVROeELAKeSJiwe3neZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJYi0c/btsJ22myHbs/NUsVROeELAKeSJiwe3neZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJYi0c/btsJ22myHbs/NUsVROeELAKeSJiwe3neZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJYi0c%2FbtsJ22myHbs%2FNUsVROeELAKeSJiwe3neZ0%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;656&quot; height=&quot;335&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;335&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;1차 캐싱 덕에 쿼리는 1번만 나가고 2,3번째 조회는 쿼리를 안 보내고 영속성 컨텍스트에서 바로 가져오는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 다른 트랜잭셔널로 묶인 서비스를 주입받아 호출하는 경우도 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 최상위 트랜잭션이 종료될 때까지 유지된다.&lt;/p&gt;
&lt;pre id=&quot;code_1728720081348&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
class FirstService(
    private val userRepository: UserRepository,
    private val secondService: SecondService
) {

    @Transactional
    fun callFirst() {
        val user = userRepository.findById(1).get()
        println(&quot;first call user = $user&quot;)
        secondService.callSecond()
    }
}

@Service
class SecondService(
    private val userRepository: UserRepository
) {

    @Transactional
    fun callSecond() {
        val user = userRepository.findById(1).get()
        println(&quot;second call user = $user&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FisrtService에서 SecondService를 주입받아 동일한 유저를 2번 리포지토리에서 가져오는 간단한 로직이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;668&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OiqZb/btsJ2S5zTvY/zu0BFxeDGWeGBw9GaCP7QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OiqZb/btsJ2S5zTvY/zu0BFxeDGWeGBw9GaCP7QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OiqZb/btsJ2S5zTvY/zu0BFxeDGWeGBw9GaCP7QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOiqZb%2FbtsJ2S5zTvY%2Fzu0BFxeDGWeGBw9GaCP7QK%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;668&quot; height=&quot;333&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;333&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;호출해보면 second call user 이전에는 select 쿼리가 나가지 않는 것을 확인할 수 있다.&lt;/p&gt;</description>
      <category>Backend/Database</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/191</guid>
      <comments>https://mopil.tistory.com/entry/JPA-1%EC%B0%A8-%EC%BA%90%EC%8B%B1%EC%9D%84-%EC%A7%81%EC%A0%91-%ED%99%95%EC%9D%B8%ED%95%B4%EB%B3%B4%EC%9E%90#entry191comment</comments>
      <pubDate>Sat, 12 Oct 2024 17:08:13 +0900</pubDate>
    </item>
    <item>
      <title>유저에게 보이는 에러메시지 워싱은 어디서 하는게 좋을까? 서버 vs 프론트</title>
      <link>https://mopil.tistory.com/entry/%EC%9C%A0%EC%A0%80%EC%97%90%EA%B2%8C-%EB%B3%B4%EC%9D%B4%EB%8A%94-%EC%97%90%EB%9F%AC%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%9B%8C%EC%8B%B1%EC%9D%80-%EC%96%B4%EB%94%94%EC%84%9C-%ED%95%98%EB%8A%94%EA%B2%8C-%EC%A2%8B%EC%9D%84%EA%B9%8C-%EC%84%9C%EB%B2%84-vs-%ED%94%84%EB%A1%A0%ED%8A%B8</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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p9KLx/btsJVGDxC77/VzXnZ1Z8PKYodIkPAQsNLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p9KLx/btsJVGDxC77/VzXnZ1Z8PKYodIkPAQsNLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p9KLx/btsJVGDxC77/VzXnZ1Z8PKYodIkPAQsNLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp9KLx%2FbtsJVGDxC77%2FVzXnZ1Z8PKYodIkPAQsNLK%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;415&quot; height=&quot;180&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 에러메시지 그대로 유저에게 보여주는 건 바람직하지 못하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보통 에러메시지를 유저편의적으로 가공하는데, 이를 '에러메시지 워싱'이라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 얼마나 구체적인 에러 메시지를 보여줄까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워싱 주체에 대해 논하기 전, 먼저 유저에게 어떤 에러메시지를 보여주는 게 적합할지 생각을 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답은 아닐 수도 있지만, 필자는 이렇게 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 유저 스스로가 다른 액션을 통해 해당 에러를 해소할 수 있는 경우는 &lt;u&gt;최대한 구체적으로 알려준다.&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 회원가입 시 비밀번호 규칙 중, 특수기호가 포함되어야 하는데 빠진 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상적인 경우: 비밀번호 규칙에 올바르지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 경우: 특수기호가 빠졌어요. 비밀번호에는 적어도 1개 이상의 특수문자가 필요해요.&lt;/p&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;b&gt;2. 반대로, 유저가 어떤 액션을 해도 해당 에러를 해소할 수 없는 경우는 &lt;u&gt;최대한 추상적으로 알려준다.&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 회원가입 시 다른 서버 호출을 하는데 해당 서버가 죽은 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상적인 경우: 요청을 처리하지 못하였어요. 잠시 후 다시 시도하거나 고객센터에 문의하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 경우: XX 서버 호출이 실패하였습니다. 사유: xxxx 에러&lt;/p&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;이는 좀 더 포괄적으로 개발자가 비즈니스 로직상으로 의도한 flow가 아닌 경우 발생한 예외의 경우 최대한 자세히 알려주고, 그 외 예상치 못한 에러나 테크니컬 적인 에러는 추상적으로 포장하는 것이 좋다고 생각해도 무방할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 워싱은 프론트에서 하는 게 적합할까? 서버에서 하는게 적합할까? 에 대해서 고민해 보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 다른 개발자들 의견&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자 회사는 보통 서버에서 다 워싱해서 내려주는데, 다른 사람들은(조직) 어떻게 하는지, 생각하는지 궁금했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20241005_145755840.jpg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ng4Gn/btsJUY50nPw/A1mIKwVe9NKW1zu1sD3X4K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ng4Gn/btsJUY50nPw/A1mIKwVe9NKW1zu1sD3X4K/img.jpg&quot; data-alt=&quot;프론트 개발자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ng4Gn/btsJUY50nPw/A1mIKwVe9NKW1zu1sD3X4K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNg4Gn%2FbtsJUY50nPw%2FA1mIKwVe9NKW1zu1sD3X4K%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;657&quot; height=&quot;834&quot; data-filename=&quot;KakaoTalk_20241005_145755840.jpg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1485&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-filename=&quot;KakaoTalk_20241005_145755840_02.jpg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DMjmt/btsJWrMoesR/8h77DckIWVzBjRQdXw4PkK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DMjmt/btsJWrMoesR/8h77DckIWVzBjRQdXw4PkK/img.jpg&quot; data-alt=&quot;서버 개발자1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DMjmt/btsJWrMoesR/8h77DckIWVzBjRQdXw4PkK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDMjmt%2FbtsJWrMoesR%2F8h77DckIWVzBjRQdXw4PkK%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;490&quot; height=&quot;506&quot; data-filename=&quot;KakaoTalk_20241005_145755840_02.jpg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1209&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서버 개발자1&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20241005_145952976.jpg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXbTMO/btsJWOG820G/FgldKabm1S6o5GFVckfcN1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXbTMO/btsJWOG820G/FgldKabm1S6o5GFVckfcN1/img.jpg&quot; data-alt=&quot;서버 개발자2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXbTMO/btsJWOG820G/FgldKabm1S6o5GFVckfcN1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXbTMO%2FbtsJWOG820G%2FFgldKabm1S6o5GFVckfcN1%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;544&quot; height=&quot;693&quot; data-filename=&quot;KakaoTalk_20241005_145952976.jpg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서버 개발자2&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버다, 프론트다 좁혀지지 않고 다양한 의견이 나왔다. 정리하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 하는 게 맞다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프론트에서까지 해버리면 관리포인트가 늘어난다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트에서 하는게 맞다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여럿 플랫폼 지원을 (확장성) 고려했을 때 더 유연하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 정의된 에러에 대한 더 유저친화적인 에러 메시지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 필자의 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개발자들의 의견을 들어보면서 내린 결론은 다음과 같다.&lt;/p&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. API가 UI에서만 사용되는 경우(다른 플랫폼 지원 가능성이 없는 경우)는 서버에서 에러메시지를 워싱해서 내려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 서버, 프론트 둘 다 메시지를 워싱하면 관리포인트가 2개가 생기기 때문에 비효율적이라고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 여럿 플랫폼을 지원하는 경우 프론트에서 워싱한다.&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;b&gt;# 좀 더 고민해 보기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 역할과 관심사의 분리의 관점에서 보면 프론트에서 워싱하는 게 더 맞지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;</description>
      <category>고민해보기</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/189</guid>
      <comments>https://mopil.tistory.com/entry/%EC%9C%A0%EC%A0%80%EC%97%90%EA%B2%8C-%EB%B3%B4%EC%9D%B4%EB%8A%94-%EC%97%90%EB%9F%AC%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%9B%8C%EC%8B%B1%EC%9D%80-%EC%96%B4%EB%94%94%EC%84%9C-%ED%95%98%EB%8A%94%EA%B2%8C-%EC%A2%8B%EC%9D%84%EA%B9%8C-%EC%84%9C%EB%B2%84-vs-%ED%94%84%EB%A1%A0%ED%8A%B8#entry189comment</comments>
      <pubDate>Sat, 5 Oct 2024 15:19:35 +0900</pubDate>
    </item>
    <item>
      <title>인덱스 꼼수 부리기</title>
      <link>https://mopil.tistory.com/entry/%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EA%BC%BC%EC%88%98-%EB%B6%80%EB%A6%AC%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# != 연산에 인덱스 태우기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;where 절의 not equal(!=) 연산은 인덱스를 걸어놔도 효과를 볼 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 해당 칼럼이 순차적인 코드값으로 되어있는 경우는 인덱스를 태우도록 꼼수(?)를 부릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 디비는 오라클 기준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, my_table이 존재하고 my_column이 이런 분포를 가진다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1000 : 32만 rows&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2000 : 10만 rows&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3000 : 5만 rows&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4000 : 22만 rows&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5000 : 1만 rows&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6000 : 43만 rows&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 my_column이 1000이 아닌 rows를 select 한다고 가정해보자. 인덱스를 걸어놔도,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;select * from my_table where my_column != '1000' 으로 하면 full scan을 한다.&lt;/p&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;select * from my_table where my_column &amp;gt; '1000' 이렇게 쿼리를 짜면 index scan을 하도록 할 수 있다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;# 카디널리티가 높지 않은 칼럼에 인덱스 효과 누리기&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, my_table이 존재하고 nationality이 이런 분포를 가진다고 해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대한민국 : 950만 rows&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일본 : 3만 rows&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가나 : 323 rows&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;태국 : 3 rows&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 테이블에서 국적이 대한민국인 row를 쿼링하는건 당연히 인덱스 효과를 볼 수 없다. (풀스캔과 동일)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단, 다른 국가들인 경우 인덱스 효과를 볼 수 있어 대한민국이 아닌 다른 국가들을 조회하는 쿼리가 많을 경우 인덱스를 적용해주는 것이 좋다.&lt;/p&gt;</description>
      <category>Backend/Database</category>
      <author>mopil</author>
      <guid isPermaLink="true">https://mopil.tistory.com/188</guid>
      <comments>https://mopil.tistory.com/entry/%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EA%BC%BC%EC%88%98-%EB%B6%80%EB%A6%AC%EA%B8%B0#entry188comment</comments>
      <pubDate>Wed, 4 Sep 2024 01:00:40 +0900</pubDate>
    </item>
  </channel>
</rss>