<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Be-The-Key</title>
    <link>https://keydo.tistory.com/</link>
    <description>Hardware 부터 시작한 다른 의미의 FFFuulll-Stack을 했지만 Back-End에 집중하여 커리어를 이어나가기 위해 노력중입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 20 Jul 2026 20:23:55 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Key Ryung</managingEditor>
    <image>
      <title>Be-The-Key</title>
      <url>https://tistory1.daumcdn.net/tistory/4408538/attach/79a0bd58773a42728159e9d91ef34709</url>
      <link>https://keydo.tistory.com</link>
    </image>
    <item>
      <title>F-lab 멘토링 후기(2/2) - 개발이 다이빙이라면</title>
      <link>https://keydo.tistory.com/25</link>
      <description>&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;blockquote data-ke-style=&quot;style3&quot;&gt;어느 순간 저는 개발이 다이빙이 아닐까라는 생각이 들었습니다. 멘토링 이전의 저는 다이빙을 한다면서 계속 배를 타고 여기저기 다니면서 수심이 얕은 곳만 다녔습니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발자에게 필요한 것은&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에프랩에서 작성한 블로그를 한 편 보게 됩니다.&lt;/p&gt;
&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://f-lab.kr/blog/importance-of-self-improvement&quot;&gt;https://f-lab.kr/blog/importance-of-self-improvement&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678671812618&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;개발자는 왜 40살에 치킨집을 차리게 될까?&quot; data-og-description=&quot;올해 하반기부터 블로그를 제대로 시작하면서 &amp;lt;Tech-Lab&amp;gt;이라는 콘텐츠를 기획하게 되었는데요. 이 카테고리에서는 개발 기술 관련 글, 프로젝트, 스터디 경험담 등 &amp;lsquo;좋은&amp;rsquo; 개발자로 성장하기 &quot; data-og-host=&quot;f-lab.kr&quot; data-og-source-url=&quot;https://f-lab.kr/blog/importance-of-self-improvement&quot; data-og-url=&quot;https://f-lab.kr/blog/importance-of-self-improvement&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cYNEcy/hyRVbU1Ulb/UsPfPpHOQFXKNGTUSEk4k0/img.png?width=1348&amp;amp;height=700&amp;amp;face=0_0_1348_700,https://scrap.kakaocdn.net/dn/bmKx9L/hyRTPF2poH/wbZSKr1LfMfKvavXMFel9k/img.jpg?width=700&amp;amp;height=467&amp;amp;face=0_0_700_467,https://scrap.kakaocdn.net/dn/c2Kq6C/hyRU8jHwUU/8sgk1bAK7SoVBG2QXghsSk/img.jpg?width=700&amp;amp;height=467&amp;amp;face=0_0_700_467&quot;&gt;&lt;a href=&quot;https://f-lab.kr/blog/importance-of-self-improvement&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://f-lab.kr/blog/importance-of-self-improvement&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cYNEcy/hyRVbU1Ulb/UsPfPpHOQFXKNGTUSEk4k0/img.png?width=1348&amp;amp;height=700&amp;amp;face=0_0_1348_700,https://scrap.kakaocdn.net/dn/bmKx9L/hyRTPF2poH/wbZSKr1LfMfKvavXMFel9k/img.jpg?width=700&amp;amp;height=467&amp;amp;face=0_0_700_467,https://scrap.kakaocdn.net/dn/c2Kq6C/hyRU8jHwUU/8sgk1bAK7SoVBG2QXghsSk/img.jpg?width=700&amp;amp;height=467&amp;amp;face=0_0_700_467');&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;개발자는 왜 40살에 치킨집을 차리게 될까?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;올해 하반기부터 블로그를 제대로 시작하면서 &amp;lt;Tech-Lab&amp;gt;이라는 콘텐츠를 기획하게 되었는데요. 이 카테고리에서는 개발 기술 관련 글, 프로젝트, 스터디 경험담 등 &amp;lsquo;좋은&amp;rsquo; 개발자로 성장하기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;f-lab.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;블로그의 제목은 &lt;code&gt;개발자는 왜 40살에 치킨집을 차리게 될까?&lt;/code&gt; 로 제가 가지고 있는 걱정에 대해 해결책을 말해줄 것이란 기대, 그동안 가지고 있던 의문들에 대한 답변을 기대하고 글을 읽기 시작했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FSgVt/btr0Wf2ZSTf/TkLkkJAzRGxHTELbhxqIRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FSgVt/btr0Wf2ZSTf/TkLkkJAzRGxHTELbhxqIRK/img.png&quot; data-alt=&quot;내가 하고 있는 개발자로써의 방향성이 바로 이것이 아닐까라는 생각이 들었던 부분&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FSgVt/btr0Wf2ZSTf/TkLkkJAzRGxHTELbhxqIRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFSgVt%2Fbtr0Wf2ZSTf%2FTkLkkJAzRGxHTELbhxqIRK%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;410&quot; height=&quot;219&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내가 하고 있는 개발자로써의 방향성이 바로 이것이 아닐까라는 생각이 들었던 부분&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;img src=&quot;http://localhost:9425/images/4f4512ca-bdb6-4b9f-9f93-c6a980f8dfa3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 읽으면서 느꼈던 것은 지금의 &lt;b&gt;나라는 개발자의 현재 혹은 미래의 모습이겠구나&lt;/b&gt;라는 생각이었습니다. 어떻게든 시간 내에 구현을 해나가나는 능력을 목표로 개발자라는 커리어를 진행하고 있던 스스로의 모습에 속상했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://f-lab.kr/blog/problem-defining-ability&quot;&gt;https://f-lab.kr/blog/problem-defining-ability&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678671843804&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;개발자는 문제 해결 능력에 앞서 문제 정의 능력이 중요하다.&quot; data-og-description=&quot;안녕하세요. 좋은 개발자로 성장할 수 있는 환경을 만들고 서포트하는 F-Lab입니다. 개발자에게 필요한 능력인 &amp;ldquo;문제를 해결하는 능력&amp;rdquo; 이 무엇인지 정의하고, 이 능력을 키우려면 어떻게 해야&quot; data-og-host=&quot;f-lab.kr&quot; data-og-source-url=&quot;https://f-lab.kr/blog/problem-defining-ability&quot; data-og-url=&quot;https://f-lab.kr/blog/problem-defining-ability&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bVncTE/hyRU3o9d4c/IU0KVcDElP80SGUMTSFykk/img.png?width=1348&amp;amp;height=700&amp;amp;face=0_0_1348_700,https://scrap.kakaocdn.net/dn/bcwIgP/hyRTVfdBsp/tBpss1Qoxkn2ottCFvgGh0/img.jpg?width=700&amp;amp;height=467&amp;amp;face=0_0_700_467,https://scrap.kakaocdn.net/dn/bIQCUE/hyRTYpyCgW/4UCUrVmMqhKHKKUXdsXke0/img.jpg?width=700&amp;amp;height=394&amp;amp;face=0_0_700_394&quot;&gt;&lt;a href=&quot;https://f-lab.kr/blog/problem-defining-ability&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://f-lab.kr/blog/problem-defining-ability&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bVncTE/hyRU3o9d4c/IU0KVcDElP80SGUMTSFykk/img.png?width=1348&amp;amp;height=700&amp;amp;face=0_0_1348_700,https://scrap.kakaocdn.net/dn/bcwIgP/hyRTVfdBsp/tBpss1Qoxkn2ottCFvgGh0/img.jpg?width=700&amp;amp;height=467&amp;amp;face=0_0_700_467,https://scrap.kakaocdn.net/dn/bIQCUE/hyRTYpyCgW/4UCUrVmMqhKHKKUXdsXke0/img.jpg?width=700&amp;amp;height=394&amp;amp;face=0_0_700_394');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;개발자는 문제 해결 능력에 앞서 문제 정의 능력이 중요하다.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 좋은 개발자로 성장할 수 있는 환경을 만들고 서포트하는 F-Lab입니다. 개발자에게 필요한 능력인 &amp;ldquo;문제를 해결하는 능력&amp;rdquo; 이 무엇인지 정의하고, 이 능력을 키우려면 어떻게 해야&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;f-lab.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;이어서 한편을 더 읽어보았습니다. 이번 편의 제목은 &lt;code&gt;개발자는 문제 해결 능력에 앞서 문제 정의 능력이 중요하다.&lt;/code&gt; 였습니다.&lt;/p&gt;
&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;1024&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HYhTo/btr3boZveeu/9sz7HYGFqEkZMugVimcFBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HYhTo/btr3boZveeu/9sz7HYGFqEkZMugVimcFBK/img.png&quot; data-alt=&quot;문제 정의에 따라 문제 해결이 달라집니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HYhTo/btr3boZveeu/9sz7HYGFqEkZMugVimcFBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHYhTo%2Fbtr3boZveeu%2F9sz7HYGFqEkZMugVimcFBK%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;430&quot; height=&quot;546&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1300&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;문제 정의에 따라 문제 해결이 달라집니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 해결에 앞서서 문제 정의를 정확히 해낼 수 있도록 하는 방향의 교육이 필요&lt;/b&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;&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;blockquote data-ke-style=&quot;style3&quot;&gt;안녕하세요!&amp;nbsp;선생님&lt;br /&gt;&lt;br /&gt;올려&amp;nbsp;주신&amp;nbsp;정보를&amp;nbsp;통해서&amp;nbsp;많은&amp;nbsp;것을&amp;nbsp;배우고&amp;nbsp;깨달아&amp;nbsp;나가는&amp;nbsp;중입니다.&amp;nbsp;항상&amp;nbsp;양질의&amp;nbsp;컨텐츠&amp;nbsp;올려주셔서&amp;nbsp;감사합니다.&lt;br /&gt;다름이&amp;nbsp;아니고&amp;nbsp;커리어와&amp;nbsp;관련하여&amp;nbsp;어떤&amp;nbsp;공부를&amp;nbsp;해야될지&amp;nbsp;모르겠어서&amp;nbsp;고민되는&amp;nbsp;상황입니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;저는&amp;nbsp;백엔드&amp;nbsp;개발자로&amp;nbsp;A라는&amp;nbsp;기술을&amp;nbsp;주로&amp;nbsp;사용하고&amp;nbsp;있는데&amp;nbsp;B를&amp;nbsp;사용하는게&amp;nbsp;전망이&amp;nbsp;좋을지&amp;nbsp;ㄱ을&amp;nbsp;사용하는게&amp;nbsp;커리어적으로&amp;nbsp;도움이&amp;nbsp;될지&amp;nbsp;판단이&amp;nbsp;안서는&amp;nbsp;상황입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 댓글을 저는 남겨본 경험이 있습니다. 이 때의 느낌은 마치 바다에 알지도 못하고 프리 스킨 스쿠버를 하는 느낌이 었습니다. 새로운 기술 스택이 나올 때마다 사용법을 빠르게 익혀서 적용하여 코드를 작성하고 문제가 발생하면 그 때마다 문제를 해결하고 더 나은 방법이 없나 구글링까지는 해보지만 문제의 원인이 무엇인지 완벽하게 파악하지 못하는 상태였습니다. 원리를 모른 채 &lt;b&gt;망망대해에서 일단 다이빙하는 상태&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;height: 98px;&quot; width=&quot;748&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;개발&lt;/th&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;다이빙&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;개발에서의 많은 방향을 탐색하여 적성 혹은 전망 있는 영역 찾기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;스쿠버 다이빙을 배를 타고 다이빙 스폿을 찾기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;해당 영역의 기술에 대해 심도 있게 학습&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;다이빙하기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHUu87/btr0HS9q4O7/IWaYM7ZI2S6kRw4J3kqZ31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHUu87/btr0HS9q4O7/IWaYM7ZI2S6kRw4J3kqZ31/img.png&quot; data-alt=&quot;숙련자가 아니라면 프리 다이빙은 매우 위험합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHUu87/btr0HS9q4O7/IWaYM7ZI2S6kRw4J3kqZ31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHUu87%2Fbtr0HS9q4O7%2FIWaYM7ZI2S6kRw4J3kqZ31%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;500&quot; height=&quot;333&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;숙련자가 아니라면 프리 다이빙은 매우 위험합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;img src=&quot;http://localhost:9425/images/da27b779-7b00-41b2-9b94-8c184bd8c747.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저는 어디에 다이빙을 해야될지 배는 어떻게 운전하는지 아무것도 모르고 일단 출발했다는 생각이 들었습니다&lt;/b&gt;. 어딘가에서 다이빙은 해야될텐데라는 이 곳이 내가 다이빙해도 되는 곳인가라는 의심이 많이 들었던 시기였습니다. 그렇게 여기저기 돌아다니면서 구현 속도에 초점을 맞추고 새로운 기술 트렌드에 깊이 없이 접근했었습니다. 이런 방식으로 커리어를 쌓아나아가다보니 명확한 한계가 서서히 보이기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈 앞에 바로 보이는 한계로써는 &lt;b&gt;이직이 가장 큰 문제&lt;/b&gt;였습니다. 더 많은 트래픽과 유의미한 트래픽을 받아볼 수 있는 서비스 기업으로 가고 싶다는 마음은 컸지만 Job Description과 동떨어진 나의 이력서를 보기도 하고 운 좋게 면접까지 갔더라도 &lt;code&gt;실질적인 트래픽을 받아 본 경험이 있나요?&lt;/code&gt; 라는 질문 하나에 열심히 적었던 포트폴리오가 무용지물이 되는 상황을 겪고나니 &lt;b&gt;어디서부터 잘못된 것인지 감조차 잡을 수 없었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1672fac0d8f9ca48.jpeg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgXJq5/btr3dEAXzoF/ou7rnEkshpymKBbkXIp9p1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgXJq5/btr3dEAXzoF/ou7rnEkshpymKBbkXIp9p1/img.jpg&quot; data-alt=&quot;실질적인 트래픽을 받은 경험이 있나요? / 그러려고 여기 오고 싶어요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgXJq5/btr3dEAXzoF/ou7rnEkshpymKBbkXIp9p1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgXJq5%2Fbtr3dEAXzoF%2Fou7rnEkshpymKBbkXIp9p1%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;300&quot; height=&quot;335&quot; data-filename=&quot;1672fac0d8f9ca48.jpeg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;715&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;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;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 글을 통해 얻었던 갈급함을, 현재 상황을 벗어나고자 하는 절박함을 다른 교육 프로그램을 들으면서 방향성으로 흘리지 않고 싶었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍이라는 것을 처음 접하거나 프론트/백엔드에 대한 전반적인 생태계에 대한 이해가 전무하다면 당연히 멘토링은 불필요하다는 생각이 들었습니다. 마치 드라마 일타 스캔들에서 나오는 유명 수학 강사가 이제 처음 수학을 배우는 단계에서는 더 많은 효과를 누릴 수 없는 것처럼 말입니다. 그래서 스스로에 대해 다시 점검했던 시간도 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 개발 시작할 때는 한 분야에 정말 최선을 다하여 GOAT가 되고 싶다는 목표도 있었습니다. 하지만 공부하면 할수록 저는 리눅스를 만든 토발즈도, 이더리움을 개발한 비탈리크 부테린 같은 개발자들과는 거리가 멀다는 것을 깨달았습니다. 게다가 이전 직장들에서 그 분야에서 정말 최고인 분들을 보면서 나는 GOAT가 될 수 없구나를 느끼면서 좌절했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌절하는 와중에도 포기하지 않았다는 것은 스스로 대견하다고 생각들지만 건강한 정신과는 멀다고 생각듭니다. 이런 &lt;b&gt;건강하지 못한 정신이 멘토링을 통해서 극복할 수 있을지 배움 이외에도 또 다른 기대&lt;/b&gt;도 하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모르는게 적을수록 적게 안다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'아는게 많을수록 모르는게 많아진다'를 반대로 '모르는 게 적을수록 많이 안다고 착각한다'라는 말로 체감하는 시기도 있었습니다..&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 시작 시에 Java만큼은 그래도 공부를 탄탄하게 진행했다고 (혼자서?) 생각하고 얼른 진도를 끝내 이직할 생각을 하게 됩니다. 지금 생각하면 부끄럽다는 생각도 들지만 한편으로는 그만큼 성장한 자신이 자랑스럽기도 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/do6A01/btr0HSO7JPZ/3hhLnymUn3tihe7nQkkPqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/do6A01/btr0HSO7JPZ/3hhLnymUn3tihe7nQkkPqk/img.png&quot; data-alt=&quot;Java만 하더라도 현재의 내가 과거의 나에게 질문을 한다면 하나도 답변 못하게 할 자신 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/do6A01/btr0HSO7JPZ/3hhLnymUn3tihe7nQkkPqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdo6A01%2Fbtr0HSO7JPZ%2F3hhLnymUn3tihe7nQkkPqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;215&quot; height=&quot;426&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2543&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Java만 하더라도 현재의 내가 과거의 나에게 질문을 한다면 하나도 답변 못하게 할 자신 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상담한 그대로 기본기를 다지고 멘토링을 받으면서 개성 있는 프로젝트도 진행했습니다. 기본기는 많은 영역들이 내포되어 있어 무엇이다라고 말할 수는 없지만 계속 쌓아나가면 나갈수록 기본기의 중요성에 대해 뼈저리게 느낍니다. 물론 여기서 말하는 기본기란 Java의 문법, SQL 문법만 의미하는 것이 당연히 아닙니다.&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;&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;411&quot; data-origin-height=&quot;799&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZ19sU/btr4Dr6r1ty/FxPTDVmhrDgFbkay3ajwo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZ19sU/btr4Dr6r1ty/FxPTDVmhrDgFbkay3ajwo1/img.png&quot; data-alt=&quot;멘토링을 진행하며 정리한 노트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZ19sU/btr4Dr6r1ty/FxPTDVmhrDgFbkay3ajwo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZ19sU%2Fbtr4Dr6r1ty%2FFxPTDVmhrDgFbkay3ajwo1%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;411&quot; height=&quot;799&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;799&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 순간에는 의문이 들었습니다. 이렇게만 하면 진짜 좋은 개발자가 될 수 있나?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네 혹은 아니오&lt;/b&gt;, 이론적으로 배웠을 뿐 체화되지 않은 상태였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 개발 얘기로 돌아온다면 Java, Spring, Database 에 대해 이론적으로 배웠을 뿐 실제로 직접 적용해본 경험도 없기 때문에 준비는 되었지만 부족한 상태입니다. 프로젝트를 시작해야만 했습니다. 이론을 깊이 있게 학습만 하면 우매함의 봉우리에 머물러 있게 됩니다. 멘토링에서는 이런 부분을 해소하고자 프로젝트를 진행하는 것이 아닐까 생각들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 멘토링 과정에서 프로젝트를 진행하고 Pull Request의 리뷰를 대응해나가야 합니다. 멘토링하는 동안의 멘토님은 더이상 계시지 않습니다. 코드 리뷰해주시는 리드 개발자만 계실뿐...&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;1562&quot; data-origin-height=&quot;1424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ncbrH/btr1TTTORhh/ek7cZFptCW0DCOOT72HJ91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ncbrH/btr1TTTORhh/ek7cZFptCW0DCOOT72HJ91/img.png&quot; data-alt=&quot;하나의 질문을 하셨지만 정말 많은 것을 답해야 됩니다. 왜냐하면 추가로 또 질문하실 테니까요.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ncbrH/btr1TTTORhh/ek7cZFptCW0DCOOT72HJ91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FncbrH%2Fbtr1TTTORhh%2Fek7cZFptCW0DCOOT72HJ91%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;645&quot; height=&quot;588&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;1424&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;하나의 질문을 하셨지만 정말 많은 것을 답해야 됩니다. 왜냐하면 추가로 또 질문하실 테니까요.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 과정을 계속 반복하고 프로젝트 마무리까지 진행하면 그동안 커리어를 이어나가면서 들었던 모든 의문들을 모두 해결할 수 있었습니다. 개발이 다이빙이라면 이제는 더 깊이 들어갈 수 있다는 자신감이 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 읽으시는 여러분들에게도  성장을 위해 혹은 목표를 위해 무엇이 필요한지 생각해보는 글이 되었으면 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EOD&lt;/p&gt;</description>
      <category>Review/Lecture</category>
      <category>F-Lab</category>
      <category>멘토링</category>
      <category>에프랩</category>
      <category>후기</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/25</guid>
      <comments>https://keydo.tistory.com/25#entry25comment</comments>
      <pubDate>Sat, 18 Mar 2023 20:41:08 +0900</pubDate>
    </item>
    <item>
      <title>F-lab 멘토링 후기(1/2) - 관객에서 다이버로</title>
      <link>https://keydo.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;멘토링 전의 나, 멘토링 중의 나, 멘토링 후의 나에 대한 내용을 담은 멘토링 리뷰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;[1편] 관객에서 다이버로&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;- 명화를 감상하려면&lt;br /&gt;- Intro.&lt;br /&gt;- 평범한 관람객이 멘토라는 전문가와 '모나리자'를 분석한다면?&lt;br /&gt;- 절망의 계곡을 너머 깨달음의 비탈길로 한걸음&lt;br /&gt;- 오롯이 홀로 서보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[2편] 개발이 다이빙이라면&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;- 개발자에게 필요한 것은?&lt;br /&gt;- 독학으로 채울 수 없는 것&lt;br /&gt;- 멘토링? 멘토? 구루?&lt;br /&gt;- 모르는게 적을수록 많이 안다.&lt;br /&gt;- 멘토링에서 하는 다이빙&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;F-lab을 고민하시는 분들 뿐만 아니라 저 또한 처해있는 상황이 모두 다를 것이기 때문에 '꼭 했으면 좋겠다'라는 의견보다 자신의 개발 실력이 더이상 성장하지 않는다라고 느끼며 답답함을 느끼거나 개발자로써의 커리어에 대한 고민을 하시는 분들이 리뷰를 통해 원하시는 실마리를 얻는데 도움이 되셨으면 좋겠습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1편은 멘토링을 진행하며 어떻게 변화했는지, 2편에서는 왜 F-lab 멘토링을 선택했는지에 대한 내용과 왜 변화했는지에 대한 내용을 담았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명화를 감상하려면&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;취미로 전시회를 정기적으로 가는 편입니다. 해당 분야 혹은 작가에 대해서 자세히 알게 되면 알수록 더 많은 것을 보고 느끼게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로그래밍의 세계도 명화 감상과 비슷하다고 느낍니다. 많은 회사와 개발자들이 사용하는 오픈 소스들은 나름대로의 철학을 갖고 있기도, 이전에 발생했던 문제들을 해결하기 위해 발전하면서 개발됩니다. 하지만 이전의 저는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;모나리자를 박물관의 모나리자구나하고 넘어갈 뿐&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;감상은 못 했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왜 이런 프레임워크가 나왔는지 왜 사용해야 하는지, 무엇이 성능을 좋게 하기 위한 기법인지 알지 못한채 말 그대로 지켜보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtO9Hh/btr0RJpNluO/MS0GdVw2pMknNCNkLHHQLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtO9Hh/btr0RJpNluO/MS0GdVw2pMknNCNkLHHQLK/img.png&quot; data-alt=&quot;멘토링의 받기 전에 이 정도 거리에서 기술을 바라보았습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtO9Hh/btr0RJpNluO/MS0GdVw2pMknNCNkLHHQLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtO9Hh%2Fbtr0RJpNluO%2FMS0GdVw2pMknNCNkLHHQLK%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;650&quot; height=&quot;446&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;멘토링의 받기 전에 이 정도 거리에서 기술을 바라보았습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모나리자는 왜 가치가 있는지 생각도 안해봤습니다. 하지만 &lt;b&gt;전문가 분들은 왜 이 작품이 가치가 있고 어떤 기법을 통해서 작품을 완성하고자 했는가&lt;/b&gt;를 알고 있으실 겁니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Intro.&lt;/h3&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;
&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;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;내가 하고 있는 백엔드는 단지 JSON을 만드는 역할만 하는 개발영역인가?&lt;br /&gt;알고리즘을 사용하는 빈도도 적은데 이런 경험들이 쌓이면 무슨 경력이 되는거지?&lt;br /&gt;초기 시작하는 단계가 아니라면 유지 보수만 하면 되는 것인가?&lt;br /&gt;새로 나오는 기술(WebFlux, Message Queue, K8S)들의 사용법을 익히면 내가 좋은 개발자가 되는 것인가?&lt;br /&gt;이렇게 경력이 쌓이는게 진짜 맞나? 괜찮은 것인가?&lt;br /&gt;코딩 테스트만 잘 보면 나도 좋은 기업에 갈 수 있는 것인가?&lt;br /&gt;나는 핵심 개발자가 될 관상인가?&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t5Jul/btr4vopPoxn/2Wt2yXfALlRcC5rg4DyRxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t5Jul/btr4vopPoxn/2Wt2yXfALlRcC5rg4DyRxK/img.png&quot; data-alt=&quot;방향성이 없이 적어놓았던 중구난방의 기술스택을 적어 놓은 흔적&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t5Jul/btr4vopPoxn/2Wt2yXfALlRcC5rg4DyRxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft5Jul%2Fbtr4vopPoxn%2F2Wt2yXfALlRcC5rg4DyRxK%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;510&quot; height=&quot;340&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;방향성이 없이 적어놓았던 중구난방의 기술스택을 적어 놓은 흔적&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;의문점들은 꼬리에 꼬리를 물면서 백엔드 개발자로서 가고자 하는 방향에 대한 확신도 줄었고 방향 조차도 모호해지고 있던 와중 동료 개발자가 신청해서 듣고 있다는 에프랩에 대해서 알게 되었습니다. 우리가 흔히 얘기하는 빅테크 기업 출신들이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;멘토&lt;/b&gt;로써 상위권 개발자가 될 수 있는 방향성을 제시받을 수 있다는 색다른 방향의 문구들도 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;평범한 관람객이 멘토라는 전문가와 '모나리자'를 분석한다면?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발자 입장에서 사용하는 라이브러리나 프레임 워크, 혹은 프로그래밍 언어의 내부까지 파악하지 않고 사용할 수 있는 강력한 개념이 있습니다. 바로 &amp;lsquo;추상화'입니다. 추상화를 통해서 데이터 베이스는 내부 구조를 몰라도 데이터를 읽고 쓰고 수정하고 지울 수 있고 내가 작성한 코드가 CPU에서 어떻게 작동 되는지 모르더라도 마치 모나리자를 보는 것은 누구나 할 수 있는 것처럼 사용할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만 차이점은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;개발자가 만드는 제품에서 사용하는 라이브러리, 프레임워크는 문제를 발생&lt;/b&gt;시킬 수 있습니다. 큰 규모의 오픈 소스 라이브러리나 프레임 워크를 사용한다면 처음으로 그 양에 압도됩니다. 무엇을 봐야 할 지, 어디서부터 봐야 할 지 감도 오지 않아 쉽게 포기하게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 멘토님이라는 전문가와 같이 모나리자를 같이 보면 재밌는 과정이 탄생합니다. 처음 모나리자를 볼 때 조금이라도 멘토님이 보는 시각을 같이 보고 싶어 기본기와 더불어 쉴새 없이 이어지는 질문들을 준비해서 멘토링에 임했습니다. 이런 과정이 끝이라면 좋았겠지만 &lt;b&gt;진짜 홀로 '모나리자'를 보려면 우매함의 봉우리를 넘어야합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;속으로 생각했습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;동시 사용 유저는 한 10만명은 충분히 쉽게 만들수 있지 않을까? 하지만 어리석음을 깨닫기까지 그리 오래걸리지 않았습니다.&lt;br /&gt;(깨달을 수 있는 기회가 생겼습니다.)&lt;/blockquote&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;803&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAmpUs/btr1XygOEQg/qGCh1XIuB0xWw2Z5ZnJGgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAmpUs/btr1XygOEQg/qGCh1XIuB0xWw2Z5ZnJGgK/img.png&quot; data-alt=&quot;YOUSINSA 프로젝트의 단순 로그인인데 성능이 처참한 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAmpUs/btr1XygOEQg/qGCh1XIuB0xWw2Z5ZnJGgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAmpUs%2Fbtr1XygOEQg%2FqGCh1XIuB0xWw2Z5ZnJGgK%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;600&quot; height=&quot;396&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;803&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;YOUSINSA 프로젝트의 단순 로그인인데 성능이 처참한 모습&lt;/figcaption&gt;
&lt;/figure&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;blockquote data-ke-style=&quot;style3&quot;&gt;동해 바다 한가운데에 혼자 다이빙하고 있다면...? 이것이 실제 서비스였다면..?&amp;nbsp;&lt;/blockquote&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;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우매함의 봉우리에 도착한 경우 더 깊게 학습이 필요하지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;그 당시의 편안함과 만족스러움에 멈춰있으면&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;반드시 실제 프로덕트를 만들면서 내가 아는 것은 아직 빙산의 일각이였구나, 혹은 수박 겉핥기를 하고 있었구나라는 깨닫고 순식간에 절망의 계곡에 빠져 흥미를 잃거나 Motivation을 잃고 헤맨 경험도 많이 했던 상황이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PeVQR/btr0VJ37ygD/wS1AQuDAIRuPutzn6nSNBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PeVQR/btr0VJ37ygD/wS1AQuDAIRuPutzn6nSNBK/img.png&quot; data-alt=&quot;동그라미가 처진 어딘가에 나는 위치할거다라는 생각을 했습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PeVQR/btr0VJ37ygD/wS1AQuDAIRuPutzn6nSNBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPeVQR%2Fbtr0VJ37ygD%2FwS1AQuDAIRuPutzn6nSNBK%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;500&quot; height=&quot;400&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;동그라미가 처진 어딘가에 나는 위치할거다라는 생각을 했습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;우매함의 봉우리를 넘어 절망의 계곡에 빠지더라도 깨달음의 비탈길로 진입하는 꿈을 가지고서 그렇게 멘토링을 시작&lt;/b&gt;했습니다.&amp;nbsp;&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;h3 style=&quot;color: #333333; text-align: start;&quot; 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;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;더 깊이 다양한 코드들을 봐야겠다는 생각과 행동이 이어졌습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&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;1510&quot; data-origin-height=&quot;1450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LCrj2/btr1YKgY9Zm/Omk3emVqfEc92Yc9tD5CRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LCrj2/btr1YKgY9Zm/Omk3emVqfEc92Yc9tD5CRK/img.png&quot; data-alt=&quot;Tomcat에 대하여 자료조사와 코드를 뜯어보면서 정리한 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LCrj2/btr1YKgY9Zm/Omk3emVqfEc92Yc9tD5CRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLCrj2%2Fbtr1YKgY9Zm%2FOmk3emVqfEc92Yc9tD5CRK%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;500&quot; height=&quot;480&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;1450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Tomcat에 대하여 자료조사와 코드를 뜯어보면서 정리한 문서&lt;/figcaption&gt;
&lt;/figure&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;프로젝트를 진행시에는 Redis 관련 동작에서 지원되지 않는 동작인데 구현한 라이브러리가 있어 분석하다가 실마리를 얻게 경험도 하게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;723&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czD6oF/btr1XxoHN7T/AMasfirfY6z4NKs41U4Ixk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czD6oF/btr1XxoHN7T/AMasfirfY6z4NKs41U4Ixk/img.png&quot; data-alt=&quot;[#11] 재고 관리는 어떻게 해야될까? - 2. Lua Script 발췌 내용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czD6oF/btr1XxoHN7T/AMasfirfY6z4NKs41U4Ixk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczD6oF%2Fbtr1XxoHN7T%2FAMasfirfY6z4NKs41U4Ixk%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;645&quot; height=&quot;574&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;723&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[#11] 재고 관리는 어떻게 해야될까? - 2. Lua Script 발췌 내용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;멘토링 과정을 통해 이론에 대해서 깊게 학습하고 프로젝트까지 진행하고 나니 이전과는 완전히 달라진 자신의 모습을 발견할 수 있었습니다. 이제 조금은 감상할 준비가 완료되었다고 생각이 들었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; 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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;멘토링을 마치고 이직 전선에 뛰어든 저는 더닝 크루거의 골짜기와 우매함의 봉우리를 수십번 왕복하는 상태가 되었습니다. 잘 안다고 생각하다가 문득 불안감이 스쳐지나가 더 자세히 파고들 때는 모든 것을 모르는 것 같이 느껴졌습니다. 하지만 단 하나 확실하게 달라진 것은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;'내가 무엇을 모르는가?'&lt;/b&gt;에 대한 확신이 들었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이직 과정에서 겪었던&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;기술 인터뷰를 생각해보더라도 모든 질문에 대해서 답하지는 못했습니다&lt;/b&gt;. 하지만 제가 배운 것은 기술들의 동작 원리나 사용 용도, 베스트 프랙티스가 아니니 홀로 다시 인터뷰를 되짚으며 나아갔습니다. 몰랐던 질문이거나 당황스러운 질문이 있었다면 지금이라도 가면 되는 곳이니 큰 걱정이 생기지는 않았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUIHi9/btr3efmKdQ5/fstBI4KpDtBn5C55R0vn10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUIHi9/btr3efmKdQ5/fstBI4KpDtBn5C55R0vn10/img.png&quot; data-alt=&quot;단단하게 만들기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUIHi9/btr3efmKdQ5/fstBI4KpDtBn5C55R0vn10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUIHi9%2Fbtr3efmKdQ5%2FfstBI4KpDtBn5C55R0vn10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;293&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;293&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;단단하게 만들기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 기술, 개념에 대해 내가 정확히 이해하고 있는지 확인하기 위해 정리했을 뿐 면접 문제 은행처럼 질문들을 모아 놓고 기계적인 대답은 최대한 지양하려고 했습니다. 기술 인터뷰는 말 그대로 인터뷰로써 서로가 기술에 대해 논의하고 확인하는 시간이므로 기계적인 답변은 오히려 마이너스가 되지 않을까라는 예상을 했었기 때문입니다.&lt;/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;지금은 '그때는 그랬지'라는 담담한 말투지만 실제로 경험할 때는 쉽지 않았습니다. 특히 가고 싶었던 회사 중 하나의 기술 면접도 아니라 Pre-Interview를 보고 정신적으로 많이 지쳐버린 순간도 왔었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;955&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu8So2/btr3c2OZ0cz/L5w1vQmB52If5yqhnOhL3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu8So2/btr3c2OZ0cz/L5w1vQmB52If5yqhnOhL3K/img.png&quot; data-alt=&quot;홀로 서는게 쉽지 않아 멘토님에게 도움을 요청하는 한장면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu8So2/btr3c2OZ0cz/L5w1vQmB52If5yqhnOhL3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu8So2%2Fbtr3c2OZ0cz%2FL5w1vQmB52If5yqhnOhL3K%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;955&quot; height=&quot;267&quot; data-origin-width=&quot;955&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;홀로 서는게 쉽지 않아 멘토님에게 도움을 요청하는 한장면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;멘토님에게 답변을 받고 다시 한걸음 나아가기 위해 블로그 한 편을 작성합니다.(해당 질문에 대해 얘기를 나눴던 캡쳐본이 유실됐네요.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://keydo.tistory.com/39&quot;&gt;https://keydo.tistory.com/39&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679118455150&quot; style=&quot;color: #333333; text-align: start;&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;[#12] YOUSINSA, 이대로 괜찮은가?&quot; data-og-description=&quot;개요 마지막 개선사항까지 포스팅을 완료했지만 그동안 면접, 포트폴리오 피드백을 다양하게 받으면서 알게 된 부분과 더불어서 이미 알고 있는 개선 필요 지점, 아쉬운 부분을 기록하기 위해 &amp;quot;&quot; data-og-host=&quot;keydo.tistory.com&quot; data-og-source-url=&quot;https://keydo.tistory.com/39&quot; data-og-url=&quot;https://keydo.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pbrZe/hyRTVkO0lC/kkDJIw1CTzLDHac2KCqPjK/img.png?width=537&amp;amp;height=240&amp;amp;face=0_0_537_240,https://scrap.kakaocdn.net/dn/kAaTs/hyRTQX6Ujg/tgtKKfrklt2ZObB5TszFf1/img.png?width=537&amp;amp;height=240&amp;amp;face=0_0_537_240,https://scrap.kakaocdn.net/dn/w2uF2/hyRTQjvn7e/lQlDSNj1WWKyDfJU5gvrJ0/img.png?width=2450&amp;amp;height=510&amp;amp;face=0_0_2450_510&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://keydo.tistory.com/39&quot; data-source-url=&quot;https://keydo.tistory.com/39&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pbrZe/hyRTVkO0lC/kkDJIw1CTzLDHac2KCqPjK/img.png?width=537&amp;amp;height=240&amp;amp;face=0_0_537_240,https://scrap.kakaocdn.net/dn/kAaTs/hyRTQX6Ujg/tgtKKfrklt2ZObB5TszFf1/img.png?width=537&amp;amp;height=240&amp;amp;face=0_0_537_240,https://scrap.kakaocdn.net/dn/w2uF2/hyRTQjvn7e/lQlDSNj1WWKyDfJU5gvrJ0/img.png?width=2450&amp;amp;height=510&amp;amp;face=0_0_2450_510');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;[#12] YOUSINSA, 이대로 괜찮은가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;개요 마지막 개선사항까지 포스팅을 완료했지만 그동안 면접, 포트폴리오 피드백을 다양하게 받으면서 알게 된 부분과 더불어서 이미 알고 있는 개선 필요 지점, 아쉬운 부분을 기록하기 위해 &quot;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;keydo.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;관객에서 프로 다이버의 길로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더이상 명화를 보면 그냥 지나치지 않고 감상할 수 있고 그에 맞춰 코드를 작성하기 위해 노력할 수 있는 단계에 올라와 있다고 느꼈습니다. 지금 필요한 것은 더 많은 딥다이브하는 경험이라고 느껴지는 와중에 핏이 정말 잘 맞는 회사를 만나게 되어 수습 기간을 지나 백엔드 엔지니어로 근무중입니다.&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 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: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;2편에서 계속됩니다&lt;/p&gt;</description>
      <category>Review/Lecture</category>
      <category>F-Lab</category>
      <category>멘토링</category>
      <category>에프랩</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/41</guid>
      <comments>https://keydo.tistory.com/41#entry41comment</comments>
      <pubDate>Sat, 11 Mar 2023 19:05:16 +0900</pubDate>
    </item>
    <item>
      <title>2022년의 회고록</title>
      <link>https://keydo.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;작년의 회고록은 2월이 돼서야 쓴 것을 보고 2022년의 회고록은 이번 해가 지나가기 전에 미리 써보려고 한다. 분명히 새해가 시작되기 전과 후는 바쁠 것이라는 예상과 새롭게 가는 곳에서 적응하기 위해 정신이 없을 것이란 느낌적인 느낌이 들기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;지금 다시 2021년의 회고를 보면 난 팀원으로써, 개발자로서, 나 자신으로써도 많이 부족했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나에게 2022년은...&lt;br /&gt;진짜 게임은 지금부터 시작이다.&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2022년 전반전&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022년의 첫 시작은 비참했다. 새해가 시작되면 흔히 회사에서는 새로운 마음으로 산뜻하게 일을 시작하곤했지만 새해가 시작됨과 동시에 소문이 들려오기 시작했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;회사 자금이 점점 바닥을 보이고 있다.&lt;/blockquote&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;2021년의 마지막에 생각했듯이 이직하겠다는 마음은 굳건했다. 하지만 고민해야 될 것은 시기였다. 다 만들어 놓은 프로젝트가 엎어지고 진행되고 있는 프로젝트에서 팀원간 감정의 골이 깊어지고 서서히 팀원들의 눈에서 '각자도생'을 생각하고 있다는 것이 서서히 느껴지기 시작한 때라는 점에서 시기를 결정하는 것이 분위기에 편승하려는 것이 아닌가라는 걱정도 많이 들었던 시기였다.&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;다짐했다시피 옆그레이드가 아니라 업그레이드를 하고싶었다. 연봉이나 회사 규모를 떠나서 더 가파르게 성장할 수 있는 곳으로 가고 싶다는 생각이 너무나 간절했다. 그래서 진행하고 있는 프로젝트를 모두 마무리하고 깔끔하게 떠나자고 결정했다. 그렇게 기획했던 프로젝트를 마무리하고 보니 3월이 덜컥 되었다. 정말 친했던 팀원들이 다른 회사에 오퍼를 받아 떠나기 시작했다.&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;회사 내부 사정이 호전될 기미가 보이지 않았다. 최근 팀장이 된 분도 곧 퇴사해서 다른 곳으로 가신다고 갑작스럽게 얘기를 하시고 퇴사 결정을 내렸다. 이 와중에 시니어 백엔드 개발자로 오신 30년 경력의 팀장님이 오셨으나 5일 정도 일하시고 그만두셨다.&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;(Python 스택이셨는데 Java 스택의 기술들이 버겁다고 하셨다.)&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 와중에 떠나게 된 친한 동료가 소개시켜준 멘토링 프로그램을 신청했다. 마지막 배수진을 친다는 생각으로 신청했다. 무언가를 더 만들고 새로운 기술 스택을 익힌다고 해도 딱 그 정도일뿐 성장한다는 생각이 별로 안 들던 순간이었는데 멘토링을 받으면서 성장한다는 느낌을 조금씩 받기 시작했다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;회사 자금을 확보하기 위하여 이미 사용되고 있는 서비스의 완전한 기술이전이 체결됐다. 생각해보니 남은 백엔드 엔지니어는 나밖에 없었다. 구성원 중 서비스 자체를 기술 이전 해본 경험은 아무도 없었기 때문에 기술 이전에 대한 범위를 정하기 위한 많은 논의 끝에 코드, 기술 문서를 넘겨주는 것으로 결론이 났다. 코드를 한번도 커밋하지 않은 프로젝트였지만 이것이 마지막 프로젝트라는 생각으로 유종의 미로 마무리 짓기 위해 의존성에 따라 분석해서 코드를 정리하고 만들어져 있지 않았던 기술문서를 완성했다.&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;팀장님 퇴사 일주일 전 말씀하셨다. 아마 이 프로젝트의 끝은 실제 서비스를 구매하는 곳에 설치하는 것일 것이라고, 하지만 불가능한 일을 할 필요 없다고 그전에 나가야 된다고 하셨다.&lt;/blockquote&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;이미 클라우드 서비스 자체에 의존성을 갖게 만들어 놓은 서비스인데 고객사에서는 클라우드를 사용할지 혹은 온-프레미스로 사용할지 아무것도 결정나지 않았고 자문을 맡길 회사를 구해 검수를 받을 예정이라고 전달받았다. 하지만 이것은 나와 상관 없는 일이라고 한편으로는 마음 먹고 있었다.&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-ke-size=&quot;size16&quot;&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;정말 고민이 많은 상황이였다. 커리어를 끊김 없이 이어가고 싶었지만 해당 프로젝트에 참여하여 대응하게 된다면 이직 타이밍부터 많은 부분이 어긋날 것이 눈에 보였다. 하지만 조금만 더 있으면 병역을 마무리 지을 수 있는 동료, 아이들이 있는 동료까지 생각했을 때 지금 나가는 게 옳은 것인가 아니면 내 앞길을 먼저 챙기는 게 맞는 것인가에 대한 고민이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;지금 바로 퇴사하겠다는 말이 계속 입 밖으로 나오려고 하는 점에서 나는 많이 부족하다고 느꼈다. 개발자로써도... 팀원으로써도...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 순간에서 누군가는 이기적이다, 자신밖에 모르는 사람이다라고 말할 수도 있다는 두려움이나 걱정보다는 나는 스스로 증명하고 있는가에 대한 두려움이 앞섰다. 그래서 수락했다. 내 코드가 한 줄도 없는 프로젝트라도 내 통제 밑에 둘 수 있다고 갑자기 생각했다. 그렇게 증명해 보자는 다짐을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작은 꽤나 순조로운 듯 보였다. 문서와 코드만 넘기면 고객사에서 잘 할 수 있다는 응답을 받았기 때문이다. 하지만 역시 처음에 뭔가 잘 돼 간다고 느끼는 만큼 후에 있을 힘듦과 비례하는 것이라는 것을 잊고 있었다. 고객사는 퇴사한 팀장의 예상대로 완벽한 설치와 운영을 보장하는 것을 원했고 그렇게 Task Force 팀이 만들어지고 참여하게 되었다. 다행히도 DevOps 개발자 분이 계셔서 DevOps를 주축으로 백엔드, 프론트, AI 까지 총 4명의 인원이 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;이 과정에서 우리가 보통 얘기하는 코드의 유지 보수성에 대해서 많은 생각을 하게 되었다. 내가 속해 있는 프로젝트가 아니라 에러 로그를 보지 않았었는데 몇달 동안 쌓인 30만건의 같은 에러 로그를 파악하여 수정하고 메모리가 지속적으로 부족해지는 현상에 대해 파악 후 수정하는 부분도 병행되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크에 연결되어 있는 온프레미스 환경에 온전히 설치하는 것을 목표로 일정이 시작되었다. 너무나 당연하게 클라우드를 통해서 쉽게 작업했던 일들에 시간이 계속 투자된다고 느껴졌다. 젠킨스를 통해 빌드하는 부분, ArgoCD를 통해 배포하던 것까지 모두 수동으로 진행했다. 하지만 이것보다 문제가 되는 부분은 클라우드에서 편하게 사용하는 서비스들에 대해서 의존성을 모두 끊어내야 했다. 오브젝트 스토리지에 대한 의존성, PubSub에 대한 의존성이 가장 걷어내기 쉽지 않았다. 언제가 될지 모르는 설치 일자 전까지 매일매일 야근하면서 결국 온프레미스 환경에 모든 서비스를 옮기는 것을 준비하였고 설치 당일이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;개발에서 완벽한 것은 없다라고 생각한다. 그렇기 때문에 설치 당일 '모든 준비가 완벽했다'라고 느꼈던 것은 앞으로 일어날 일에 대한 복선이었을지도 모른다는 생각이 든다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객사의 개발자분들과 전반적인 포팅 과정을 같이 진행하는 방향으로 얘기가 오고 갔고 이에 맞춰서 한 스텝씩 나가기 위해 먼저 서버를 데브 옵스 분께서 세팅하시고 AI/프론트엔드/백엔드가 후에 들어가는 시나리오였다. 중간중간 고객사의 네트워크 설정에 맞춰서 수정이 필요하고 규약을 논의할 필요가 있어서 딜레이 된 부분 이외에는 모든 것이 순조로웠다. 이제 필요한 것은 서비스의 완벽한 동작과 기존 데이터의 마이그레이션이었다. 그렇게 점심에 시작한 작업이 다음날 저녁에 되서야 끝이 났다.&lt;/p&gt;
&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주 정도는 완벽하게 이직 준비모드가 되지는 않았다. 벌써 시간은 흘러 7월이 되어갔다.&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;2022년 후반전&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반전 종료 휘슬이 전에 막판 스퍼트를 달렸던 탓일까 체력적으로 쉽지 않았지만 나름대로 목표와 전략을 세웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;일이 많아 힘들더라도 성장할 수 있는 곳으로 이직하자는 목표와 함께 힘든 와중에도 이어왔던 멘토링 관련 토이 프로젝트에 모든 것을 거는 배수진의 전략을 세웠다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 판단이 가능했던 것은 구상한 전략 중에 가장 현실성이 있고 나라는 개발자에 대한 상품성을 높일 수 있을 것이란 판단이었다. 구상한 이직 전략은 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;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;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;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;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;프로젝트 진행 도중 같이 진행하시는 파트너 멘티는 다른 부분에 초점을 맞추기 위해 프로젝트 진행에서 하차하셨다. 혼자가 아니라 함께 진행한다면 더 많은 것을 해볼 수 있었을 텐데라는 아쉬움은 남았지만 적절하게 기획을 축소하는 방향으로 결정하여 마무리를 목표로 달려 나갔다. 그렇게 토이 프로젝트 'YOUSINSA'가 시작되었다.&lt;/p&gt;
&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://keydo.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://keydo.tistory.com/20&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677252475679&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;YOUSINSA 프로젝트&quot; data-og-description=&quot;프로젝트 소개 MUSINSA, 29CM, ZIGZAG와 같은 패션 도메인의 E-commerce 서비스를 개발하는 프로젝트입니다. 웹 서비스 전반적인 개발을 진행하기보다 안정적으로 트래픽을 처리하기 위한 백엔드 개발을&quot; data-og-host=&quot;keydo.tistory.com&quot; data-og-source-url=&quot;https://keydo.tistory.com/20&quot; data-og-url=&quot;https://keydo.tistory.com/20&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wkPqB/hyRKTmleJX/7jOFYjIyVL4TAKB75fQark/img.png?width=706&amp;amp;height=457&amp;amp;face=0_0_706_457,https://scrap.kakaocdn.net/dn/RC9Gb/hyRJTVMyUp/xZGD9m5rHi0TChswOT6tG1/img.png?width=706&amp;amp;height=457&amp;amp;face=0_0_706_457,https://scrap.kakaocdn.net/dn/b87jDj/hyRKQwnYAQ/NOk4WQYVJvk8ilacPWTbWK/img.png?width=900&amp;amp;height=446&amp;amp;face=0_0_900_446&quot;&gt;&lt;a href=&quot;https://keydo.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://keydo.tistory.com/20&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wkPqB/hyRKTmleJX/7jOFYjIyVL4TAKB75fQark/img.png?width=706&amp;amp;height=457&amp;amp;face=0_0_706_457,https://scrap.kakaocdn.net/dn/RC9Gb/hyRJTVMyUp/xZGD9m5rHi0TChswOT6tG1/img.png?width=706&amp;amp;height=457&amp;amp;face=0_0_706_457,https://scrap.kakaocdn.net/dn/b87jDj/hyRKQwnYAQ/NOk4WQYVJvk8ilacPWTbWK/img.png?width=900&amp;amp;height=446&amp;amp;face=0_0_900_446');&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;YOUSINSA 프로젝트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 소개 MUSINSA, 29CM, ZIGZAG와 같은 패션 도메인의 E-commerce 서비스를 개발하는 프로젝트입니다. 웹 서비스 전반적인 개발을 진행하기보다 안정적으로 트래픽을 처리하기 위한 백엔드 개발을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;keydo.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;그렇게 만든 프로젝트를 바탕으로 포트폴리오를 작성했다. 이전에는 쓸 내용이 많이 없어서 하나라도 더 만들기 위해 쓰는 행위를 반복했다면 이제는 오히려 임팩트 있는 부분만 남기기 위해 많이 지워나간 것이 이전과의 차이점이었다.&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;720&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k3F6e/btr0JNZ8Pqa/vLdknzSe1168JehvDWTDk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k3F6e/btr0JNZ8Pqa/vLdknzSe1168JehvDWTDk1/img.png&quot; data-alt=&quot;계속 작성했던 이력서 목록들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k3F6e/btr0JNZ8Pqa/vLdknzSe1168JehvDWTDk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk3F6e%2Fbtr0JNZ8Pqa%2FvLdknzSe1168JehvDWTDk1%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;532&quot; height=&quot;427&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;578&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;&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;blockquote data-ke-style=&quot;style3&quot;&gt;면접 과정에서 배운 점들은 따로 적어놓으려고 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 탈락의 고배를 계속 마시고 있던 와중 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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 2022년의 막차 이직을 목표했던 것을 이룰 수 있는 기업에 탑승하게 되어 현재도 다니고 있다. 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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Note/회고</category>
      <category>2022</category>
      <category>성장</category>
      <category>회고</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/40</guid>
      <comments>https://keydo.tistory.com/40#entry40comment</comments>
      <pubDate>Tue, 10 Jan 2023 22:49:26 +0900</pubDate>
    </item>
    <item>
      <title>[#12] YOUSINSA, 이대로 괜찮은가?</title>
      <link>https://keydo.tistory.com/39</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 개선사항까지 포스팅을 완료했지만 그동안 면접, 포트폴리오 피드백을 다양하게 받으면서 알게 된 부분과 더불어서 이미 알고 있는 개선 필요 지점, 아쉬운 부분을 기록하기 위해 &quot;이대로 괜찮은가&quot; 편을 남기려고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nxsG9/btrQVZWgaP3/oDA8JEhhE2z8eMbpZzfGzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nxsG9/btrQVZWgaP3/oDA8JEhhE2z8eMbpZzfGzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nxsG9/btrQVZWgaP3/oDA8JEhhE2z8eMbpZzfGzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnxsG9%2FbtrQVZWgaP3%2FoDA8JEhhE2z8eMbpZzfGzK%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;537&quot; height=&quot;240&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이렇게 기록된 사항을 바탕으로 다시 프로젝트를 이어서 진행하면서 개선해나가면서 최종적인 목표인 Version 4까지 달려가기 위한 증거로 남기고 싶었습니다.&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;867&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F2QER/btrQV0VkwlE/3HYomQxZOL5zKZfrjrU6vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F2QER/btrQV0VkwlE/3HYomQxZOL5zKZfrjrU6vK/img.png&quot; data-alt=&quot;YOUSINSA Version 정의&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F2QER/btrQV0VkwlE/3HYomQxZOL5zKZfrjrU6vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF2QER%2FbtrQV0VkwlE%2F3HYomQxZOL5zKZfrjrU6vK%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;867&quot; height=&quot;551&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;YOUSINSA Version 정의&lt;/figcaption&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;YOUSINSA 프로젝트 테스트 시나리오의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선을 진행해서 목표한 동시 사용자 500명을 기준으로 각각의 API에 대해서 1250 TPS는 달성했습니다. 하지만 실서비스에서도 정상적으로 돌아갈 것이라 낙관할 수 있을까라는 의문이 들었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chqi0q/btrQTQzvs54/jdBmIkkjL7oQjfv563jdX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chqi0q/btrQTQzvs54/jdBmIkkjL7oQjfv563jdX0/img.png&quot; data-alt=&quot;YOUSINSA 개선 그래프&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chqi0q/btrQTQzvs54/jdBmIkkjL7oQjfv563jdX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchqi0q%2FbtrQTQzvs54%2FjdBmIkkjL7oQjfv563jdX0%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;1000&quot; height=&quot;510&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;YOUSINSA 개선 그래프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실서비스에서도 정상적으로 문제없이 돌아갈 것이라고 확신하게 되는 경우는 무엇이 있을지 고민했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;실제 유저가 직접 사용하는 시나리오대로 테스트를 진행하면 오차가 줄어들지 않을까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 유저는 어떻게 물품을 구매할지 고민해보면 로그인, 물품 조회, 상세 물품 조회, 물품 구매까지 일련의 프로세스로 간단하게 시나리오를 구성할 수 있을 것이라 예상됩니다. 주문 취소 혹은 상품 이미지와 관련된 기능 등은 기본 시나리오의 파생이라고 생각됩니다. 각각의 API는 목표 TPS를 달성했지만 더 큰 단위의 프로세스로 구성하게 된다면 목표 TPS를 달성할 수 있을지 고민해보면 &lt;b&gt;주어진 테스트 결과로는 판단이 되지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2450&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGpXcn/btrQSZcNocH/d08VJko5jag6nqIVpdpuPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGpXcn/btrQSZcNocH/d08VJko5jag6nqIVpdpuPk/img.png&quot; data-alt=&quot;각 단계 테스트에 대한 파생 테스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGpXcn/btrQSZcNocH/d08VJko5jag6nqIVpdpuPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGpXcn%2FbtrQSZcNocH%2Fd08VJko5jag6nqIVpdpuPk%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;2450&quot; height=&quot;510&quot; data-origin-width=&quot;2450&quot; data-origin-height=&quot;510&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;가상의 시나리오만 생각해보더라도 모든 API를 독립적으로 생각해 각 API가 목표에 다다르면 실서비스에서도 문제가 없을 것이라 예상했지만 Database, Redis와 같이 여러 관계가 있는 Component로부터의 다양한 상호 연관을 고려하면 더 정밀한 시나리오가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 서비스가 더 성장하게 되어 MSA 환경으로 구축되어 있는 경우에도 각각의 Domain Application Server들이 격리되어 있지만 분산 트랜잭션, Message Queue, Server Component 간의 통신도 고려해야 되기 때문에 더더욱 사용자가 사용할 시나리오에 가깝게 테스트해야 하므로 현재 진행한 테스트의 경우 불완전합니다.&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;YOUSINSA 프로젝트 Architecture의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLA(Service Level Agreement)라는 용어는 서비스 수준 협약서라고 말하며 공급자와 사용자 간에 서비스에 대하여 측정지표, 목표 등에 대한 협약서를 지칭합니다. SLA에서 중요한 지표로는 다운 타임과 연관되어 99.9~99.999로 표시하는 서비스 수준 지표가 있습니다. 1년 중 어느 정도의 다운 타임을 보장할 것인지 나타냅니다. 특히 서비스의 서버는 1년 365일 무중단으로 운영되어야 고객들이 아무 문제없이 사용하여 수익률을 극대화하고 나아가 고객 경험도 개선할 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 필요한 것은 바로 Availability(가용성)입니다. 현재 Infra Architecture는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mHb1V/btrQT6IRVLL/m197G6HWdCvJGDGXZwmAjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mHb1V/btrQT6IRVLL/m197G6HWdCvJGDGXZwmAjK/img.png&quot; data-alt=&quot;현재 Infra Architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mHb1V/btrQT6IRVLL/m197G6HWdCvJGDGXZwmAjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmHb1V%2FbtrQT6IRVLL%2Fm197G6HWdCvJGDGXZwmAjK%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;900&quot; height=&quot;446&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;현재 Infra Architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;과연 현재 Infra Architecture는 가용성이 높다고 말할 수 있을까요? NOPE&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;높은 가용성(HA)의 반대말이라고 생각되는 SPOF(Single Point of Failure)는 서비스에 치명적일 수 있습니다. 왜냐하면 하나의 지점이 문제가 있을 뿐인데 서비스 전체가 사용불가가 되기 때문입니다. 제가 생각하는 SPoF 지점은 4곳입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Load Balancer&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Application Server Event Publisher(▲, 정확한 의미의 SPOF는 아님)&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;b&gt;Load Balancer&lt;/b&gt;가 Fail 되면 Application Server로 Request가 전달이 안 될 것이기 때문에 서비스가 사용불가 상태가 됩니다. 다음으로 &lt;b&gt;Redis&lt;/b&gt;는 Session 관리와 재고 관리를 하기 위해 사용하고 있습니다. 이 부분에서 Session Storage Redis가 Fail 된다면 로그인 관련 서비스들이 모두 사용할 수 없으므로 서비스를 이용할 수 없게 됩니다. 다음으로 재고 관련 Caching Redis가 Fail 된다면 재고의 정합성을 보장할 수 없는 상태로 운용되므로 정상적인 UX를 제공할 수 없습니다. 그리고 &lt;b&gt;Database&lt;/b&gt;의 경우 모든 API에 관여하므로 Fail 되면 전체 서비스가 수행되지 않습니다. &lt;br /&gt;&lt;br /&gt;마지막으로 만약 하나의 &lt;b&gt;Application Server&lt;/b&gt;가 Fail 된다면 어떻게 될지 고민했습니다. 구매 주문이 접수되지 않은 경우 영향이 없을 것이나 구매 주문이 접수되어 진행되고 있는 중 Fail 된다면 해당 Event는 사라져 버릴 것입니다. 따라서 해당 Event를 발생시킨 구매 주문 요청은 재고 차감이 진행되지 않고 주문 상태는 진행 중 상태에 머물러 있으므로 단일 실패 지점으로 판단했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;그래서 어떻게 SPoF를 방지할 것인가? 다중화가 필요, Primary-Secondary구조, Health-Check, Messaging-Queue&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 잘못된 부분이 있다면 피드백 감사합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPoF를 방지하기 위해서는 다중화, Health-Check, Message-Queue가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Load Balancer는 최근 MSA 환경에서 SPoF 가능성, 서버 Resource 소모를 이유로 Service Discovery 방식으로 구성하거나 Docker 환경으로 구성되어 있다면 k8s, Docker Swarm과 같이 Container Orchestration을 사용, 혹은 Load Balancer의 설정을 통하여 Fail 환경에 대비할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 더 정확히 표현한다면 단일 실패 지점(SPOF)에서 자유롭지 못한 Load Balancer를 제거하고 다른 방식의 접근도 고려해야합니다.&lt;/p&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 또한 Redis Clustering, Redis Sentinel을 통해 SPOF를 방지할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mH9xD/btrQUuW410e/YnQjBpw4oBKPguMVlmGfi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mH9xD/btrQUuW410e/YnQjBpw4oBKPguMVlmGfi0/img.png&quot; data-alt=&quot;Redis Sentinel&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mH9xD/btrQUuW410e/YnQjBpw4oBKPguMVlmGfi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmH9xD%2FbtrQUuW410e%2FYnQjBpw4oBKPguMVlmGfi0%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;900&quot; height=&quot;357&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Redis Sentinel&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;MySQL의 경우 Replication을 지원하여 Primary-Secondary 구조를 만들어 가용성을 높일 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Master - Slave란 용어와 혼용해서 사용하기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Message-Queue의 경우 Event를 Publish 하는 부분을 담당하여 Event 혹은 Message가 유실되지 않도록 처리를 하여 잘못된 동작이 되지 않도록 할 수 있습니다.&lt;br /&gt;* 이 부분에서는 의견이 틀릴 수 있다고 생각이 들어 개인적인 의견도 남깁니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Application Server가 Fail 되더라도 전체 시스템 자체가 Fail되는 것은 아니기 때문에 정확한 의미의 단일 실패지점으로 볼 수는 없습니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyOV2U/btrQVqzRkFO/pVQhrrcPh9I14lLhiDfz6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyOV2U/btrQVqzRkFO/pVQhrrcPh9I14lLhiDfz6K/img.png&quot; data-alt=&quot;Message Queue&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyOV2U/btrQVqzRkFO/pVQhrrcPh9I14lLhiDfz6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyOV2U%2FbtrQVqzRkFO%2FpVQhrrcPh9I14lLhiDfz6K%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;1000&quot; height=&quot;199&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;199&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Message Queue&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;h3 data-ke-size=&quot;size23&quot;&gt;YOUSINSA 프로젝트 설계의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 서버에서 발생하는 병목 지점이 될 확률이 높은 부분을 네트워크와 데이터 베이스를 지목합니다. 그렇다면 당연히 이런 병목 지점들이 발생하지 않도록 설계하는 것이 기본적인 원칙으로 생각됩니다. 하지만 이번 프로젝트를 설계를 시작할 때부터 개발을 진행할 때까지 지속적으로 문제가 되었던 부분은 Database 부분의 병목이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 경험하지 않았지만 익히 알고 있는 문제점을 알고 있음에도 Database 중심적인 설계를 진행한 부분은 문제가 있다고 느껴집니다.&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;* 책 중 하나인 'Data Intensive Application Design'에서 말하는 데이터 중심 설계와는 관계없습니다.&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;이번 기회를 통해 직접 경험했으니 추후의 설계에서는 Database에 모든 상태를 담아 관리하는 것이 아니라 분산 환경을 고려하여 설계하고 모든 상태를 Database에 의존적이지 않게 설계할 필요성이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;추가적으로 Database에서 병목이 발생하는 것은 Read/Write를 분리해서 생각할 수 있습니다. 개선 작업에서 특히 문제가 되는 부분은 Write(INSERT, UPDATE, DELETE)에서 실행되는 Lock에 의해서 발생했습니다. 따라서 이런 부분을 분리하여 Source-Replica 구조를 적용하여 Read는 Replica DB, Write는 Source DB로 쿼리 요청을 분배함으로써 성능적인 개선을 이룰 수 있을 것이라 예상합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REF&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/message-queue/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://aws.amazon.com/ko/message-queue/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1668086180573&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;company&quot; data-og-title=&quot;메시지 대기열이란 무엇입니까?&quot; data-og-description=&quot;최신 클라우드 아키텍처에서는 애플리케이션이 좀 더 쉽게 개발, 배포 및 유지 관리할 수 있는 더 작고 독립적인 빌딩 블록으로 결합 해제됩니다. 메시지 대기열은 이러한 분산 애플리케이션을 &quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/message-queue/&quot; data-og-url=&quot;https://aws.amazon.com/ko/message-queue/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dCozZu/hyQxNAQQdl/PRBPFENBYiDD6jHj1GQ7kK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dLzUJd/hyQxIGjvSi/NUTtYersJNiyOPvimhOWtK/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109,https://scrap.kakaocdn.net/dn/bvvkmT/hyQxJrGsDF/GilJHuc8PgkynUt3628TFk/img.png?width=1502&amp;amp;height=845&amp;amp;face=0_0_1502_845&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/message-queue/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/message-queue/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dCozZu/hyQxNAQQdl/PRBPFENBYiDD6jHj1GQ7kK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dLzUJd/hyQxIGjvSi/NUTtYersJNiyOPvimhOWtK/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109,https://scrap.kakaocdn.net/dn/bvvkmT/hyQxJrGsDF/GilJHuc8PgkynUt3628TFk/img.png?width=1502&amp;amp;height=845&amp;amp;face=0_0_1502_845');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;메시지 대기열이란 무엇입니까?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최신 클라우드 아키텍처에서는 애플리케이션이 좀 더 쉽게 개발, 배포 및 유지 관리할 수 있는 더 작고 독립적인 빌딩 블록으로 결합 해제됩니다. 메시지 대기열은 이러한 분산 애플리케이션을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project/YOUSINSA</category>
      <category>CQRS</category>
      <category>Distributed system</category>
      <category>F-Lab</category>
      <category>ha</category>
      <category>Message Queue</category>
      <category>Raft Election</category>
      <category>Service Discovery</category>
      <category>YOUSINSA</category>
      <category>이대로 괜찮은가?</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/39</guid>
      <comments>https://keydo.tistory.com/39#entry39comment</comments>
      <pubDate>Fri, 11 Nov 2022 00:15:28 +0900</pubDate>
    </item>
    <item>
      <title>[#11] 재고 관리는 어떻게 해야될까? - 3. Eventually Consistency</title>
      <link>https://keydo.tistory.com/38</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 진행했듯이 Lua Script를 적용함으로써 Redis의 Atomic한 재고 관리를 수행할 수 있었습니다. 곧바로 nGrinder를 통해 트래픽을 발생시켜 이전보다 개선되었는지 확인하고 싶은 마음이 굴뚝같았으나 다시 한번 고민에 빠졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;왜 항상 Database는 구매 시마다 차감되는 재고를 기록해야될까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. &lt;a href=&quot;https://keydo.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[#10] 재고 관리 Integrity 문제&lt;/a&gt; 포스팅을 해결하는 시점에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재고를 실시간으로 차감하여 실물상 재고와 서비스 상의 재고를 일치시켜 고객 경험을 높이기 위해서 구매 시마다 Database에서의 실시간 재고 차감이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B. &lt;a href=&quot;https://keydo.tistory.com/36&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[#11] 재고 관리는 어떻게 해야 될까? - 1. Redis Transaction&lt;/a&gt; 포스팅부터 &lt;a href=&quot;https://keydo.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[#11] 재고 관리는 어떻게 해야될까? - 2. Lua Script를&lt;/a&gt; 적용하는 시점에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재고는 Cache Layer에서 실물상 재고와 서비스 상의 재고를 일치시키므로 Database에서의 실시간 재고 차감은 불필요합니다.&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;처음 문제를 정의하고 해결 방법들을 고안한 시점과는 다르게 이제 Database는 실시간으로 재고 차감이 필요 없다고 생각됐습니다. 비즈니스 요구 사항(실물과 서비스 재고의 일치)을 Cache Layer의 책임으로 옮김으로써 Database는 더 이상 해당 요구 사항에 대한 책임을 지지 않아도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 구매 주문 과정을 Sequence Diagram으로 표현해본다면 다음과 같을 것입니다.&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;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tagoV/btrQTk8ITU3/3dZoIj7EAET0lqoTJ6ZzgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tagoV/btrQTk8ITU3/3dZoIj7EAET0lqoTJ6ZzgK/img.png&quot; data-alt=&quot;구매주문 과정 Sequence Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tagoV/btrQTk8ITU3/3dZoIj7EAET0lqoTJ6ZzgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtagoV%2FbtrQTk8ITU3%2F3dZoIj7EAET0lqoTJ6ZzgK%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;900&quot; height=&quot;194&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구매주문 과정 Sequence Diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0. 상위 레이어인 PurchaseOrderAssembleService에서 주문 접수&lt;/p&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. 하위 레이어인 ProductOptionReadService에서 해당 구매 물품 Entity 획득&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Repository Layer를 통해 Database에서 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;2. 하위 레이어인 UserReadService에서 구매자 Entity를 획득&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Repository Layer를 통해 Database에서 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;3. 하위 레이어인 PurchaseCreateService에서 구매 주문서 작성(저장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Repository Layer를 통해 Database에서 INSERT 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 하위 레이어인 ProductOptionStockService에서 구매 상품의 재고 차감&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Cache Layer를 통해 Redis에서 재고 차감&lt;br /&gt;- Repository Layer를 통해 Database에서 UPDATE 실행(Lock)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 순서대로 실행된다면 Database도 실시간으로 재고를 차감하며 비즈니스 요구 사항에 대한 책임을 함께 수행하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pk73z/btrQOPtBXhe/OlQPwJUS9ojcybaJfKQjC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pk73z/btrQOPtBXhe/OlQPwJUS9ojcybaJfKQjC1/img.png&quot; data-alt=&quot;Database Node Monitoring / 위 - DB Lock, 아래 - Distributed-Lock&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pk73z/btrQOPtBXhe/OlQPwJUS9ojcybaJfKQjC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPk73z%2FbtrQOPtBXhe%2FOlQPwJUS9ojcybaJfKQjC1%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;900&quot; height=&quot;721&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Database Node Monitoring / 위 - DB Lock, 아래 - Distributed-Lock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에도 언급했듯이 Database에서의 UPDATE는 Lock을 동반하므로 IO wait를 동반합니다. 그렇다면 결국 테스트를 해보지 않아도 이전과 같은 결과가 나올 것이 예상되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Consistency에도 종류가 있다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 일련의 고민들에서 대해서 조사를 하던 중 Consistency의 종류에 대해서 알게 되었습니다. Strong, Weak, Eventually Consistent 이외에도 더 자세히 분류할 수도 있지만 현재는 이 정도만 언급하겠습니다.&lt;/p&gt;
&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;현재까지 설계한 구조는 Strong Consistency 하므로 실시간으로 정합성이 보장되지만 이로 인해 TPS Performance는 Trade-Off 하게 되었습니다. 하지만 Cache Layer를 Database 앞단에 배치함으로써 Cache를 통해서는 Strong Consistency를 만족하고 있습니다. Database는 데이터의 영속성을 위해 사용되므로 최종적으로는 실제 재고를 갖고 있어야 하고 실시간으로는 재고가 틀려도 서비스에는 무관합니다. Eventaully Consistency!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다른 고민이 시작되었습니다. 어떻게 Eventually Consistency 하게 만들 수 있을까?&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;Publish/Subscribe 패턴을 Spring에서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Eventually Consistency를 구현하는 여러 가지 방법을 생각하던 중 디자인 패턴의 Pub/Sub 패턴이 가장 먼저 생각났습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfyhNs/btrQNHXF5w0/E1vWWQCP1O0aqAMYurHsC0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfyhNs/btrQNHXF5w0/E1vWWQCP1O0aqAMYurHsC0/img.jpg&quot; data-alt=&quot;Event Publish(?)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfyhNs/btrQNHXF5w0/E1vWWQCP1O0aqAMYurHsC0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfyhNs%2FbtrQNHXF5w0%2FE1vWWQCP1O0aqAMYurHsC0%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;400&quot; height=&quot;227&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;227&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Event Publish(?)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event를 발행하는 Publisher와 Event를 수신하는 Subscriber는 구조적으로 떨어져 있다는 부분을 실마리로 생각했습니다. Publisher에서는 구매 주문에 대한 요청만 진행하고 실제 차감은 구매 주문 요청을 받아들이는 Subscriber에서 진행한다면 Subscriber에서 처리가 완료될 때까지는 정합성이 맞지 않지만 완료가 된 이후에는 정합성이 맞게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 빠르게 Pub/Sub을 구현하는 방향으로 선정을 진행했습니다. 그 결과 바로 Spring Event Publisher/Listener입니다. ApplicationContext가 상속하고 있는 기본 Feature이기 때문에 추가적인 Component(MQ, Streaming)가 필요 없고 Spring 기능을 충실히 사용하여 Over-Engineering을 하지 말자는 프로젝트의 취지와도 부합한다고 판단했습니다.&lt;/p&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 Event Publisher를 사용했을 때의 Class Diagram을 구상해보았습니다. 이전의 구조와는 다르게 재고 수량을 업데이트하는 Service는 구매 주문을 요청 시 필요 없으므로 관계가 필요 없게 됩니다. 상위 Service Layer인 PurchaseOrderAssembleService는 구매 주문서를 작성하는 것과 Cache Layer에서 재고를 관리하는 Service와 Event Publisher를 Composition 관계로 연결되어 있습니다.&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;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1kLs7/btrQUbXpLTS/uwhEjgVe76puQNKt2QXKS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1kLs7/btrQUbXpLTS/uwhEjgVe76puQNKt2QXKS0/img.png&quot; data-alt=&quot;구매 주문을 수행하는 Service의 Class Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1kLs7/btrQUbXpLTS/uwhEjgVe76puQNKt2QXKS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1kLs7%2FbtrQUbXpLTS%2FuwhEjgVe76puQNKt2QXKS0%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;1000&quot; height=&quot;411&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구매 주문을 수행하는 Service의 Class Diagram&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;구매 주문이 수행되는 과정을 Sequence diagram 중 Listener 부분만 나타내면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cR486h/btrQSZKuzC6/KRxznwHbIFI02yeFySXwmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cR486h/btrQSZKuzC6/KRxznwHbIFI02yeFySXwmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cR486h/btrQSZKuzC6/KRxznwHbIFI02yeFySXwmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcR486h%2FbtrQSZKuzC6%2FKRxznwHbIFI02yeFySXwmk%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;1000&quot; height=&quot;255&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;255&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;EventListener에서 특징적인 부분은 @Async와 @TransactionalEventListener입니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Slf4j
@RequiredArgsConstructor
@Component
public class SellProductEventListener {

	private final ProductOptionUpdateService productOptionUpdateService;
	private final PurchaseOrderUpdateService purchaseOrderUpdateService;

	@Async
	@TransactionalEventListener
	public void handleSellProductEvent(SellProductEvent sellProductEvent) {
		productOptionUpdateService.deductProductOptionCount(
			sellProductEvent.getSoldProductOptionId(),
			sellProductEvent.getPurchaseAmount()
		);

		purchaseOrderUpdateService.acceptPurchaseOrderStatus(sellProductEvent.getPurchaseOrderId());
	}
}&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;@TransactionEventListener는 Publish가 포함되어 있는 Transaction이 Commit 된 이후에 Event를 Listen 하여 처리합니다. 만약 구매 주문이 정상적으로 접수되지 않으면 Event를 수신하는 측에서도 해당 Event에 대한 처리를 진행하지 않습니다. 그리고 Event가 수신되었을 때 Asynchronous 하게 처리하도록 @Async를 추가하였습니다.&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;Lua Script와 Pub/Sub 구조 적용 후 Test&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pub/Sub 구조까지 적용을 완료한 뒤 테스트를 진행했습니다. VUser는 이전과 마찬가지로 500으로 설정하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2394&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVv552/btrQTD8bmMe/Ykitpp9ZsZOpkFkySLKKy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVv552/btrQTD8bmMe/Ykitpp9ZsZOpkFkySLKKy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVv552/btrQTD8bmMe/Ykitpp9ZsZOpkFkySLKKy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVv552%2FbtrQTD8bmMe%2FYkitpp9ZsZOpkFkySLKKy1%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;2394&quot; height=&quot;788&quot; data-origin-width=&quot;2394&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;도입 전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;도입 후&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;TPS&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;411.8&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1651.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전의 구조에 비해서 4배 정도의 TPS 성능이 올라간 것을 확인할 수 있었습니다. Database의 지표도 살펴보겠습니다.&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;2258&quot; data-origin-height=&quot;1802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mFNah/btrQTQGag9M/su9VH5F8Pwd8ap0rFAJ0EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mFNah/btrQTQGag9M/su9VH5F8Pwd8ap0rFAJ0EK/img.png&quot; data-alt=&quot;위 - 도입 전 / 아래 - 도입 후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mFNah/btrQTQGag9M/su9VH5F8Pwd8ap0rFAJ0EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmFNah%2FbtrQTQGag9M%2Fsu9VH5F8Pwd8ap0rFAJ0EK%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;2258&quot; height=&quot;1802&quot; data-origin-width=&quot;2258&quot; data-origin-height=&quot;1802&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;도입 전과 도입 후를 비교해보면 Busy wait(RED)/Busy user(BLUE)가 테스트 시작 후 특정 시점 이후에 점유율의 추세가 달라지는 것을 확인할 수 있습니다. 재고의 차감은 지속적으로 수행되고 있으며 이전에 응답은 반환됩니다. 추가적으로 Eventually Consistency 하게 되므로 주문의 상태가 Database에 반영될 때 정상적으로 주문이 접수되었다는 것을 사용자에게 보여줄 필요성을 느껴 구매 주문의 상태를 분류했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1668079104735&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum PurchaseOrderStatus {
	ACCEPTED,
	CANCELLED,
	IN_PROGRESS
}&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;초기에 주문이 접수되면 IN_PROGRESS 상태로 저장되고 재고가 차감된 이후에 ACCEPTED로 전환되어 사용자가 자신이 한 주문의 상태를 판별할 수 있는 부분을 Enum을 통해서 표현하도록 했습니다. Status를 Enum으로 표현함으로써 추후 비즈니스 요구 사항에 배송 지연됨, 결제 중, 결제 완료와 같은 부분이 추가되는 상황에서도 기존의 로직은 유지하고 확장할 수 있도록 구성했습니다.&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;Conclusion&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅을 마지막으로 현재까지 진행한 YOUSINSA의 개선 과정은 모두 포스팅을 통해 완료했습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아는 만큼 보인다고 개선을 진행할수록 개선이 필요한 부분들이 더 많아지는 것으로 보이고 아쉬운 부분들이 더 커져갔습니다. 처음 시작은 계획했던 Version 3까지는 완료하고 이직 준비를 진행하자라고 생각했지만 Naver Cloud Platform에서 지원해주는 금액이 고갈되어 가는 부분과 실서비스에서 트래픽을 만나보고 싶은 열망이 더 커지게 되어 남은 Version 3와 Version 4는 이직 후에 이어나갈 계획입니다.&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;923&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctelS8/btrQRm0rSkD/kxkKJAkWIbhUOCrylK4450/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctelS8/btrQRm0rSkD/kxkKJAkWIbhUOCrylK4450/img.png&quot; data-alt=&quot;그동안 다양한 테스트를 위해 사용한 783,970원의 내역들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctelS8/btrQRm0rSkD/kxkKJAkWIbhUOCrylK4450/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctelS8%2FbtrQRm0rSkD%2FkxkKJAkWIbhUOCrylK4450%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;923&quot; height=&quot;320&quot; data-origin-width=&quot;923&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그동안 다양한 테스트를 위해 사용한 783,970원의 내역들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfHkPE/btrQRE0XR2C/VkJjxUbRVkWh19J4fyJkTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfHkPE/btrQRE0XR2C/VkJjxUbRVkWh19J4fyJkTk/img.png&quot; data-alt=&quot;나중에 다시 만나자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfHkPE/btrQRE0XR2C/VkJjxUbRVkWh19J4fyJkTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfHkPE%2FbtrQRE0XR2C%2FVkJjxUbRVkWh19J4fyJkTk%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;762&quot; height=&quot;317&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;317&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;blockquote data-ke-style=&quot;style3&quot;&gt;해당 프로젝트는 네이버 클라우드를 활용하여 진행했습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REF&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.orchard.co.uk/blog/are-too-many-messages-turning-off-consumers-15191.aspx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.orchard.co.uk/blog/are-too-many-messages-turning-off-consumers-15191.aspx&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1668010381669&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;Are too many messages turning off consumers?&quot; data-og-description=&quot;&amp;times; Are too many messages turning off consumers?08/09/2017 In a survey it was revealed that nearly two-thirds of adults in the UK say they receive too many digital marketing promotions. Consumers revealed that they wouldn&amp;rsquo;t think twice about unsubscribing&quot; data-og-host=&quot;www.orchard.co.uk&quot; data-og-source-url=&quot;https://www.orchard.co.uk/blog/are-too-many-messages-turning-off-consumers-15191.aspx&quot; data-og-url=&quot;https://www.orchard.co.uk/blog/are-too-many-messages-turning-off-consumers-15191.aspx&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.orchard.co.uk/blog/are-too-many-messages-turning-off-consumers-15191.aspx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.orchard.co.uk/blog/are-too-many-messages-turning-off-consumers-15191.aspx&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Are too many messages turning off consumers?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;times; Are too many messages turning off consumers?08/09/2017 In a survey it was revealed that nearly two-thirds of adults in the UK say they receive too many digital marketing promotions. Consumers revealed that they wouldn&amp;rsquo;t think twice about unsubscribing&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.orchard.co.uk&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project/YOUSINSA</category>
      <category>Eventually Consistency</category>
      <category>F-Lab</category>
      <category>Pub/Sub</category>
      <category>Spring Event</category>
      <category>네이버 클라우드</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/38</guid>
      <comments>https://keydo.tistory.com/38#entry38comment</comments>
      <pubDate>Mon, 7 Nov 2022 17:42:37 +0900</pubDate>
    </item>
    <item>
      <title>[#11] 재고 관리는 어떻게 해야될까? - 2. Lua Script</title>
      <link>https://keydo.tistory.com/37</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서는 Redis의 Transaction을 사용하여 Cache Layer에서의 데이터 일관성을 보장하려고 시도했지만 Redis의 Transaction과 관련된 동작에서 Read-Write 패턴의 사용은 지원하지 않는 한계점이 있었습니다. 이런 한계점을 다시 해결해보기 위해서 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;재고 관리와 관련된 Redis의 동작을 정리하면 크게 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;- Redis에 해당 제품의 재고가 없으면 Database에서 재고를 갖고 온 뒤 검증한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Redis에 해당 제품의 재고가 있다면 재고 수에 대해 검증한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 재고수를 차감한 뒤 Database에도 차감된 재고를 동기화한다.&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;Redis에서 지원하지 않는 동작은 수행하지 못하는 것일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 문제들을 해결하기 위해서 생각해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;내가 겪었던 문제들을 다른 개발자분들도 겪지 않았을까? 당연히 YES&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Redisson이라는 Redis Client의 코드들을 찬찬히 보다 의문점이 생긴 부분도 해결 방향을 정하는 데 도움을 주었습니다. Redisson의 Code를 살펴보면 정말 신기하게도 TTL(Time-To-Live)를 지원하지 않는 자료구조에 대해서도 만료 시간을 설정할 수 있습니다.&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;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SepkJ/btrQuIIs2U3/h42GebMxbk7rKqmGPegWl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SepkJ/btrQuIIs2U3/h42GebMxbk7rKqmGPegWl1/img.png&quot; data-alt=&quot;RedissonMapCache의 코드 일부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SepkJ/btrQuIIs2U3/h42GebMxbk7rKqmGPegWl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSepkJ%2FbtrQuIIs2U3%2Fh42GebMxbk7rKqmGPegWl1%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;800&quot; height=&quot;575&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RedissonMapCache의 코드 일부&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;위 코드와 같이 Map이라는 자료 구조에서 지원하지 않는 TTL 기능을 구현한 것을 볼 수 있습니다. `evalWriteAsync` 메서드 마지막에 script라고 표시된 부분을 보면 Code인 듯 보이는 문자열로 if, then 등 예약어들을 볼 수 있습니다. 이 Script를 Lua Script라고 말하며 프로그래밍적인 문법과 더불어 하나의 Script는 Atomic 하다는 것을 보장해줍니다.&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;재고 관리 시스템에 Lua Script 적용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Script를 작성하기에 앞서 어떻게 Script를 유지 보수하기 쉽게 적용할지 고민했습니다. Redisson 처럼 문자열 형태로 작성하는 것도 채택될 수도 있었지만 개행, Indent, 공백(Space)의 관리를 생각했을 때 적합하지 않다고 판단했습니다. 이런 점을 Spring에서도 인지하고 Lua Script를 하나의 객체로 관리할 수 있는 `RedisScript&amp;lt;T&amp;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에서 Resource를 불러오듯 Script 또한 ClassPath를 통해 불러와 등록할 수 있었습니다. 그리고 Redis와의 재고만 처리하는 하나의 Template으로 Component로 관리할 수 있도록 `ProductStockManageTemplate`을 정의했습니다.&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;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cw1ZEs/btrQxcbozQy/PvkBpGnaGIX5KvTtP1TjJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cw1ZEs/btrQxcbozQy/PvkBpGnaGIX5KvTtP1TjJ0/img.png&quot; data-alt=&quot;LuaScript를 사용하는 RedisTemplate 정의&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cw1ZEs/btrQxcbozQy/PvkBpGnaGIX5KvTtP1TjJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcw1ZEs%2FbtrQxcbozQy%2FPvkBpGnaGIX5KvTtP1TjJ0%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;800&quot; height=&quot;327&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LuaScript를 사용하는 RedisTemplate 정의&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;ProductStockManageTemplate의 manageStockWithCache 메서드의 parameter를 통해 Script에 필요한 값들을 전달하도록 설계를 진행했습니다.&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;Lua Script 작성하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Script를 작성하기에 앞서 Redis의 특징 중 하나인 다양한 자료구조들을 어떻게 적용할지 고민이 필요했습니다. 그리고 현재는 수량을 차감하는 구매 과정만 있었으나 추후 반품, 취소와 같이 수량을 다시 더해줘야 되는 기능까지 확장되는 상황을 고려했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667789813772&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Sorted Set
key [zproduct:option:{productOptionId}]
value [purchase_order_id:{purchaseOrderId}]
score [{current_time} + {ttl}]

// Map
key [hproduct:option:{productOptionId}]
field [stock_count]
value [{stock_count}]&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;먼저 Sorted Set의 경우 해당 상품이 어떤 주문에 포함되어 있는지를 포함하고 TTL을 설정할 수 있도록 설계를 진행했습니다. Sorted Set의 경우 score를 기준으로 정렬되니 만료가 빠른 순서부터 느린 순서로 정렬될 것이고 ZREMRANGEBYSCORE를 사용해서 현재 시간까지의 값들을 삭제한다면 EXPIRE 명령어를 사용하지 않아도 TTL 기능을 구현할 수 있습니다. TTL 기능은 Cache의 불일치, 즉 Cache Pollution을 고려했습니다.&lt;/p&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에 있는 재고 수량이 Database에 있는 수량과 불일치되는 문제가 발생한 뒤 지속적으로 서비스가 운영된다면 결국 서비스 장애로 이루어질 것이라 생각했습니다. 그래서 Redis의 Map 자료 구조에 등록되어 있는 key들을 지속적으로 만료시켜(Cache Eviction) Database와의 동기화를 주기적으로 맞춰 순간적인 불일치가 발생하더라도 추후에는 다시 정합성이 맞게 되는 Eventually Consistency 방식을 고려했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667793563700&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;local key = KEYS[1]

local purchase_order_id = ARGV[1]
local current_stock = tonumber(ARGV[2])
local purchase_amount = tonumber(ARGV[3])
local current_time = tonumber(ARGV[4])
local ttl = tonumber(ARGV[5])

local remained_stock = 0

local stock_count_field = &quot;stock_count&quot;
local purchase_order_set_key = &quot;z&quot;..key
local product_stock_hash_key = &quot;h&quot;..key

local is_stock_exist = redis.call(&quot;hexists&quot;, product_stock_hash_key, stock_count_field)

if is_stock_exist == 0 then
    local deductStock = current_stock - purchase_amount
    if deductStock &amp;gt;= 0 then
        remained_stock = deductStock
    else
        return nil
    end

    local value = &quot;purchase_order_id:&quot;..purchase_order_id
    redis.call(&quot;zadd&quot;, purchase_order_set_key, current_time + ttl, value)
    redis.call(&quot;hset&quot;, product_stock_hash_key, stock_count_field, remained_stock)
else
    local recent_stock = redis.call(&quot;hget&quot;, product_stock_hash_key, stock_count_field)
    remained_stock = recent_stock - purchase_amount

    local value = &quot;purchase_order_id:&quot;..purchase_order_id
    redis.call(&quot;zadd&quot;, purchase_order_set_key, current_time + ttl, value)
    redis.call(&quot;hset&quot;, product_stock_hash_key, stock_count_field, remained_stock)
end

return remained_stock&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;전반적인 로직은 처음 개요에서 말했던 부분과 일맥 상통합니다. 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;Lua Script를 작성하면서 고려했던 부분은 각 명령, 연산(hget, hexist, hset, zadd) 들의 시간 복잡도였습니다. In-Memory DB 이므로 같은 O(N)이라도 File I/O에 비해 속도가 더 빠를 수는 있으나 Redis의 특성을 고려했습니다. Lua Script 자체는 하나의 연산으로 Atomic 함을 보장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 Atomic함을 보장하면서 처리 속도를 올렸는지 추가적인 조사를 진행했습니다.&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;Redis가 Atomic한 연산을 보장하는 Single Thread? Multi-Thread?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 Redis와 Hazelcast의 Performance를 비교하는 아래 그림을 보여드린 적이 있습니다. 선정 시에는 의문점으로만 남겼지만 직접 사용해보면서 의문점을 파보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Redis는 Single Thread로 작동하는데 Multi Thread 환경에서 실행시킨다면 의미가 없는 것 아닌가?&lt;/blockquote&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;900&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqZsv2/btrQz6vkcXl/NASYAno8GCiVv5BkNn5uSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqZsv2/btrQz6vkcXl/NASYAno8GCiVv5BkNn5uSK/img.png&quot; data-alt=&quot;왼쪽 - Redis 공식 비교, 오른쪽 - Hazelcast 공식 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqZsv2/btrQz6vkcXl/NASYAno8GCiVv5BkNn5uSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqZsv2%2FbtrQz6vkcXl%2FNASYAno8GCiVv5BkNn5uSK%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;900&quot; height=&quot;277&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;왼쪽 - Redis 공식 비교, 오른쪽 - Hazelcast 공식 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문에 대한 답은 여러 관점, 레디스 버전에 따라 YES가 될 수도 NO가 될 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 `Single Thread로 동작하여 Atomic 한 연산을 보장한다`의 의미를 Redis의 구조와 연관시켜 살펴보면 다음과 같이 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oSQiD/btrQAwVOlrq/jRcCidmxREYVesrBVwdmM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oSQiD/btrQAwVOlrq/jRcCidmxREYVesrBVwdmM0/img.png&quot; data-alt=&quot;Redis Event Loop 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oSQiD/btrQAwVOlrq/jRcCidmxREYVesrBVwdmM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoSQiD%2FbtrQAwVOlrq%2FjRcCidmxREYVesrBVwdmM0%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;268&quot; height=&quot;189&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Redis Event Loop 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event Loop에서 IO Multiplexing을 이용해서 Read/Write 이벤트를 받아오고, Read 이벤트가 발생하면 네트워크를 통해 패킷을 읽고, Command가 완성되면 실행되는 구조입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;249&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt75Hl/btrQywIEIVd/u5a4RvxdaGK5l0yVvlKUm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt75Hl/btrQywIEIVd/u5a4RvxdaGK5l0yVvlKUm1/img.png&quot; data-alt=&quot;Redis의 실행구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt75Hl/btrQywIEIVd/u5a4RvxdaGK5l0yVvlKUm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt75Hl%2FbtrQywIEIVd%2Fu5a4RvxdaGK5l0yVvlKUm1%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;543&quot; height=&quot;249&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;249&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Redis의 실행구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 다시 말해 우리가 흔히 Redis는 Single Thread로 동작한다는 것의 의미는 &lt;b&gt;Command의 실행이 하나의 Thread에 의해 실행된 다는 것을 의미합니다. &lt;/b&gt;이를 Atomic한 연산과 관련지어 생각해본다면 실행을 하는 Thread는 한 개밖에 없으므로 동시성 문제에 대해서 고민하지 않아도 되므로 프로그래밍 자체가 쉬워집니다.&lt;/p&gt;
&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/getting-started/faq/#redis-is-single-threaded-how-can-i-exploit-multiple-cpu--cores&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://redis.io/docs/getting-started/faq/#redis-is-single-threaded-how-can-i-exploit-multiple-cpu--cores&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667807780094&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;Redis FAQ&quot; data-og-description=&quot;Commonly asked questions when getting started with Redis&quot; data-og-host=&quot;redis.io&quot; data-og-source-url=&quot;https://redis.io/docs/getting-started/faq/#redis-is-single-threaded-how-can-i-exploit-multiple-cpu--cores&quot; data-og-url=&quot;https://redis.io/docs/getting-started/faq/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://redis.io/docs/getting-started/faq/#redis-is-single-threaded-how-can-i-exploit-multiple-cpu--cores&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://redis.io/docs/getting-started/faq/#redis-is-single-threaded-how-can-i-exploit-multiple-cpu--cores&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Redis FAQ&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Commonly asked questions when getting started with Redis&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;redis.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 공식 문서에서는 Redis에서 CPU가 BottleNeck이 아니라 메모리나 네트워크 부분에서 발생한다고 소개하고 있습니다. 하지만 여기서 더 성능을 높이기 위해 v4부터 실행은 그대로 단일 스레드가 사용되고 Multi-Thread를 사용할 방법을 적용하기 시작합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm2CJE/btrQyxt1g68/JLi54aqwdDf35NPt11Rbmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm2CJE/btrQyxt1g68/JLi54aqwdDf35NPt11Rbmk/img.png&quot; data-alt=&quot;Threaded IO의 구조 - 관련된 부분은 더 분석하여 포스팅할 예정입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm2CJE/btrQyxt1g68/JLi54aqwdDf35NPt11Rbmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm2CJE%2FbtrQyxt1g68%2FJLi54aqwdDf35NPt11Rbmk%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;773&quot; height=&quot;251&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Threaded IO의 구조 - 관련된 부분은 더 분석하여 포스팅할 예정입니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구조가 변경되어 Multi-Thread를 사용하여 Performance를 개선하면서도 Atomic 한 연산은 보장하기 위해서 실행은 Single Thread로 수행합니다. 이런 점에서 Lua Script가 실행될 때 실행 시간이 길어지면 연속적으로 뒤따르는 연산들이 느려지기 때문에 이런 문제를 막기 위해서 각각 연산들의 시간 복잡도를 O(N) 이하보다 작은 연산을 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667808832116&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZADD - O(log(N))
HEXISTS - O(1)
HGET - O(1)
HSET - O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Lua Script는 Silver Bullet일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 아닙니다. 그동안 갖고 있던 모든 고민들을 해결할 수 있는 해결책이라고 판단했었지만 Lua Script는 Redis Server에서 실행되는 Script이기 때문에 Unit 테스트를 할 수 없습니다. Refactoring과 기능의 확장을 고려했었을 때 Unit Test가 없다는 점 때문에 Lua Script 가 문제점이 될 것으로 예상합니다.&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;Conclusion&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 갖고 있던 문제점(Database IO 병목)의 해결에 초점을 두고 지속적으로 개선해 나갔습니다. 그러던 중 의문점이 하나 생겼습니다. Database의 재고는 항상 Consistency 해야 될까라는 의문이었습니다. 구매 주문 과정을 통해 풀어 설명해본다면 한 명의 유저가 10개를 구매했다면 곧바로 Database의 재고가 10개가 차감되어 일관성이 실시간으로 유지되어야 하는가로 말할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;해당 프로젝트는 네이버 클라우드를 활용하여 진행했습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;REF&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/29cm/redis-redis%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EC%86%8D%EB%8F%84-%EC%A4%84%EC%9D%B4%EA%B8%B0-8132ea90bc39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/29cm/redis-redis%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EC%86%8D%EB%8F%84-%EC%A4%84%EC%9D%B4%EA%B8%B0-8132ea90bc39&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667809759891&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;Redis를 활용하여 Request 5배 더 받기&quot; data-og-description=&quot;In-memory DB인 redis를 활용하여 갑작스럽게 주문이 몰릴 때 유입속도를 늦출 수 있는 방법을 공유하려고합니다.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/29cm/redis-redis%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EC%86%8D%EB%8F%84-%EC%A4%84%EC%9D%B4%EA%B8%B0-8132ea90bc39&quot; data-og-url=&quot;https://medium.com/29cm/redis-redis%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EC%86%8D%EB%8F%84-%EC%A4%84%EC%9D%B4%EA%B8%B0-8132ea90bc39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bi4YPd/hyQtobZvtP/7i2qRRPukkFGRjkw4WCNA0/img.png?width=1200&amp;amp;height=635&amp;amp;face=0_0_1200_635&quot;&gt;&lt;a href=&quot;https://medium.com/29cm/redis-redis%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EC%86%8D%EB%8F%84-%EC%A4%84%EC%9D%B4%EA%B8%B0-8132ea90bc39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/29cm/redis-redis%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EC%86%8D%EB%8F%84-%EC%A4%84%EC%9D%B4%EA%B8%B0-8132ea90bc39&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bi4YPd/hyQtobZvtP/7i2qRRPukkFGRjkw4WCNA0/img.png?width=1200&amp;amp;height=635&amp;amp;face=0_0_1200_635');&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를 활용하여 Request 5배 더 받기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In-memory DB인 redis를 활용하여 갑작스럽게 주문이 몰릴 때 유입속도를 늦출 수 있는 방법을 공유하려고합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://charsyam.wordpress.com/2020/05/05/%EC%9E%85-%EA%B0%9C%EB%B0%9C-redis-6-0-threadedio%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://charsyam.wordpress.com/2020/05/05/%EC%9E%85-%EA%B0%9C%EB%B0%9C-redis-6-0-threadedio%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667809740051&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;[입 개발] Redis 6.0 &amp;ndash; ThreadedIO를 알아보자.&quot; data-og-description=&quot;안녕하세요. 입개말만 하는 CharSyam 입니다. 이번에 Redis Version 6.0.x 가 출시되었습니다. Redis 5.0에서도 Stream 등 새로운 기능이 들어왔었는데, 이번 6.0에서도 ACL 및 여러가지 기능들이 들어왔습니다&quot; data-og-host=&quot;charsyam.wordpress.com&quot; data-og-source-url=&quot;https://charsyam.wordpress.com/2020/05/05/%EC%9E%85-%EA%B0%9C%EB%B0%9C-redis-6-0-threadedio%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90/&quot; data-og-url=&quot;https://charsyam.wordpress.com/2020/05/05/%ec%9e%85-%ea%b0%9c%eb%b0%9c-redis-6-0-threadedio%eb%a5%bc-%ec%95%8c%ec%95%84%eb%b3%b4%ec%9e%90/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bBTIsH/hyQtqHBToZ/TgYRMbndZYKKof9pB6v68k/img.png?width=795&amp;amp;height=258&amp;amp;face=0_0_795_258,https://scrap.kakaocdn.net/dn/dua3bf/hyQtnD7PpB/KsLrJRlErA5dNdGjWJkhr1/img.png?width=638&amp;amp;height=207&amp;amp;face=0_0_638_207,https://scrap.kakaocdn.net/dn/b3xvCY/hyQtqAQmbb/ikRzBSKGCg8JV6ylibTyr0/img.png?width=773&amp;amp;height=251&amp;amp;face=0_0_773_251&quot;&gt;&lt;a href=&quot;https://charsyam.wordpress.com/2020/05/05/%EC%9E%85-%EA%B0%9C%EB%B0%9C-redis-6-0-threadedio%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://charsyam.wordpress.com/2020/05/05/%EC%9E%85-%EA%B0%9C%EB%B0%9C-redis-6-0-threadedio%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bBTIsH/hyQtqHBToZ/TgYRMbndZYKKof9pB6v68k/img.png?width=795&amp;amp;height=258&amp;amp;face=0_0_795_258,https://scrap.kakaocdn.net/dn/dua3bf/hyQtnD7PpB/KsLrJRlErA5dNdGjWJkhr1/img.png?width=638&amp;amp;height=207&amp;amp;face=0_0_638_207,https://scrap.kakaocdn.net/dn/b3xvCY/hyQtqAQmbb/ikRzBSKGCg8JV6ylibTyr0/img.png?width=773&amp;amp;height=251&amp;amp;face=0_0_773_251');&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 6.0 &amp;ndash; ThreadedIO를 알아보자.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 입개말만 하는 CharSyam 입니다. 이번에 Redis Version 6.0.x 가 출시되었습니다. Redis 5.0에서도 Stream 등 새로운 기능이 들어왔었는데, 이번 6.0에서도 ACL 및 여러가지 기능들이 들어왔습니다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;charsyam.wordpress.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project/YOUSINSA</category>
      <category>Consistency</category>
      <category>F-Lab</category>
      <category>lua script</category>
      <category>NAVER CLOUD</category>
      <category>Redis</category>
      <category>YOUSINSA</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/37</guid>
      <comments>https://keydo.tistory.com/37#entry37comment</comments>
      <pubDate>Mon, 7 Nov 2022 01:08:22 +0900</pubDate>
    </item>
    <item>
      <title>[#11] 재고 관리는 어떻게 해야될까? - 1. Redis Transaction</title>
      <link>https://keydo.tistory.com/36</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 데이터의 무결성을 위해 해결을 시도한 영역은 백엔드를 구성하는 부분 중 Database였습니다. 데이터 베이스에서 Transaction들을 Serial하게 수행하여 무결성을 확보하고 성능과 Trade-Off 했습니다. 하지만 E-Commerce 분야에서 하나의 상품을 많은 사람들이 구매하는 상황은 빈번하게 발생한다고 생각했었을 때 성능 또한 향상시킬 필요성이 있습니다. 왜냐하면 구매를 하기 위해 오랜 시간 대기하였지만 실패되는 경험은 유저에게 서비스의 신뢰도를 떨어뜨린다고 판단했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvtLXq/btrPB1pDEhE/mZKyMs9zW3Q3Ggb9QRNsTK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvtLXq/btrPB1pDEhE/mZKyMs9zW3Q3Ggb9QRNsTK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvtLXq/btrPB1pDEhE/mZKyMs9zW3Q3Ggb9QRNsTK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvtLXq%2FbtrPB1pDEhE%2FmZKyMs9zW3Q3Ggb9QRNsTK%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;700&quot; height=&quot;411&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;411&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;h3 data-ke-size=&quot;size23&quot;&gt;구매 주문 과정 분해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 '재고'라는 상태를 모두 데이터 베이스에 저장하기 때문에 발생하는 문제라고 생각했습니다. 특히 재고의 차감이라는 수정을 위해서는 Lock을 수행하기 때문에 병목이 발생한다는 것을 이전 테스트에서 알 수 있었습니다. 이런 문제의 해결책을 생각해보기 위해 구매 주문 과정을 더 작은 단위로 분해해보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DV0xB/btrPDGymMHx/ckOYrODCprkPDPjk8ggE10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DV0xB/btrPDGymMHx/ckOYrODCprkPDPjk8ggE10/img.png&quot; data-alt=&quot;구매 주문과정을 더 작은 단위로 분해한 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DV0xB/btrPDGymMHx/ckOYrODCprkPDPjk8ggE10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDV0xB%2FbtrPDGymMHx%2FckOYrODCprkPDPjk8ggE10%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;500&quot; height=&quot;474&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구매 주문과정을 더 작은 단위로 분해한 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 재고를 읽고 구매자가 구매한 만큼의 충분한 재고가 있는지 평가하고 만약 충분하다면 구매 주문 접수가 시작되어 최종적으로 재고가 차감되는 방식으로 구성되어 있습니다. 한순간에 한 명의 사용자만 상품을 구매한다면 문제가 없지만 여러 명의 사용자가 상품 구매를 할 때 발생하는 동시성 문제를 해결하기 위하여 재고를 읽는 순간부터 하나의 요청만 순차적으로 처리되도록 한 것이 이전 포스팅까지의 과정이였습니다.&lt;/p&gt;
&lt;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;Q1. 재고는 항상 데이터 베이스에서만 읽어와야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A1. 재고라는 상태를 관리하는 다른 무언가가 있다면 데이터 베이스에서만 읽을 필요는 없을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q2. 구매 주문하기라는 동작에서 만약 재고가 무한정 존재하여 재고의 검증이 없다면 병목이 발생할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A2. 재고 차감에서는 항상 Lock이 수행되므로 발생할 것이다.&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;Cache?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 접근 방식은 Database를 1차 저장소, Cache Layer를 2차 저장소로 만드는 것이었습니다. Local Cache와 Global Cache를 검토했었을 때 트래픽에 따른 확장을 고려하면 Local Cache를 적용할 경우 Application간의 데이터 불일치가 예상되므로 추가적인 Replication을 적용해야 하므로 제외하여 Global Cache를 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Cache Layer와 Database간의 관계에 대해서 고민했습니다. 대표적으로 Cache Aside, Write Back, Read Through, Write Through 전략이 있습니다.&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;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mkSzz/btrPIE17Opc/erj0DOuCdMELbhpCwmOKBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mkSzz/btrPIE17Opc/erj0DOuCdMELbhpCwmOKBK/img.png&quot; data-alt=&quot;Cache 전략 Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mkSzz/btrPIE17Opc/erj0DOuCdMELbhpCwmOKBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmkSzz%2FbtrPIE17Opc%2Ferj0DOuCdMELbhpCwmOKBK%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;900&quot; height=&quot;491&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Cache 전략 Diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에 고려했던 설계는 Write Back과 Write Through 였습니다. Cache에서 재고들을 수정하고 Database에 전달해기 전 Buffer로써 설계한다면 현재 생기고 있는 병목을 막을 수 있을 것이라 생각했습니다.&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;Redisson을 활용한 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 그럼 구현은 어떻게 해야될지 고민했습니다. 직접 구현하는 방법도 있었지만 Redis Client 중 Redisson은 해당 전략들의 구현체를 제공하고 있다는 점에서 적용해 보기 위해 검토를 했습니다. 검토 해본 결과 Cache에 저장되어 있는 재고 데이터를 Database로 Write Back, Thorough 하는 것에는 문제가 없었으나 재고 검증이라는 과정이 해결되지 않았습니다. 추가적인 문제로 Database로 Sync하는 과정 중 예외가 발생했을 때 예외를 처리할 수 없었습니다.&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;Redis Transaction을 이용한 직접 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리를 사용하므로써 예외 처리의 부재 같은 문제들을 직접 해결해보고자 Redis Transaction을 사용하여 직접 구현해보고자 했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666886250277&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** 
/* ProductOptionId를 key로 operation 진행
**/
BoundListOperations&amp;lt;String, String&amp;gt; listOps = redisTemplate.boundListOps(key);

Long size = listOps.size();
if (size != null &amp;amp;&amp;amp; size &amp;lt;= 0) {
	// Redis에 재고 정보가 없다면 Database의 재고 정보를 조회 후
	// 재고 차감
	// Redis에 차감 재고 기록 
} else {
	// --Redis Transaction 범위 시작--
	// Redis에 재고 정보가 있다면 Redis의 재고 정보를 조회 후
	// 재고 차감
	// Redis에 차감 재고 기록
}

return remainedStock[0];
// --상위 트랜잭션 종료 후 Redis Transaction 실행--&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재고의 차감되는 히스토리들을 확인하고 싶었기 때문에 초기에는 List 자료구조를 사용했습니다. List 자료구조와 Redis Transaction을 사용했을 때 원하는 바대로 동작한다면 Sorted Set 구조로 변경하여 TTL을 주어 Cache를 주기적으로 만료시킬 계획이었습니다. 하지만 위 로직에는 문제점이 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uiSFR/btrPJeaz3U9/fH6GCVHtqNBb3l9UMnuNw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uiSFR/btrPJeaz3U9/fH6GCVHtqNBb3l9UMnuNw1/img.png&quot; data-alt=&quot;실제로 들어가 있는 Stock History와 이상적인 Stock History 모식도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uiSFR/btrPJeaz3U9/fH6GCVHtqNBb3l9UMnuNw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuiSFR%2FbtrPJeaz3U9%2FfH6GCVHtqNBb3l9UMnuNw1%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;600&quot; height=&quot;336&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제로 들어가 있는 Stock History와 이상적인 Stock History 모식도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 나타낸 코드가 모두 Redis Transaction 내에 포함되어 있지 않으면 다음과 같이 같은 재고를 바탕으로 Cache 재고를 차감하게 됩니다. 그렇다면 Redis Transaction 내에 모두 포함하면 되지 않을까라는 생각이 들었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w521n/btrPJnFicF3/k0vIH6DkhrsegiZQ6xZQjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w521n/btrPJnFicF3/k0vIH6DkhrsegiZQ6xZQjk/img.png&quot; data-alt=&quot;Redis Transaction 내의 Read 연산은 Null 반환&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w521n/btrPJnFicF3/k0vIH6DkhrsegiZQ6xZQjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw521n%2FbtrPJnFicF3%2Fk0vIH6DkhrsegiZQ6xZQjk%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;349&quot; height=&quot;135&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Redis Transaction 내의 Read 연산은 Null 반환&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 재고 검증이라는 단계에서 Read 과정은 Redis Transaction 내에서 항상 Null을 반환합니다. 결론적으로 Redis의 Transaction은 Read-Write 패턴의 형태로 사용할 수 없습니다. 이유는 RedisTemplate의 Transaction은 Transaction의 Atomic하도록 만들기 위해 multi - exec까지의 코드를 다음과 같이 수행합니다.&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;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;639&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pKOTJ/btrPIEA4T7H/R7PAKiVxhap6PJsKO9tKsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pKOTJ/btrPIEA4T7H/R7PAKiVxhap6PJsKO9tKsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pKOTJ/btrPIEA4T7H/R7PAKiVxhap6PJsKO9tKsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpKOTJ%2FbtrPIEA4T7H%2FR7PAKiVxhap6PJsKO9tKsK%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;900&quot; height=&quot;639&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;639&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;코드 레벨에서 들여다 보면 Redis Template에서 Database Transaction의 commit 쿼리와 같이 exec()를 수행하면 한 번에 Redis Transaction 내의 operation을 한 번에 Redis에게 요청합니다. 따라서 Redis의 특성에 따라 한 순간에 전송된 연산들은 순차적으로 처리되어 Atomic하게 동작하게 되는 방식으로 동작합니다. (이런 이유로 Read를 Transaction 내에서 수행하고 싶어도 exec()을 호출후 Redis에 요청됨으로 Transaction 내에서 값을 읽는 부분은 필요없게 되므로 null 을 반환하도록 처리되어 있습니다.)&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Atomic 하면서 재고 검증을 할 수 있기 위해서는 어떻게 해야될까?&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;REF&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://waspro.tistory.com/697&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://waspro.tistory.com/697&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1666885244466&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;Redis5 설계하기 총정리&quot; data-og-description=&quot;개요 이번 포스팅에서는 Redis를 효과적으로 구축/운영하기 위한 설계방법에 대해 알아보도록 하자. Redis는 대표적인 In-memory DB로 세션, 캐시, 큐 등으로 활용된다. 단일 환경으로 가볍게 구성이 &quot; data-og-host=&quot;waspro.tistory.com&quot; data-og-source-url=&quot;https://waspro.tistory.com/697&quot; data-og-url=&quot;https://waspro.tistory.com/697&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eJsdbl/hyQmxlIJuV/4XiEGIZmzqVT242Osg4Hd0/img.png?width=753&amp;amp;height=353&amp;amp;face=0_0_753_353,https://scrap.kakaocdn.net/dn/blX43k/hyQmvnVsVK/XdPZhasz3Iudw8K0mLvLJ0/img.png?width=753&amp;amp;height=353&amp;amp;face=0_0_753_353,https://scrap.kakaocdn.net/dn/chmhXV/hyQmEd4oOB/HbpNbpfvD60FgPG1oqt6Tk/img.png?width=755&amp;amp;height=354&amp;amp;face=0_0_755_354&quot;&gt;&lt;a href=&quot;https://waspro.tistory.com/697&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://waspro.tistory.com/697&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eJsdbl/hyQmxlIJuV/4XiEGIZmzqVT242Osg4Hd0/img.png?width=753&amp;amp;height=353&amp;amp;face=0_0_753_353,https://scrap.kakaocdn.net/dn/blX43k/hyQmvnVsVK/XdPZhasz3Iudw8K0mLvLJ0/img.png?width=753&amp;amp;height=353&amp;amp;face=0_0_753_353,https://scrap.kakaocdn.net/dn/chmhXV/hyQmEd4oOB/HbpNbpfvD60FgPG1oqt6Tk/img.png?width=755&amp;amp;height=354&amp;amp;face=0_0_755_354');&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;Redis5 설계하기 총정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요 이번 포스팅에서는 Redis를 효과적으로 구축/운영하기 위한 설계방법에 대해 알아보도록 하자. Redis는 대표적인 In-memory DB로 세션, 캐시, 큐 등으로 활용된다. 단일 환경으로 가볍게 구성이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;waspro.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.gccompany.co.kr/redis-kafka%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%BF%A0%ED%8F%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-feat-%EB%84%A4%EA%B3%A0%EC%99%95-ec6682e39731&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.gccompany.co.kr/redis-kafka%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%BF%A0%ED%8F%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-feat-%EB%84%A4%EA%B3%A0%EC%99%95-ec6682e39731&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1666887259701&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;Redis&amp;amp;Kafka를 활용한 선착순 쿠폰 이벤트 개발기 (feat. 네고왕)&quot; data-og-description=&quot;안녕하세요. 유저혜택개발팀 쿠폰 백앤드 개발자 페이든입니다.&quot; data-og-host=&quot;techblog.gccompany.co.kr&quot; data-og-source-url=&quot;https://techblog.gccompany.co.kr/redis-kafka%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%BF%A0%ED%8F%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-feat-%EB%84%A4%EA%B3%A0%EC%99%95-ec6682e39731&quot; data-og-url=&quot;https://techblog.gccompany.co.kr/redis-kafka%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%BF%A0%ED%8F%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-feat-%EB%84%A4%EA%B3%A0%EC%99%95-ec6682e39731&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EpZa2/hyQmzcNUT6/ZycC4ROVy9NVkhFd0sKQpK/img.jpg?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200&quot;&gt;&lt;a href=&quot;https://techblog.gccompany.co.kr/redis-kafka%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%BF%A0%ED%8F%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-feat-%EB%84%A4%EA%B3%A0%EC%99%95-ec6682e39731&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.gccompany.co.kr/redis-kafka%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%BF%A0%ED%8F%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-feat-%EB%84%A4%EA%B3%A0%EC%99%95-ec6682e39731&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EpZa2/hyQmzcNUT6/ZycC4ROVy9NVkhFd0sKQpK/img.jpg?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_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;Redis&amp;amp;Kafka를 활용한 선착순 쿠폰 이벤트 개발기 (feat. 네고왕)&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;techblog.gccompany.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;</description>
      <category>Project/YOUSINSA</category>
      <category>Cache 전략</category>
      <category>Pinpoint</category>
      <category>redis transaction</category>
      <category>YOUSINSA</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/36</guid>
      <comments>https://keydo.tistory.com/36#entry36comment</comments>
      <pubDate>Thu, 27 Oct 2022 00:45:15 +0900</pubDate>
    </item>
    <item>
      <title>[#10] 재고 관리 Integrity 문제 - 2</title>
      <link>https://keydo.tistory.com/35</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 재고 관리 Integrity 문제 1편의 마지막에 '왜 Database Lock이 Distributed-Lock보다 TPS 성능이 좋겠나왔는가?'를 다뤄보려고 합니다.&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;주의  : 해당 테스트에서 Distributed-Lock이 DB Lock보다 성능이 안 좋게 나왔지만 Database Lock은 항상 Distributed-Lock보다 좋다라는 관점은 적절하지 않습니다.&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;Distributed-Lock은 왜 사용할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 예상하기로는 In-memory DB를 통해서 Lock을 수행하므로 막연히 더 빠르겠지(?)라는 생각을 갖고 있었습니다. 더불어 분산 락으로 인해 Database에서 해당 Row에 대한 Update 동작을 경합할 Transaction의 수가 적어지니 Database에서도 Lock에 대한 WAIT가 짧아지므로 전체적인 요청-응답 사이클이 빨라질 것이라 예상했습니다. 하지만 이 모든 가정들이 저만의 상상이었다는 것을 모니터링 결과를 통해 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Distributed-Lock은 분산 환경에서 Lock을 제공하여 동기화된 처리를 제공할 목적이라는 것이 핵심입니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y8pVS/btrOwntKxpe/5ssTDHy9ieIokkgcwrkK5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y8pVS/btrOwntKxpe/5ssTDHy9ieIokkgcwrkK5k/img.png&quot; data-alt=&quot;Distributed-Lock 동작 Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y8pVS/btrOwntKxpe/5ssTDHy9ieIokkgcwrkK5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy8pVS%2FbtrOwntKxpe%2F5ssTDHy9ieIokkgcwrkK5k%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;700&quot; height=&quot;431&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Distributed-Lock 동작 Diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇이 다시 병목 지점을 만들어냈을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 Diagram에서 Thread가 어디서 Blocking 될지 예상해보면 두 곳이 보입니다. 분산 락이 수행되는 부분과 DB에서 재고를 차감하기 위해 락이 수행되는 부분입니다. 분산 락을 통해서 재고 데이터 검증하는 트랜잭션이 하나만 유지되어 무결성을 지킬 수 있었지만 다시 DB를 통해서 Lock이 수행되므로 DB Lock이 해제된 후 분산락도 해제되는 구조입니다.&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;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n1uNm/btrOD0X6zg8/4wapprkjg2fgegmavLzi6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n1uNm/btrOD0X6zg8/4wapprkjg2fgegmavLzi6K/img.png&quot; data-alt=&quot;Database Node Monitoring / 위 - DB Lock, 아래 - Distributed-Lock&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n1uNm/btrOD0X6zg8/4wapprkjg2fgegmavLzi6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn1uNm%2FbtrOD0X6zg8%2F4wapprkjg2fgegmavLzi6K%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;900&quot; height=&quot;721&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Database Node Monitoring / 위 - DB Lock, 아래 - Distributed-Lock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 노드 모니터링 결과를 보면 차이가 없는 것을 확인할 수 있습니다. 따라서 분산락을 사용해도 DB의 Busy IO Wait가 비슷한 비율로 수행됩니다. 그렇다면 다시 원래 질문으로 돌아가기 위한 Pinpoint 지표를 살펴보겠습니다.&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;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QEami/btrOGuxoAJe/moDNdyOADvSu6mGjptdR2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QEami/btrOGuxoAJe/moDNdyOADvSu6mGjptdR2K/img.png&quot; data-alt=&quot;Pinpoint Inspector / 위 - DB Lock, 아래 - Distributed Lock&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QEami/btrOGuxoAJe/moDNdyOADvSu6mGjptdR2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQEami%2FbtrOGuxoAJe%2FmoDNdyOADvSu6mGjptdR2K%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;900&quot; height=&quot;708&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Pinpoint Inspector / 위 - DB Lock, 아래 - Distributed Lock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response Time은 Distributed Lock이 짧고 특히 Total Thread와 Tomcat의 Connection과 관련된(Socket 동작) Direct Buffer Count의 경우 Distributed Lock을 사용하는 곳에서 더 적은 것을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Thread Dump를 확인했었으면 더 정확한 판단이 가능했을 텐데 이 부분은 확인하지 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Application 기준으로 DB Lock은 Thread가 280개 정도 사용되고 분산 락은 130개 정도 사용됩니다. Direct Buffer Count를 맺어진 Connection의 개수와 비슷하다고 가정한다면 DB Lock은 200개로 Tomcat의 Max Thread 기본 세팅과 같고 이에 반해 Distributed Lock은 75개 정도로 측정됩니다. 이와 비슷한 비율을 TPS 비교표에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Distributed-Lock&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Database Lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;TPS&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;158&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;411.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 현상은 Distributed-Lock의 동작과 관련 있다고 판단했습니다. 분산 락은 분산 환경에서 동기화된 처리를 위해 사용됩니다. Scale-Out을 사용하면서 Resource들이 수평적으로 확장되어 성능이 올라갔지만 분산 락을 사용함으로써 결국 한 순간에 처리될 수 있는 요청을 하나밖에 없으므로 이에 따라 하나의 요청이 처리되는 시간 자체(Response Time)는 향상되지만 전체적으로 처리되는 양은 줄어듭니다.&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;Database Lock과 분산 락 모두 재고 데이터에 대한 무결성을 검증했습니다. Redis와 통신하는 부분은 로그인 한 유저만 구매할 수 있도록 처리했기 때문에 로그인 정보를 가지고 오는 부분에서 Counting 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yt5yz/btrODvElxP2/lkBJABf7nYYHY9sX8rzMp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yt5yz/btrODvElxP2/lkBJABf7nYYHY9sX8rzMp1/img.png&quot; data-alt=&quot;구매 주문만 Filtering한 Pinpoint Bird View - DB Lock&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yt5yz/btrODvElxP2/lkBJABf7nYYHY9sX8rzMp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyt5yz%2FbtrODvElxP2%2FlkBJABf7nYYHY9sX8rzMp1%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;644&quot; height=&quot;299&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구매 주문만 Filtering한 Pinpoint Bird View - DB Lock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinpoint에서 확인한 결과 구매 요청은 80,518이라고 모니터링되었지만 실제로 생성된 주문 건수는 80,521개로 확인되었고 한 번의 구매 시 10개씩 구매하여 99,194,790개의 재고가 정확히 남는 것을 확인할 수 있었습니다.&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;Next Level&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Database Lock을 통해 Write-Skew를 막아 데이터의 무결성을 보장할 수 있었지만 목표한 TPS에는 한참 미치지 못합니다. 처음 프로젝트 기획 시 적어도 500명의 유저들이 동시에 구매하는 상황까지 예상을 했기 때문에 성능을 더 높일 필요성이 있습니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜 분산 락을 적용했을 때 Connection의 수가 줄었을까? 재확인 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;해당 프로젝트는 네이버 클라우드를 활용하여 진행했습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project/YOUSINSA</category>
      <category>Distributed-Lock</category>
      <category>F-Lab</category>
      <category>Pinpoint</category>
      <category>tomcat</category>
      <category>Write-Skew</category>
      <category>YOUSINSA</category>
      <category>네이버 클라우드</category>
      <category>비관적 락</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/35</guid>
      <comments>https://keydo.tistory.com/35#entry35comment</comments>
      <pubDate>Sat, 15 Oct 2022 12:33:41 +0900</pubDate>
    </item>
    <item>
      <title>[#10] 재고 관리 Integrity 문제 - 1</title>
      <link>https://keydo.tistory.com/34</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구매 부분은 대부분 서비스들의 핵심이고 커머스 도메인에서 재고 관리의 경우 비즈니스와 밀접한 연관이 있다고 생각합니다. 관리측면에서 보면 100개 밖에 없는 상품을 120개 판매했다고 기록한다면 추후 실제 재고를 관리하는 팀에서는 추가 발주를 진행해야 될 수도 있고 만약 추가 발주를 통해 재고가 확보가 안된다면 구매했던 고객들의 상품을 취소해야 합니다. 결국 이런 일들이 반복되면 비즈니스적으로 악영향을 끼칠 것이 분명합니다.&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;400&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn7v8j/btrOreXeveP/pzb4B3Fh8umtk5SQhsYRhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn7v8j/btrOreXeveP/pzb4B3Fh8umtk5SQhsYRhK/img.png&quot; data-alt=&quot;실물상 재고와 서비스상 재고가 일치하지 않는다면?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn7v8j/btrOreXeveP/pzb4B3Fh8umtk5SQhsYRhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn7v8j%2FbtrOreXeveP%2Fpzb4B3Fh8umtk5SQhsYRhK%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;400&quot; height=&quot;231&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실물상 재고와 서비스상 재고가 일치하지 않는다면?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;도메인마다, 서비스마다 다를 수도 있습니다! 결국 Trade-Off가 핵심&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10개의 제품을 10명이 1개씩 구매했지만 2개가 남아있는 상황같이 반대의 경우는 어떨지 생각해보았습니다. 현재 구매를 진행한 고객들은 상품을 잘 받았지만 재고가 있어 추가적으로 구매하려 했지만 실패하는 경우가 생길 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;재고와 구매 주문 수와의 불일치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다품목 구매 테스트와 단품목 구매 테스트를 진행했을 시 때로는 재고와 구매 주문 물품의 갯수가 일치하고 때로는 일치 않는 현상이 발견됐습니다. StackTrace에서 확인해보면 Update 쿼리가 수행될 때, 같은 재고로 수정하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxKnuy/btrOpSHsAmS/XdqjXDaNwccBJO8aAwpzn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxKnuy/btrOpSHsAmS/XdqjXDaNwccBJO8aAwpzn1/img.png&quot; data-alt=&quot;구매 Transaction StackTrace(재고, PK 순)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxKnuy/btrOpSHsAmS/XdqjXDaNwccBJO8aAwpzn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxKnuy%2FbtrOpSHsAmS%2FXdqjXDaNwccBJO8aAwpzn1%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;900&quot; height=&quot;166&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구매 Transaction StackTrace(재고, PK 순)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 확인을 통해 재고 검증 과정에서 Read, 재고 차감 과정에서 Update하는 '구매 주문'이라는 Transaction이 문제라는 것을 알 수 있었습니다. 왜 이런 현상이 발생하는 것일까요?&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;Repeatable Read에서 발생하는 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Database에서 데이터의 무결성을 지키기 위해서는 ACID라는 특성을 갖게 됩니다. 실제 환경에서는 이 특성들을 서로 Trade-Off 하면서 속도 혹은 정합성을 고려해야 합니다. MySQL은 기본적인 Transaction의 Isolation Level(이하. TIL)로 Repetable Read를 채택하고 있습니다. Vendor 사마다 각자만의 장점이 있지만 MySQL의 장점으로는 이론적으로 Serializable 레벨에서 막을 수 있는 Phantom Read를 Repeatable Read 레벨에서도 막을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이런 MySQL에서도 Write-Skew라는 현상은 막을 수 없었습니다. Write-Skew란 Database에 있는 Record를 읽은 값을 통해 애플리케이션에서 판단한 후 다음 동작에서 해당 Record에 있는 값을 업데이트하면서 발생합니다. 구매 주문 과정과 연결시켜 본다면 '재고 검증' -&amp;gt; '구매 주문 접수' -&amp;gt; '재고 차감' 과정에서 재고 검증과 재고 차감이 Write-Skew 발생 조건에 부합합니다.&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;edited_blob&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yi4Oh/btrOtcYHq7B/duJMNixr4jEYkQhgHHqVKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yi4Oh/btrOtcYHq7B/duJMNixr4jEYkQhgHHqVKK/img.png&quot; data-alt=&quot;구매 주문과정 문제 상황 모식도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yi4Oh/btrOtcYHq7B/duJMNixr4jEYkQhgHHqVKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyi4Oh%2FbtrOtcYHq7B%2FduJMNixr4jEYkQhgHHqVKK%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;761&quot; height=&quot;322&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;322&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;위 그림과 같이 하나의 트랜잭션은 재고 검증 단계에서 실패해야 되지만 잠금 없는 일관된 읽기를 지원하는 MySQL에서는 Ta와 Tb 모두 검증 단계를 통과해 구매가 성공하게 됩니다. Write-Skew가 발생해서 재고 데이터의 무결성이 깨지는 상황이 만들어지므로 테스트 시 겪었던 재고 불일치가 발생하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;* Lost-Update는 Write-Skew가 특정 시기, 특정 조건에서 발생하는 이상 현상으로 Write-Skew를 Lost-Update의 일반화된 것으로 생각할 수 있습니다.&lt;br /&gt;* Data Intensive Design 출처&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;이 모든 시작은 동시성 문제라고 생각했습니다. 서비스를 구성하고 있는 요소 중 하나라도 동시성을 해결하기 위한 장치를 마련한다면 데이터의 무결성을 지킬 수 있을 것이라고 판단했습니다. 현재 서비스를 구성하고 있는 요소 중 비즈니스 로직 혹은 상태를 담고 있는 것은 Application과 Database이므로 두 곳에서 해결책을 찾아 나갔습니다.&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;Synchronized&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synchronized 키워드를 사용하여 요청들을 Block하여 처리하면 요청들을 하나씩 처리할 수 있기 때문에 위와 같은 문제가 발생하지 않을 것이나 Scale-Out을 진행함으로써 적절한 해결책은 아닙니다. 요청들이 Load Balancer에 의해 분산될 것이므로 각 Application마다의 동시성 문제는 해결될 수 있으나 전체적인 관점에서 동시성 문제는 해결되지 않습니다. 분산 환경에 사용할 수 있는 해결책을 찾아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 확장성을 더 높이기 위해서는 Application Server를 무상태(Stateless)로 유지해야 됩니다.&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;Distributed Lock&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synchronized와 같이 분산 환경에서는 잠금(Blocking)을 공유할 수 없으므로 공유할 수 있도록 다른 Component를 통해 서로 잠금을 공유하는 것이 분산 락의 기본 원리입니다. Redis, Hazelcast, Ignite 등 In-memory 데이터 베이스에서 분산 락을 지원해주고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJj9kt/btrOq3n905k/t1hC2FVC4vegRiEn1gBoW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJj9kt/btrOq3n905k/t1hC2FVC4vegRiEn1gBoW0/img.png&quot; data-alt=&quot;Distributed-Lock&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJj9kt/btrOq3n905k/t1hC2FVC4vegRiEn1gBoW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJj9kt%2FbtrOq3n905k%2Ft1hC2FVC4vegRiEn1gBoW0%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;372&quot; height=&quot;372&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Distributed-Lock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 사용하고 있는 Redis를 통해서 분산 락을 적용했습니다. Redis Client 중 Lettuce와 Redisson이라는 선택지 중 Redisson을 선택했습니다. Lettuce는 Spin-Lock 방식으로 지속적인 요청으로 Lock이 해제되었는지 확인하고 Redisson은 Pub/Sub 방식을 채택하여 Lock이 해제되면 Subscribe 하고 있는 클라이언트에게 원자적으로 알려주어 Redis의 부하를 줄였습니다.&amp;nbsp;이런 관점에서 Redis Client로 Redisson을 채택했습니다.&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;Database Lock&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 문제가 발생하는 것은 Database이므로 Database에서도 이런 동시성 문제들을 해결할 수 있는 방안을 마련해놨습니다. 바로 Intention Exclusive/Shared Lock입니다. 비관적 Lock이라고도 알려져 있습니다. 다음과 같이 'FOR UPDATE' 키워드를 적용하여 구현할 수 있습니다. 낙관적 Lock을 선택하지 않은 이유는 하나의 상품을 구매하려고 하는 것은 도메인에서 빈번하게 일어날 것이므로 재시도까지 생각했을 때 부하를 더 많이 줄 것으로 판단했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1665632955058&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Pessimistic(비관적) Lock은 Pessimistic Concurrency Control과 혼용되어 쓰이는 것으로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Undo 로그에 있는 Multi Version에 대해서는 Lock을 수행할 수 없으므로 Update를 통해 수정된 값들을 순차적으로 조회합니다.&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;Distributed-Lock과 Database Lock 비교 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 재고 데이터의 무결성과 TPS 성능 모두 포기할 수 없었기 때문에 2개의 방식을 모두 테스트하여 비교했습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.4031%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 34.4961%;&quot;&gt;Distributed-Lock&lt;/td&gt;
&lt;td style=&quot;width: 43.1007%;&quot;&gt;Database Lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.4031%;&quot;&gt;TPS&lt;/td&gt;
&lt;td style=&quot;width: 34.4961%;&quot;&gt;158&lt;/td&gt;
&lt;td style=&quot;width: 43.1007%;&quot;&gt;411.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JOKRO/btrOtcq7nBm/7OLPPvOKL5rYgddywd6AY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JOKRO/btrOtcq7nBm/7OLPPvOKL5rYgddywd6AY0/img.png&quot; data-alt=&quot;Pinpoint에서 확인한 Distributed-Lock과 Database Lock 트랜잭션 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JOKRO/btrOtcq7nBm/7OLPPvOKL5rYgddywd6AY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJOKRO%2FbtrOtcq7nBm%2F7OLPPvOKL5rYgddywd6AY0%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;800&quot; height=&quot;604&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Pinpoint에서 확인한 Distributed-Lock과 Database Lock 트랜잭션 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinpoint Transaction의 응답 속도만 비교했을 때 왼쪽이 전체적인 응답 속도가 빠르므로 Database Lock을 통한 결과로 보여집니다. 하지만 전체적인 성공 응답의 횟수는 오른쪽이 더 큰 것을 볼 수 있습니다. JVM의 지표들도 살펴봤습니다.&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;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;711&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdGMp6/btrOsbsAWsI/I7WqarLAp5jCfViZV4VGh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdGMp6/btrOsbsAWsI/I7WqarLAp5jCfViZV4VGh1/img.png&quot; data-alt=&quot;Pinpoint에서 확인한 Distributed-Lock과 Database Lock JVM 상태 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdGMp6/btrOsbsAWsI/I7WqarLAp5jCfViZV4VGh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdGMp6%2FbtrOsbsAWsI%2FI7WqarLAp5jCfViZV4VGh1%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;900&quot; height=&quot;711&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;711&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Pinpoint에서 확인한 Distributed-Lock과 Database Lock JVM 상태 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM의 상태를 비교해보면 메모리 사용량의 경우 비슷한 양상을 보이지만 CPU 사용량을 비교해보면 상단에 위치한 그래프에서 초기에 증가했다가 더 낮아지는 것을 볼 수 있습니다. 마지막으로 Database의 모니터링도 살펴보았습니다.&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;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6faS8/btrOtF7Pvse/fiwfoxt61Fx7pOtwlc2Ji0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6faS8/btrOtF7Pvse/fiwfoxt61Fx7pOtwlc2Ji0/img.png&quot; data-alt=&quot;Grafana에서 확인한 Distributed-Lock과 Database Lock Database 상태 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6faS8/btrOtF7Pvse/fiwfoxt61Fx7pOtwlc2Ji0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6faS8%2FbtrOtF7Pvse%2Ffiwfoxt61Fx7pOtwlc2Ji0%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;900&quot; height=&quot;721&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Grafana에서 확인한 Distributed-Lock과 Database Lock Database 상태 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 베이스 모니터링 결과에서는 두개의 테스트 모두 비슷한 양상을 보이고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 Database Lock이 TPS 성능이 더 높을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첨부한 지표들이 각각 어떤 테스트에 대한 지표들인지는 의도적으로 밝히지 않았습니다. 이유는 'Database Lock이 TPS 성능이 더 좋다'라는 결론보다 '왜 Database Lock이 진행한 비교 테스트에서 더 TPS 성능이 잘 나올까'가 더 중요하다고 생각했기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2편에서 계속됩니다.&lt;/p&gt;
&lt;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜 Database Lock이 성능이 더 좋을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;해당 프로젝트는 네이버 클라우드를 활용하여 진행했습니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;REF&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.ulogistics.co.kr/test/board.php?board=column2&amp;amp;command=body&amp;amp;no=558&quot;&gt;http://www.ulogistics.co.kr/test/board.php?board=column2&amp;amp;command=body&amp;amp;no=558&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1665629869407&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;최영호의 물류, 기본이 중요하다(9) / 재고관리의 중요성-실물재고와 전산재고 일치의 중요성&quot; data-og-description=&quot;재고관리의 중요성-실물재고와 전산재고 일치의 중요성 &amp;nbsp; &amp;nbsp; 물류센터의 가장 큰 고민중 하나는 정확한 재고관리이다. 재고가 맞지 않다는 것은 실물재고와 전산재고가 일치하지 않는 것을 의&quot; data-og-host=&quot;www.ulogistics.co.kr&quot; data-og-source-url=&quot;http://www.ulogistics.co.kr/test/board.php?board=column2&amp;amp;command=body&amp;amp;no=558&quot; data-og-url=&quot;http://www.ulogistics.co.kr/test/board.php?board=column2&amp;amp;command=body&amp;amp;no=558&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4FZaE/hyP9mqeGUv/33rkMBkoUrv0rCcyKdfmU0/img.png?width=400&amp;amp;height=231&amp;amp;face=0_0_400_231&quot;&gt;&lt;a href=&quot;http://www.ulogistics.co.kr/test/board.php?board=column2&amp;amp;command=body&amp;amp;no=558&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://www.ulogistics.co.kr/test/board.php?board=column2&amp;amp;command=body&amp;amp;no=558&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4FZaE/hyP9mqeGUv/33rkMBkoUrv0rCcyKdfmU0/img.png?width=400&amp;amp;height=231&amp;amp;face=0_0_400_231');&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;최영호의 물류, 기본이 중요하다(9) / 재고관리의 중요성-실물재고와 전산재고 일치의 중요성&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;재고관리의 중요성-실물재고와 전산재고 일치의 중요성 &amp;nbsp; &amp;nbsp; 물류센터의 가장 큰 고민중 하나는 정확한 재고관리이다. 재고가 맞지 않다는 것은 실물재고와 전산재고가 일치하지 않는 것을 의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.ulogistics.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1665632197877&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;레디스와 분산 락(1/2) - 레디스를 활용한 분산 락과 안전하고 빠른 락의 구현&quot; data-og-description=&quot;레디스를 활용한 분산 락에 대해 알아봅니다. 그리고 성능을 높이고 일관성을 보장하는 방법에 대해 알아봅니다.&quot; data-og-host=&quot;hyperconnect.github.io&quot; data-og-source-url=&quot;https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html&quot; data-og-url=&quot;https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jkjxt/hyP9mRjHNA/fkWMw8kw9b5oFpckHrySRK/img.jpg?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256,https://scrap.kakaocdn.net/dn/kk13X/hyP73Z82vJ/EgF42C5EeZCRQsYKP9RC81/img.jpg?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256&quot;&gt;&lt;a href=&quot;https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jkjxt/hyP9mRjHNA/fkWMw8kw9b5oFpckHrySRK/img.jpg?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256,https://scrap.kakaocdn.net/dn/kk13X/hyP73Z82vJ/EgF42C5EeZCRQsYKP9RC81/img.jpg?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256');&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;레디스와 분산 락(1/2) - 레디스를 활용한 분산 락과 안전하고 빠른 락의 구현&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;hyperconnect.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project/YOUSINSA</category>
      <category>Distributed-Lock</category>
      <category>Redisson</category>
      <category>Write-Skew</category>
      <category>YOUSINSA</category>
      <category>네이버 클라우드</category>
      <category>비관적 락</category>
      <category>재고 관리</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/34</guid>
      <comments>https://keydo.tistory.com/34#entry34comment</comments>
      <pubDate>Thu, 13 Oct 2022 00:51:58 +0900</pubDate>
    </item>
    <item>
      <title>[#9] Scale-Out 테스트 결과 분석하기</title>
      <link>https://keydo.tistory.com/32</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scale-Out에 필요한 사전 준비 사항들은 모두 Infra에 반영하여 구성을 완료했습니다. Pinpoint를 Scale-Out 시킨 Server에 모두 설치하여 Application Server를 모니터링할 수 있도록 구성하였고 Database Server의 경우 이전과 마찬가지로 Prometheus, Grafana를 통하여 테스트 결과를 확인했습니다.&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;TPS 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 테스트의 목표는 Scale-Out을 도입하였을 때 어느정도의 성능 향상이 이루어지는지와 또 다른 병목지점을 찾기 위한 목적입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dn9Zpp/btrN9jLusTj/vN18RT7tkdGLWwct0Mmni1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dn9Zpp/btrN9jLusTj/vN18RT7tkdGLWwct0Mmni1/img.png&quot; data-alt=&quot;Scale-Out TPS&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dn9Zpp/btrN9jLusTj/vN18RT7tkdGLWwct0Mmni1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdn9Zpp%2FbtrN9jLusTj%2FvN18RT7tkdGLWwct0Mmni1%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;800&quot; height=&quot;516&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Scale-Out TPS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scale-Out을 위한 사전 작업을 적용하기 전과 Scale-Out 적용된 Infra를 비교했습니다. &lt;span&gt;그래프를 확인해보면 전반적으로 Scale-Out시 성능이 향상 된 것을 볼 수 있습니다.&lt;span&gt; 비교를 해보니 Scale-Out 시 발생하는 현상들을 찾아낼 수 있었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;먼저 Session Management를 도입하므로써 단일 서버의 성능은 이전보다 떨어져 한 개의 Server 성능 * N(Scale-Out Server 개수) 보다 성능이 안 좋게 나오는 것을 확인할 수 있었습니다. 그리고 물품 조회 테스트, 구매 단품목 주문 테스트의 경우 성능이 향상되기는 하지만 목표 기준(1250 TPS)에는 한참 못 미치는 성능을 보이고 특히 구매 단품목 주문 테스트의 경우 8% 정도밖에 향상이 되지 않습니다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;이런 결과를 통해 알 수 있는 것은 결국 Database Lock을 통해 병목이 생기는 부분은 Scale-Out을 진행하더라도 향상 폭이 크지 않다는 것입니다. 앞으로 해결할 병목 개선 지점을 하나 더 찾게 되었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Database Lock을 통해 병목이 생기는 부분을 해결해야 된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Resource 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Scale-Out시 변화하는 CPU, Memory 사용량 지표에 대해서 비교해보며 어떤 변화가 생겼는지 알아보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v5Y1w/btrOlhd5NLc/pOOj2RMpmsVhyWQsmRQWek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v5Y1w/btrOlhd5NLc/pOOj2RMpmsVhyWQsmRQWek/img.png&quot; data-alt=&quot;CPU 사용량 비교 - Application, Database&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v5Y1w/btrOlhd5NLc/pOOj2RMpmsVhyWQsmRQWek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv5Y1w%2FbtrOlhd5NLc%2FpOOj2RMpmsVhyWQsmRQWek%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;1000&quot; height=&quot;318&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CPU 사용량 비교 - Application, Database&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 CPU 사용량에 대해 비교해보면 Application Server의 경우 전반적으로 사용량이 감소했습니다. Request를 처리할 수 있는 자원이 늘어났고 Load Balancer를 통해서 부하를 분산했기 때문에 나타날 수 있는 결과라고 판단했습니다. 이와는 반대로 Database CPU 사용량의 경우 사용량이 증가하는 것을 볼 수 있었습니다. Application Server에서 DBCP에서 connection을 갖고 오는 부분이 분산되므로 Database에 더 많은 Request를 보내 처리량이 증가하여 CPU 사용량이 증가하는 것으로 예측할 수 있었습니다.&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;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s4JRG/btrOlgM1qks/msvSiAjFNHiS3Qj5uRgVi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s4JRG/btrOlgM1qks/msvSiAjFNHiS3Qj5uRgVi0/img.png&quot; data-alt=&quot;Memory 사용량 비교 - Application, Database&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s4JRG/btrOlgM1qks/msvSiAjFNHiS3Qj5uRgVi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs4JRG%2FbtrOlgM1qks%2FmsvSiAjFNHiS3Qj5uRgVi0%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;1000&quot; height=&quot;329&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Memory 사용량 비교 - Application, Database&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;메모리 사용량은 Application Server의 경우 Heap 메모리를 기준으로 모니터링을 진행했습니다. 하지만 GC 수행 여부에 따라서 메모리 사용량이 달라 의미있는 비교 지표를 나타내지 못한다고 판단했습니다. Database의 경우는 전반적으로 상승은 있었으나 유의미한 차이를 보이지 않아 큰 소득이 없었습니다. 다만 아직 메모리로 인해 문제가 나타나는 영역에는 다다르지 못했다는 확인은 할 수 있었습니다.&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;Conclusion&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scale-Out을 진행한 뒤 테스트를 해보며 얻은 병목 지점의 실마리는 Database에서 병목이 생기는 부분으로 인해 Scale-Out으로 처리할 수 있는 한계가 생긴다는 부분입니다. 물품 조회는 페이징 쿼리, 단일 주문 구매의 경우 Database Lock(UPDATE)으로 인해 대기하는 시간이 존재하므로 기존 Application, Database 구조에서 새로운 해결책의 필요성을 볼 수 있었습니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Database Lock으로 인한 병목은 어떻게 해결할 수 있을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;해당 프로젝트는 네이버 클라우드를 활용하여 진행했습니다.&lt;/blockquote&gt;</description>
      <category>Project/YOUSINSA</category>
      <category>F-Lab</category>
      <category>Pinpoint</category>
      <category>prometheus</category>
      <category>Scale-Out</category>
      <category>TPS 비교</category>
      <category>YOUSINSA</category>
      <category>네이버 클라우드</category>
      <author>Key Ryung</author>
      <guid isPermaLink="true">https://keydo.tistory.com/32</guid>
      <comments>https://keydo.tistory.com/32#entry32comment</comments>
      <pubDate>Tue, 11 Oct 2022 11:35:30 +0900</pubDate>
    </item>
  </channel>
</rss>