<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Juno's daily</title>
    <link>https://juno-juno.tistory.com/</link>
    <description>Everything is always impossible until someone does it.</description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 18:48:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>junojuno</managingEditor>
    <image>
      <title>Juno's daily</title>
      <url>https://tistory1.daumcdn.net/tistory/3977847/attach/d17e087be7f141d8ac3ccb64719f95a7</url>
      <link>https://juno-juno.tistory.com</link>
    </image>
    <item>
      <title> &amp;zwj; 5개월차가 생각하는 개발자란..? </title>
      <link>https://juno-juno.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 기술적인 글을 안쓴지도 오래되었고 (조만간 한둘씩 터트릴 예정이다), 미루던 24년 회고글을 올리기에 앞서 본질을 짚어보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 왜 개발자가 되려고 했지에 대해서 말이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pB8al/btsLIy3Rje1/bXRsQd6hQ8beKKbdnKy5p0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pB8al/btsLIy3Rje1/bXRsQd6hQ8beKKbdnKy5p0/img.jpg&quot; data-alt=&quot;평소에는 별거 아니지만 자세히 보면 예쁜 그런걸 표현하고 싶었다  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pB8al/btsLIy3Rje1/bXRsQd6hQ8beKKbdnKy5p0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpB8al%2FbtsLIy3Rje1%2FbXRsQd6hQ8beKKbdnKy5p0%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;606&quot; height=&quot;404&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;404&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;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;회고글에서 이야기 하고싶었지만(이야기 할것이지만), 현재 나는 국내에서 협업툴을 만드는 회사에 재직중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SaaS로 서비스가 운영되고 있고 (on premise로 설치해 주는 부서도 따로 있다) 나는 올해 우리 서비스의 운영업무를 메인으로 맡았다.&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;5일동안 하나의 오류를 해결하는데 쏟다 결국 GG 선언을 했는데 팀장님은 보시고 10분도 안되어서 해결한다거나....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루 종일 하나의 오류 해결을 위해 수십번의 디버깅과 테스트를 거친 결과물이 변수 하나, 파라미터 값 하나 바뀌어서 들어간 것이였다거나...&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&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;아무튼 하소연은 이까지만 하고 개발자란 무엇일까?&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;12월 말에 고마웠던 부트캠프 멘토님께 밥을 사드리며 잔소리를 저녁 먹고 집가기 전 내내 들은것 같은데 그때를 계기로 이런 생각을 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취준때는 얼마나 많은 기술을 알고, 코테를 얼마나 잘하고, 프레임워크와 CS를 잘 아는지가 전부라고 생각했다. 그리고 솔직히 취준할때는 그게 대부분이다. 그걸로 평가를 하니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 취업 후 느낀 개발자. 즉 현 시점, 이 회사에 다닌지 5개월차인 내가 느끼는 개발자는 취준때와는 사뭇 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 생각하는 개발자란, 결국 회사원이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 목적은 수익(돈)이고 회사원은 회사에 돈을 벌어다 줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술도 기술이지만, 회사의 비즈니스 또한 고려를 해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 잘 만든 서비스라도, 팔리지 못하고 회사에 돈을 못가져주면 결국 나라는 개인의 가치도 잃어버리는게 현실이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그렇다고 개발자가 위에서 시키는대로만 해야하는 것도 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서는 제품을 많이 만들어 많이 팔고 싶은데, 많이 팔더라도 오류가 많고 품질이 안좋으면 누가쓰고 싶겠는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짜장면 가게를 오픈한다 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짜장면 가게 오픈전 아무리 인테리어를 예쁘게 해도 짜장면 못팔면 문닫아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 또 짜장면 가게 인테리어가 곧 무너질 것 같이 해놓고, 짜장면 안에서 벌레 나오는 비율이 높다면 그 짜장면집은 오래 지속될 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 개발자는 &lt;b&gt;현 상황을 파악하고 미래의 위협을 감지하고 대비&lt;/b&gt;할 수 있어야하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 &lt;b&gt;발생한 문제를 빠르게 해결할 수 있는 능력&lt;/b&gt;이 있어야 하는 게 맞다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 기술은 문제를 해결하기 위해서 학습하는것이고 존재하는 것이기에 현재 문제를 해결하는게 중요하지 않을까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그게 개발자가 되고 싶었던 목적이자 이유 아닐까.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;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;관계에 대한 고민도 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 개발자란 &lt;b&gt;소통(커뮤니케이션)이 잘 되고, 함께 성장하는 살아있는 문화를 만드는데 기여하는것&lt;/b&gt;도 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소통이 중요한 이유는 결국 회사에서 운영하는 큰 규모의 서비스를 만들기 위해서는 협업이 필요하고 혼자 할 수가 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문화가 중요한 이유는 &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;를 만드는게 중요하다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 나는 신입이라도 할말은 할 수 있어야 한다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 서비스 이대로 가면 이런 문제가 생길것이고 그래서 이렇게 조치를 취해야 한다고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 보수적인 관성을 깨고 더 나은 방식이 있으면 설득을 통한 혁신을 가져와야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 신뢰와 친분을 구분하여, 나는 회사에 신뢰를 주는 개발자가 되야 한다고 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맡은 일 열심히하고, 마감기한 지키고, 할 수 있으면 다른 사람 업무도 좀 덜어주고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 5개월차 병아리 신입의 주절대며 쓴 글이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/잡다한것</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/122</guid>
      <comments>https://juno-juno.tistory.com/122#entry122comment</comments>
      <pubDate>Wed, 8 Jan 2025 22:57:26 +0900</pubDate>
    </item>
    <item>
      <title>  MySQL과 PostgreSQL, 어떤 것을 선택하는게 좋을까? DB 차이점 비교</title>
      <link>https://juno-juno.tistory.com/121</link>
      <description>&lt;div&gt;저는 8월 12일 부로 협업툴 B2B 서비스를 운영하는 회사에 근무하게 되었습니다.&lt;/div&gt;
&lt;div&gt;근황에 대해 짧게 이야기 하자면, 2주간의 온보딩을 마치고 과제를 받아 진행중입니다.&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;✔️ Overview&lt;/b&gt;&lt;/h2&gt;
&lt;div&gt;저는 개발을 학습해 오면서 널리쓰이는 DBMS인 MySQL을 주로 사용/학습을 진행 해 왔습니다.&lt;/div&gt;
&lt;div&gt;제 회사에서는 단일 DB로 PostgreSQL을 사용하는데, 이때 까지 학습한 &lt;b&gt;RDBMS인 MySQL과의 차이점&lt;/b&gt;에 대해 기술해 보고자 합니다! :)&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[차이점 1️⃣]&amp;nbsp;INDEX&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp;MySQL&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;MySQL은 primary index를 &lt;b&gt;테이블 당 반드시 하나 존재&lt;/b&gt;합니다. (지정 하지 않으면 MySQL InnoDB 스토리지 엔진이 자동 생성합니다)&lt;/div&gt;
&lt;div&gt;primary key를 기준으로 primary index가 생성되며, 이는 clusered index라는 중요한 특징을 가집니다.&lt;/div&gt;
&lt;div&gt;clustered index란 PK를 기준으로 물리적으로 디스크에 정렬되어 저장되게 됩니다.&lt;/div&gt;
&lt;div contenteditable=&quot;false&quot; data-cke-pa-onselectstart=&quot;return true&quot; data-cke-img=&quot;%7B%22ONSELECTSTART%22%3A%22return%20true%22%2C%22FILE_SIZE%22%3A%22569119%22%2C%22RGSN_DTTM%22%3A%2220240819203508%22%2C%22ATCH_SRNO%22%3A%2297439537%22%2C%22DRM_YN%22%3A%22N%22%2C%22STTS%22%3A%22%22%2C%22VOICE_WAVEFORM%22%3A%22%22%2C%22THUM_IMG_PATH%22%3A%22https%3A%2F%2Fflow.team%2FflowImg%2FFLOW_20240819359618_cbcca64f-7c7b-4c42-8f81-85f7bf699c53_thumb.png%22%2C%22WIDTH%22%3A%22345%22%2C%22HEIGHT%22%3A%22153%22%2C%22FILE_DOWN_URL%22%3A%22https%3A%2F%2Fflow.team%2FFLOW_DOWNLOAD_R001.act%3FRAND_KEY%3DFLOW_20240819359618_c188aee5-eaf8-408f-9bf5-d14acb7aabe2%26ATCH_SRNO%3D97439537%26USER_ID%3Dssmm0205%26DUID%3D330817-913-524-684541%26OBJ_CNTS_NM%3D%26USE_INTT_ID%3DUTLZ_1608261809693%22%2C%22PLAYTIME%22%3A%220%22%2C%22RGSR_NM%22%3A%22%ED%99%A9%EC%A4%80%ED%98%B8%22%2C%22FILE_NM%22%3A%22image.png%22%2C%22EXPIRE_DTTM%22%3A%22%22%2C%22FILE_TYPE%22%3A%22FLOW%22%2C%22IMG_PATH%22%3A%22https%3A%2F%2Fflow.team%2FflowImg%2FFLOW_20240819359618_c188aee5-eaf8-408f-9bf5-d14acb7aabe2.png%22%2C%22RAND_KEY%22%3A%22FLOW_20240819359618_c188aee5-eaf8-408f-9bf5-d14acb7aabe2%22%2C%22DRM_MSG%22%3A%22%22%2C%22ATCH_URL%22%3A%22https%3A%2F%2Fflow.team%2FflowImg%2FFLOW_20240819359618_c188aee5-eaf8-408f-9bf5-d14acb7aabe2.png%22%2C%22CODE%22%3A%22IMAGE%22%7D&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ekAt4j/btsJtIbxS9q/0yjPZjfLsXHX1Nzng8RLUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ekAt4j/btsJtIbxS9q/0yjPZjfLsXHX1Nzng8RLUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ekAt4j/btsJtIbxS9q/0yjPZjfLsXHX1Nzng8RLUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FekAt4j%2FbtsJtIbxS9q%2F0yjPZjfLsXHX1Nzng8RLUk%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;1280&quot; height=&quot;518&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;따라서 세컨더리 인덱스(primary index를 제외한 모든 인덱스)를 통해서 데이터를 조회하게 된다면,&lt;/div&gt;
&lt;div&gt;세컨더리 인덱스 B-Tree 구조의 leaf 노드에는 pk를 가지게 되고 pk로 primary index를 추가적으로 조회하여 디스크의 페이지에 접근하게 됩니다.&lt;/div&gt;
&lt;div&gt;세컨더리 인덱스가 아닌 primary index를 통해 값을 조회하면 키가 존재하는 페이지와 해당 키 값인 전체 행을 찾을 수 있어서 추가적인 열을 가져오기 위해서 추가적인 IO가 발생하지 않습니다.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp;PostgreSQL&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;1370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc4crN/btsJtFlDfRV/fKXbdaaoyBexcfmHW8hyo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc4crN/btsJtFlDfRV/fKXbdaaoyBexcfmHW8hyo0/img.png&quot; data-alt=&quot;[Example of where Postgres tables are heap organized and all indexes point to the tuple ids]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc4crN/btsJtFlDfRV/fKXbdaaoyBexcfmHW8hyo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc4crN%2FbtsJtFlDfRV%2FfKXbdaaoyBexcfmHW8hyo0%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;554&quot; height=&quot;465&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;1370&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Example of where Postgres tables are heap organized and all indexes point to the tuple ids]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;MySQL과 다르게 PostgreSQL에서는 primary index가 존재하지 않습니다.&lt;/div&gt;
&lt;div&gt;또한, 모든 인덱스는 세컨더리 인덱스입니다. 또한 모든 인덱스는 heap 영역에 로드 된 데이터 페이지에 있는 tuple ID를 가리킵니다.&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;tuple id(TID)&lt;/b&gt;는 두 부분으로 구성된 6byte 숫자입니다.&lt;/li&gt;
&lt;li&gt;첫 번째 부분은 4byte 페이지 번호, 나머지 2byte는 페이지 내 튜플 인덱스입니다.&lt;/li&gt;
&lt;li&gt;위 두 숫자의 조합으로 특정 튜플의 저장 위치를 고유하게 가리킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div contenteditable=&quot;false&quot; data-cke-pa-onselectstart=&quot;return true&quot; data-cke-img=&quot;%7B%22ONSELECTSTART%22%3A%22return%20true%22%2C%22FILE_SIZE%22%3A%2218024%22%2C%22RGSN_DTTM%22%3A%2220240819203835%22%2C%22ATCH_SRNO%22%3A%2297439680%22%2C%22DRM_YN%22%3A%22N%22%2C%22STTS%22%3A%22%22%2C%22VOICE_WAVEFORM%22%3A%22%22%2C%22THUM_IMG_PATH%22%3A%22https%3A%2F%2Fflow.team%2FflowImg%2FFLOW_202408193801735_a1fbbe46-ece1-4cd7-a884-ea5599056e62_thumb.png%22%2C%22WIDTH%22%3A%22345%22%2C%22HEIGHT%22%3A%22188%22%2C%22FILE_DOWN_URL%22%3A%22https%3A%2F%2Fflow.team%2FFLOW_DOWNLOAD_R001.act%3FRAND_KEY%3DFLOW_202408193801735_340312ff-3b18-4d24-86fb-987ab6482896%26ATCH_SRNO%3D97439680%26USER_ID%3Dssmm0205%26DUID%3D330817-913-524-684541%26OBJ_CNTS_NM%3D%26USE_INTT_ID%3DUTLZ_1608261809693%22%2C%22PLAYTIME%22%3A%220%22%2C%22RGSR_NM%22%3A%22%ED%99%A9%EC%A4%80%ED%98%B8%22%2C%22FILE_NM%22%3A%22image.png%22%2C%22EXPIRE_DTTM%22%3A%22%22%2C%22FILE_TYPE%22%3A%22FLOW%22%2C%22IMG_PATH%22%3A%22https%3A%2F%2Fflow.team%2FflowImg%2FFLOW_202408193801735_340312ff-3b18-4d24-86fb-987ab6482896.png%22%2C%22RAND_KEY%22%3A%22FLOW_202408193801735_340312ff-3b18-4d24-86fb-987ab6482896%22%2C%22DRM_MSG%22%3A%22%22%2C%22ATCH_URL%22%3A%22https%3A%2F%2Fflow.team%2FflowImg%2FFLOW_202408193801735_340312ff-3b18-4d24-86fb-987ab6482896.png%22%2C%22CODE%22%3A%22IMAGE%22%7D&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KtPBv/btsJuVUZ3Gp/0oYLAuwKZ67qSUY5kiFYw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KtPBv/btsJuVUZ3Gp/0oYLAuwKZ67qSUY5kiFYw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KtPBv/btsJuVUZ3Gp/0oYLAuwKZ67qSUY5kiFYw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKtPBv%2FbtsJuVUZ3Gp%2F0oYLAuwKZ67qSUY5kiFYw1%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;718&quot; height=&quot;370&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;heap에 있는 테이블 데이터는 mysql의 primary index의 leaf page와 다르게 정렬되어 있지 않습니다.&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Heap은 테이블의 전체 행을 저장하는 저장 영역을 의미합니다.&lt;/li&gt;
&lt;li&gt;여러 페이지로 나뉘며 기본적으로 8KB이며 각 항목 포인터는 페이지 내의 데이터를 가리킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;PostgreSQL 테이블은 index origanized table이 아닌 heap organized table (힙 조직형 테이블)입니다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;또한 중요한 점이, postgreSQL에서는 update와 delete가 실제로는 insert입니다.&lt;/div&gt;
&lt;div&gt;매 새로운 update와 delete가 발생할 때마다 새로운 tuple id가 생성되고, 이전 tuple id는 MVCC때문에 유지됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[차이점 2️⃣]&amp;nbsp; MVCC를 보장하는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVCC는 multi version concurrency control이라는 뜻으로, transaction isolation level과 관련되어 트랜잭션간 데이터의 정합성을 맞춰주는 역할을 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp;MySQL&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;MySQL 8 버전 부터 default 스토리지 엔진으로 채택 된 InnoDB 스토리지 엔진은 레코드 수준까지 트랜잭션 제어를 제공해 줍니다.&lt;/div&gt;
&lt;div&gt;이는 Isolation level에 따라 다른데, default로 &lt;b&gt;REPEATABLE-READ&lt;/b&gt;로 설정되어 있습니다.&lt;/div&gt;
&lt;div&gt;MVCC를 사용하는 이유는 락을 사용하지 않기 위해서인데, 동시성 제어를 위해서 락을 사용하면 가장 쉽지만 동시 요청시 성능이 떨어지기에 MVCC를 사용합니다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;❓어떻게&amp;nbsp;MVCC를 보장하는가&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;MVCC를 MySQL에서 보장하는 방법은 Undo Log(언두로그)를 활용합니다. 언두로그란 트랜잭션 격리 수준을 보장하기 위해 백업해 둔 변경 전의 데이터를 말합니다.&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;MySQL에서는 트랜잭션이 완료되지 않는다면 언두로그가 계속 쌓이기 때문에 트랜잭션을 가능한 짧게 유지해 언두로그가 쌓이지 않게 하는 것이 중요합니다.&lt;/div&gt;
&lt;div&gt;언두로그는 InnoDB 스토리지 엔진에서 자동으로 삭제시킵니다. (Purge라는 과정인데, InnoDB의 백그라운드 스레드가 주기적으로, 자동으로 실행되어 사용자 개입의 필요성이 없습니다)&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp;PostgreSQL&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;PostgreSQL은 MVCC를 위해서 update와 delete시 기존 데이터를 변경/삭제 하는것이 아닌 insert 작업이 일어납니다.&lt;/div&gt;
&lt;div&gt;트랜잭션 격리 레벨의 default는 READ-COMMITTED입니다.&lt;/div&gt;
&lt;div&gt;동시성 제어에 성능상 복잡한 것에 대해 MySQL보다 성능이 뛰어납니다.&lt;/div&gt;
&lt;div&gt;하지만 update / delete 작업이 많이 발생하여 쌓이는 데이터 제거를 위해서 Vacuum이라는 clean up 작업이 필요합니다. (수동의 작업 필요)&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; ️ Vacuum VS Full Vacuum&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vacuum에 일반 베큠과 풀 베큠이라는 두가지 종류가 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반 베큠&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데드 튜플이 재사용가능하다고 마킹해 둬 용량은 그대로라 성능상 이점 얻기는 어렵습니다.&lt;/li&gt;
&lt;li&gt;이후 새로운 업데이트가 이루어진다면 그 데드튜플을 재사용 합니다.&lt;/li&gt;
&lt;li&gt;하지만 업데이트 잦지 않다면 일반 배큠으로 커버 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;풀 베큠&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데드 튜플들 모두 정리(삭제) 합니다.&lt;/li&gt;
&lt;li&gt;바로 용량 확보하고 인덱스 팽창을 해결 합니다.&lt;/li&gt;
&lt;li&gt;Full Vacuum 수행 시 테이블에 lock이 걸려 모든 트랜잭션이 거부 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;하지만 문제는 운영 영향도로서 확실한 청소 방법이지만 24시간 운영된다면 고려할 사항이 많습니다.&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Auto Vacuum&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자동으로, 정기적으로 vacuum을 수행합니다. (lock 발생 x)&lt;/li&gt;
&lt;li&gt;일반 베큠 + 통계를 갱신하기에 영향도를 잘 따져 설정해야 합니다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[차이점 3️⃣]&amp;nbsp;Thread 기반 vs Process 기반&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp;MySQL&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 기반입니다.&lt;/li&gt;
&lt;li&gt;thread 간 컨텍스트 스위칭 시 TCB를 안전하게 공유 가능하지만 PC, Stack Pointer, Register는 업데이트 시켜주어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp;PostgreSQL&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스 기반입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 가상 메모리 오버헤드 + PCB가 TCB에 비해 커서 메모리를 많이 잡아 먹습니다.&lt;/li&gt;
&lt;li&gt;컨텍스트 스위칭 시에도 프로세스간 컨텍스트 스위칭시에는 TLB를 무효화 시켜주는 작업이 필요합니다.&lt;/li&gt;
&lt;li&gt;각 프로세스는 독립된 가상 메모리를 가지기에 하나의 프로세스가 죽더라도 다른 프로세스에 영향을 미치지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개인적인 추측, 어떤 RDBMS를 선택해야 할까?&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;&amp;rArr; 메모리 공유에 있어서는 thread 기반 RDBMS가 더 효율적일 것이라 판단됩니다. 하지만 안정성에 있어서는 PostgreSQL이 더 우위에 있을것이라 예상됩니다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://medium.com/@hnasr/postgres-vs-mysql-5fa3c588a94e&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@hnasr/postgres-vs-mysql-5fa3c588a94e&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위 블로그를 정리한 개인 노션 페이지 입니다)&lt;/p&gt;
&lt;figure id=&quot;og_1725797543623&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;MySQL vs PostgreSQL | Notion&quot; data-og-description=&quot;간단하게 두 데이터베이스의 주요 차이점은&quot; data-og-host=&quot;kaput-trombone-343.notion.site&quot; data-og-source-url=&quot;https://kaput-trombone-343.notion.site/MySQL-vs-PostgreSQL-f892abc0eec344938184057fd769707d?pvs=4&quot; data-og-url=&quot;https://kaput-trombone-343.notion.site/MySQL-vs-PostgreSQL-f892abc0eec344938184057fd769707d&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/L95jc/hyW2U1pxz9/PfoOWGzeCqbF9GIQKIDiZ1/img.png?width=2000&amp;amp;height=1050&amp;amp;face=0_0_2000_1050,https://scrap.kakaocdn.net/dn/cTnBH2/hyWZegc7WP/EACgCSGgmw0sW0pkk230J0/img.png?width=2000&amp;amp;height=1050&amp;amp;face=0_0_2000_1050&quot;&gt;&lt;a href=&quot;https://kaput-trombone-343.notion.site/MySQL-vs-PostgreSQL-f892abc0eec344938184057fd769707d?pvs=4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kaput-trombone-343.notion.site/MySQL-vs-PostgreSQL-f892abc0eec344938184057fd769707d?pvs=4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/L95jc/hyW2U1pxz9/PfoOWGzeCqbF9GIQKIDiZ1/img.png?width=2000&amp;amp;height=1050&amp;amp;face=0_0_2000_1050,https://scrap.kakaocdn.net/dn/cTnBH2/hyWZegc7WP/EACgCSGgmw0sW0pkk230J0/img.png?width=2000&amp;amp;height=1050&amp;amp;face=0_0_2000_1050');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL vs PostgreSQL | Notion&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;kaput-trombone-343.notion.site&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>Programming/Database</category>
      <category>MYSQL</category>
      <category>PostgreSQL</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/121</guid>
      <comments>https://juno-juno.tistory.com/121#entry121comment</comments>
      <pubDate>Sat, 7 Sep 2024 20:00:21 +0900</pubDate>
    </item>
    <item>
      <title> &amp;zwj;  금칙어 필터링 기능을 분석하고 리팩토링 해보자! - (2) 실제 성능을 측정해보자!</title>
      <link>https://juno-juno.tistory.com/118</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://juno-juno.tistory.com/117&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&amp;nbsp;금칙어 필터링 기능을 분석하고 리팩토링 해보자!(1)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 포스팅에서 나는 기존 팀원이 짜놓은 코드를 리팩토링 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 라이브러리를 이해하고 리팩토링 한 것 뿐아니라, 파일로 읽던 금칙어 목록들을 DB로 이전시켰다.&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;b&gt;  파일로 금칙어를 읽어 처리하는데의 문제점&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ulR49/btsHx9UlpAX/1r2qbEox4kRyiZZ0qX3muK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ulR49/btsHx9UlpAX/1r2qbEox4kRyiZZ0qX3muK/img.png&quot; data-alt=&quot;Sub module 아래의 금칙어 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ulR49/btsHx9UlpAX/1r2qbEox4kRyiZZ0qX3muK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FulR49%2FbtsHx9UlpAX%2F1r2qbEox4kRyiZZ0qX3muK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;465&quot; height=&quot;343&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Sub module 아래의 금칙어 파일&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;b&gt;[문제점 1]&lt;/b&gt; 금칙어 목록을 Continuous Delivery 과정에서 jar 파일 뿐 아니라 금칙어 파일도 S3에 같이 저장해줘야 했다. 그래야 application 실행 시점에 메모리에 읽을 수 있으니깐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[문제점 2]&lt;/b&gt; 금칙어를 추가하거나, 삭제하려면 다시 배포를 해야한다. 그러면, 서브모듈 관리도 해야하고, 버전 관리에 금칙어 텍스트 파일 관리가 포함되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 코드가 아닌 단순 목록 관리가 버전 관리에 포함되게 되는 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[문제점 3]&lt;/b&gt;&amp;nbsp;금칙어에 대한 추가적인 요구사항이 발생했을때 확장성이 떨어진다. 예를들어, 사람들이 자주 입력하는 금칙어에 대한 통계정보가 필요한 경우 어떻게 기능추가를 할 수 있을까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 어차피 DB로 테이블을 분리해야하기 때문에, 처음부터 파일이 아닌 DB로 분리하는게 맞다고 판단했다.&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;b&gt;  이렇게 해결했어요!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서, 나는 DB로 금칙어 목록을 관리하는게 맞다고 판단했고, 다음과 같이 금칙어에 대한 변동사항이 생기면 서버를 껐다가 다시 키는게 아닌, api를 통해 동적으로 관리할 수 있도록 구조를 변경했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 4가지 api를 추가했다.&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;235&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3ybUg/btsHw5Mb8GE/zk0rKTVHtiQY7LfZtdHI7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3ybUg/btsHw5Mb8GE/zk0rKTVHtiQY7LfZtdHI7k/img.png&quot; data-alt=&quot;추가한 금칙어 관리 api들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3ybUg/btsHw5Mb8GE/zk0rKTVHtiQY7LfZtdHI7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3ybUg%2FbtsHw5Mb8GE%2Fzk0rKTVHtiQY7LfZtdHI7k%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;235&quot; height=&quot;131&quot; data-origin-width=&quot;235&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;추가한 금칙어 관리 api들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래와 같이 문장에서 발견된 금칙어에 대해 사용횟수도 올려주어 통계정보로 사용할 수 있게 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUaYGi/btsHxF7f3EB/YH181SYzg9ajfe4aYXGv3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUaYGi/btsHxF7f3EB/YH181SYzg9ajfe4aYXGv3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUaYGi/btsHxF7f3EB/YH181SYzg9ajfe4aYXGv3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUaYGi%2FbtsHxF7f3EB%2FYH181SYzg9ajfe4aYXGv3k%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;746&quot; height=&quot;341&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;816&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;또한, 기존 파일로 관리되던 금칙어 목록을 Client에서 필요 할 수도 있기 때문에, 영향을 최소화하기 위해서 CSV로 금칙어에 대한 정보를 추출 할 수 있게 만들었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api 호출 시마다 CSV 파일을 만드는 것은 비효율적이며 서버에 많은 부하를 줄 수 있기 때문에 아래와 같이 CSV 파일을 만들어 S3에 저장하고, 금칙어에 대한 변경사항이 생긴 시간 (LastModifiedDate 컬럼 중 가장 최근 값)과 다운로드 시간을 비교해 변동사항이 없다면 기존 생성해 둔 CSV 경로를 메모리에 저장해두고 리턴하게 만들었다. (CSV 생성은 apache common csv 라이브러리를 이용했다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AxlEQ/btsHwggySR1/dJAHACgjcXaz8bYdETYMMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AxlEQ/btsHwggySR1/dJAHACgjcXaz8bYdETYMMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AxlEQ/btsHwggySR1/dJAHACgjcXaz8bYdETYMMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAxlEQ%2FbtsHwggySR1%2FdJAHACgjcXaz8bYdETYMMK%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;1784&quot; height=&quot;194&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;194&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;그리고 아래와 같이 금칙어에 대한 변동사항이 있을때, Concurrent Radix Tree의 이점을 최대한 활용하여 trie또한 업데이트 시켜주었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2066&quot; data-origin-height=&quot;1600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOBvBl/btsHw6RUhIz/tfp9h0fSSl4gdYc5oW89Gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOBvBl/btsHw6RUhIz/tfp9h0fSSl4gdYc5oW89Gk/img.png&quot; data-alt=&quot;Profanity Loader 클래스, Trie를 관리하는 역할을 한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOBvBl/btsHw6RUhIz/tfp9h0fSSl4gdYc5oW89Gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOBvBl%2FbtsHw6RUhIz%2Ftfp9h0fSSl4gdYc5oW89Gk%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;2066&quot; height=&quot;1600&quot; data-origin-width=&quot;2066&quot; data-origin-height=&quot;1600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Profanity Loader 클래스, Trie를 관리하는 역할을 한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 이 Concurrent Tree 라이브러리를 사용하면 아호코라식 알고리즘을 사용하지 못한다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  아호코라식 알고리즘을 도입해야할까? 라이브러리들을 비교해보자!&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wIHZ3/btsHwwKgLQJ/ZxkksJskGheNViWX9N4mf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wIHZ3/btsHwwKgLQJ/ZxkksJskGheNViWX9N4mf0/img.png&quot; data-alt=&quot;Concurrent-Trees 라이브러리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wIHZ3/btsHwwKgLQJ/ZxkksJskGheNViWX9N4mf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwIHZ3%2FbtsHwwKgLQJ%2FZxkksJskGheNViWX9N4mf0%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;845&quot; height=&quot;305&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Concurrent-Trees 라이브러리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 사용하고 있던 ConcurrentInvertedRadixTree는 금칙어 개수에 상관없이(n) 입력받는 문자열 d의 길이 * log(키워드 평균길이)의 시간복잡도를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/robert-bor/aho-corasick&quot;&gt;https://github.com/robert-bor/aho-corasick&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716293692025&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - robert-bor/aho-corasick: Java implementation of the Aho-Corasick algorithm for efficient string matching&quot; data-og-description=&quot;Java implementation of the Aho-Corasick algorithm for efficient string matching - robert-bor/aho-corasick&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/robert-bor/aho-corasick&quot; data-og-url=&quot;https://github.com/robert-bor/aho-corasick&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0KKIE/hyV9QzglAq/XfA9RRkA3sDzHHrrjoLMeK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/robert-bor/aho-corasick&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/robert-bor/aho-corasick&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0KKIE/hyV9QzglAq/XfA9RRkA3sDzHHrrjoLMeK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - robert-bor/aho-corasick: Java implementation of the Aho-Corasick algorithm for efficient string matching&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Java implementation of the Aho-Corasick algorithm for efficient string matching - robert-bor/aho-corasick&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 아호 코라식 알고리즘을 제공하는 위 라이브러리를 확인해보면, 다음과 같은 말을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mrFZL/btsHwC4Emld/070UulKcR2Rpx3kmEGfKO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mrFZL/btsHwC4Emld/070UulKcR2Rpx3kmEGfKO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mrFZL/btsHwC4Emld/070UulKcR2Rpx3kmEGfKO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmrFZL%2FbtsHwC4Emld%2F070UulKcR2Rpx3kmEGfKO1%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;822&quot; height=&quot;62&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마나 많은 키워드가 주어지더라도, O(n)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;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;하지만 위 라이브러리의 문제점은, 트라이 생성에 있는데, 아래와 같이 Trie 생성이 한번 생성되면 고정되고, 변경할 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 동적으로 금칙어를 추가/삭제할 때 Trie에 추가하는게 아닌 Trie 자체를 다시 만들어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 Trie를 금칙어 추가 / 삭제 기능이 일어날때마다 Trie를 다시 만들어줘야하고, 그런 삭제 / 변경이 빈번할 경우 동시성 문제도 고려해야하고, 매번 다시 메모리에 생성하는데 비용이 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ETOvY/btsHvVRkogc/4PWcWON9xp8GOj0pI0RbaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ETOvY/btsHvVRkogc/4PWcWON9xp8GOj0pI0RbaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ETOvY/btsHvVRkogc/4PWcWON9xp8GOj0pI0RbaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FETOvY%2FbtsHvVRkogc%2F4PWcWON9xp8GOj0pI0RbaK%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;794&quot; height=&quot;179&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;190&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;가장 크게 아호코라식 알고리즘을 제공하는 라이브러리를 사용하는 것을 멈춘 이유는 금칙어는 상대적으로 긴 단어가 없다. 길어도 5글자가 대부분이라 시간복잡도는 거의 차이가 나지 않을 것으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 기존 라이브러리로도 충분한 성능을 낼 것으로 기대되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;✨ 성능 개선 결과&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성능을 측정하기 위해서 Spring에서 제공하는 StopWatch를 &lt;b&gt;local에서만 사용할 수 있도록&lt;/b&gt; AOP를 이용한 어노테이션으로 만들어서 메서드 시간 측정을 진행했다.&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;전역에서 금칙어 필터링을 DTO에서 처리하기 위해서 Custom annotation으로 만들어 검증했는데 해당 `isValid()` 메서드 stopwatch를 붙혀 결과를 측정했다.&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;DB에서 가지고와 List를 모두 순회할 경우&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjRgtO/btsHtf3nGm9/NyvuTArLnaihyM3H4h8JKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjRgtO/btsHtf3nGm9/NyvuTArLnaihyM3H4h8JKk/img.png&quot; data-alt=&quot;글자 500자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjRgtO/btsHtf3nGm9/NyvuTArLnaihyM3H4h8JKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjRgtO%2FbtsHtf3nGm9%2FNyvuTArLnaihyM3H4h8JKk%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;글자 500자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nwGMy/btsHtNyxWXo/mIkhNKeUWENsHNsVCJPsX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nwGMy/btsHtNyxWXo/mIkhNKeUWENsHNsVCJPsX1/img.png&quot; data-alt=&quot;1000자 (비속어 포함 x)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nwGMy/btsHtNyxWXo/mIkhNKeUWENsHNsVCJPsX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnwGMy%2FbtsHtNyxWXo%2FmIkhNKeUWENsHNsVCJPsX1%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1000자 (비속어 포함 x)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2bS7Z/btsHuQt8SPF/Xx1IeNpNA28meTav8L1I8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2bS7Z/btsHuQt8SPF/Xx1IeNpNA28meTav8L1I8k/img.png&quot; data-alt=&quot;비속어 포함 1000자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2bS7Z/btsHuQt8SPF/Xx1IeNpNA28meTav8L1I8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2bS7Z%2FbtsHuQt8SPF%2FXx1IeNpNA28meTav8L1I8k%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비속어 포함 1000자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IY8St/btsHt4mlXux/OxYw9Tukh7fNxXjTD346mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IY8St/btsHt4mlXux/OxYw9Tukh7fNxXjTD346mK/img.png&quot; data-alt=&quot;5000자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IY8St/btsHt4mlXux/OxYw9Tukh7fNxXjTD346mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIY8St%2FbtsHt4mlXux%2FOxYw9Tukh7fNxXjTD346mK%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5000자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BqyPU/btsHxyGSxts/ZRp8NjNA2QOekuTSKyjOA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BqyPU/btsHxyGSxts/ZRp8NjNA2QOekuTSKyjOA0/img.png&quot; data-alt=&quot;금칙어 1000개, 글자수 5000자 기준, List 사용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BqyPU/btsHxyGSxts/ZRp8NjNA2QOekuTSKyjOA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBqyPU%2FbtsHxyGSxts%2FZRp8NjNA2QOekuTSKyjOA0%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;1474&quot; height=&quot;208&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;금칙어 1000개, 글자수 5000자 기준, List 사용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQDc2V/btsHxq3lSBH/41FrvRIKlBSqNJt7l7JhtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQDc2V/btsHxq3lSBH/41FrvRIKlBSqNJt7l7JhtK/img.png&quot; data-alt=&quot;금칙어 1000개, 글자수 5000자 기준, List 사용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQDc2V/btsHxq3lSBH/41FrvRIKlBSqNJt7l7JhtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQDc2V%2FbtsHxq3lSBH%2F41FrvRIKlBSqNJt7l7JhtK%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;1474&quot; height=&quot;220&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;금칙어 1000개, 글자수 5000자 기준, List 사용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모리에 있는  Trie를 사용해 순회할 경우&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBJUZ3/btsHudDuKEi/n3xuLzEspaEMdGxq5Ut601/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBJUZ3/btsHudDuKEi/n3xuLzEspaEMdGxq5Ut601/img.png&quot; data-alt=&quot;글자 500자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBJUZ3/btsHudDuKEi/n3xuLzEspaEMdGxq5Ut601/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBJUZ3%2FbtsHudDuKEi%2Fn3xuLzEspaEMdGxq5Ut601%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;글자 500자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJLjC/btsHuxavyrt/V9JlcOKfIXZm8GyVg2Dds1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJLjC/btsHuxavyrt/V9JlcOKfIXZm8GyVg2Dds1/img.png&quot; data-alt=&quot;1000자 (비속어 포함 x)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJLjC/btsHuxavyrt/V9JlcOKfIXZm8GyVg2Dds1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJLjC%2FbtsHuxavyrt%2FV9JlcOKfIXZm8GyVg2Dds1%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1000자 (비속어 포함 x)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CC9t7/btsHua7OMUB/6KT31fNkyOOBsBeKbOO8W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CC9t7/btsHua7OMUB/6KT31fNkyOOBsBeKbOO8W0/img.png&quot; data-alt=&quot;1000자 (비속어 포함 x)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CC9t7/btsHua7OMUB/6KT31fNkyOOBsBeKbOO8W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCC9t7%2FbtsHua7OMUB%2F6KT31fNkyOOBsBeKbOO8W0%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1000자 (비속어 포함 x)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qZoz4/btsHucdwC14/KNHyKevk6CBvSqCJ0d0VKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qZoz4/btsHucdwC14/KNHyKevk6CBvSqCJ0d0VKk/img.png&quot; data-alt=&quot;비속어 포함 1000자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qZoz4/btsHucdwC14/KNHyKevk6CBvSqCJ0d0VKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqZoz4%2FbtsHucdwC14%2FKNHyKevk6CBvSqCJ0d0VKk%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비속어 포함 1000자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vp4hf/btsHvt6r0tz/AFMbSa2Gd6iKElhauxtKKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vp4hf/btsHvt6r0tz/AFMbSa2Gd6iKElhauxtKKk/img.png&quot; data-alt=&quot;5천자, 비속어 포함&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vp4hf/btsHvt6r0tz/AFMbSa2Gd6iKElhauxtKKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvp4hf%2FbtsHvt6r0tz%2FAFMbSa2Gd6iKElhauxtKKk%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5천자, 비속어 포함&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJbzK/btsHtF1GTdG/lvxuJRAQV7BMfokndRn5Mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJbzK/btsHtF1GTdG/lvxuJRAQV7BMfokndRn5Mk/img.png&quot; data-alt=&quot;5천자, 비속어 포함&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJbzK/btsHtF1GTdG/lvxuJRAQV7BMfokndRn5Mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJbzK%2FbtsHtF1GTdG%2FlvxuJRAQV7BMfokndRn5Mk%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;1468&quot; height=&quot;218&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5천자, 비속어 포함&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kw1V1/btsHw47nhxX/obbdD9XzjAxPfKfBSLeCyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kw1V1/btsHw47nhxX/obbdD9XzjAxPfKfBSLeCyk/img.png&quot; data-alt=&quot;금칙어 1000개, 글자수 5000자 기준 (Trie) 사용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kw1V1/btsHw47nhxX/obbdD9XzjAxPfKfBSLeCyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKw1V1%2FbtsHw47nhxX%2FobbdD9XzjAxPfKfBSLeCyk%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;1294&quot; height=&quot;188&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;금칙어 1000개, 글자수 5000자 기준 (Trie) 사용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxr4ZK/btsHxDaiIwc/1jTrFC3fdw4pFVpkwZPJfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxr4ZK/btsHxDaiIwc/1jTrFC3fdw4pFVpkwZPJfk/img.png&quot; data-alt=&quot;금칙어 1000개, 글자수 5000자 기준 (Trie) 사용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxr4ZK/btsHxDaiIwc/1jTrFC3fdw4pFVpkwZPJfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxr4ZK%2FbtsHxDaiIwc%2F1jTrFC3fdw4pFVpkwZPJfk%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;1354&quot; height=&quot;200&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;금칙어 1000개, 글자수 5000자 기준 (Trie) 사용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cckeVt/btsHv8WYBK5/xJABGajtZYSVxHnhROIj7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cckeVt/btsHv8WYBK5/xJABGajtZYSVxHnhROIj7k/img.png&quot; data-alt=&quot;금칙어 1000개, 글자수 5000자 기준 (Trie) 사용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cckeVt/btsHv8WYBK5/xJABGajtZYSVxHnhROIj7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcckeVt%2FbtsHv8WYBK5%2FxJABGajtZYSVxHnhROIj7k%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;1354&quot; height=&quot;200&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;금칙어 1000개, 글자수 5000자 기준 (Trie) 사용&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;위 결과를 보면 Trie를 사용할 경우 응답 시간이 &lt;b&gt;약 2.5배 이상&lt;/b&gt; 단축되는 것을 확인할 수 있었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/15764/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.woowahan.com/15764/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713609514688&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;고르곤졸라는 되지만 고르곤 졸라는 안 돼! 배달의민족에서 금칙어를 관리하는 방법 | 우아한형&quot; data-og-description=&quot;{{item.name}} 안녕하세요! 셀러시스템팀에서 서버 개발을 하고 있는 김예빈이라고 합니다. 배달의민족에는 금칙어를 관리하는 &amp;quot;통합금칙어시스템&amp;quot;이라는 것이 있습니다. 금칙어란? 법 혹은 규칙으&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/15764/&quot; data-og-url=&quot;https://techblog.woowahan.com/15764/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/q8Rka/hyVS9fataY/KcxSTdxR6npyCMdGEbmoa0/img.png?width=4000&amp;amp;height=2088&amp;amp;face=0_0_4000_2088,https://scrap.kakaocdn.net/dn/f8rwU/hyVSYdAjTs/7q1SyH2Q5SKFOlDcKLuBo1/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bL7TLB/hyVSVnDeLz/PdS70mmXSxaeSagOYkG0l1/img.jpg?width=1586&amp;amp;height=1402&amp;amp;face=0_0_1586_1402&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/15764/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/15764/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/q8Rka/hyVS9fataY/KcxSTdxR6npyCMdGEbmoa0/img.png?width=4000&amp;amp;height=2088&amp;amp;face=0_0_4000_2088,https://scrap.kakaocdn.net/dn/f8rwU/hyVSYdAjTs/7q1SyH2Q5SKFOlDcKLuBo1/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bL7TLB/hyVSVnDeLz/PdS70mmXSxaeSagOYkG0l1/img.jpg?width=1586&amp;amp;height=1402&amp;amp;face=0_0_1586_1402');&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;{{item.name}} 안녕하세요! 셀러시스템팀에서 서버 개발을 하고 있는 김예빈이라고 합니다. 배달의민족에는 금칙어를 관리하는 &quot;통합금칙어시스템&quot;이라는 것이 있습니다. 금칙어란? 법 혹은 규칙으&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.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>Programming</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/118</guid>
      <comments>https://juno-juno.tistory.com/118#entry118comment</comments>
      <pubDate>Tue, 21 May 2024 23:48:16 +0900</pubDate>
    </item>
    <item>
      <title> &amp;zwj;  금칙어 필터링 기능을 분석하고 리팩토링 해보자! (1) - Trie 자료구조 및 라이브러리들 분석</title>
      <link>https://juno-juno.tistory.com/117</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Overview&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Space club 프로젝트를 진행할 당시, 각 클럽 별 게시글을 작성하거나 공지, 댓글등을 작성할 때 금칙어가 포함되어 있을 경우 아래 그림과 같이 작성에 실패했다는 모달을 보여주며 작성에 실패하게 처리를 했었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때, 입력받는 값에 금칙어를 찾는 과정에 있어서 Trie 자료구조를 사용했는데, 그때의 생각프로세스를 정리해 보고자 한다.&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;935&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Czcz2/btsGNA6YQWm/tau26knXI6WP57zxkHAX50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Czcz2/btsGNA6YQWm/tau26knXI6WP57zxkHAX50/img.png&quot; data-alt=&quot;글 입력시 금칙어가 포함되어 있을 경우 작성 불가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Czcz2/btsGNA6YQWm/tau26knXI6WP57zxkHAX50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCzcz2%2FbtsGNA6YQWm%2Ftau26knXI6WP57zxkHAX50%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;728&quot; height=&quot;635&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;816&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;글 입력시 금칙어가 포함되어 있을 경우 작성 불가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  자료구조 / 알고리즘 선택하기!&lt;/b&gt;&lt;/h2&gt;
&lt;p 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;먼저, 금칙어를&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;List 자료구조로 저장해 놓고 비교&lt;/b&gt;를 한다면, 금칙어 List 크기가 N이고, 입력받는 문자열의 길이가 M일때,&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;시간복잡도는 O(MN)&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;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;b&gt;KMP 알고리즘&lt;/b&gt;이였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다행히 KMP 알고리즘은 예전에 알고리즘을 학습하며 정리해 놓은 글이 있어 &lt;a href=&quot;https://juno-juno.tistory.com/60&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[여기]&lt;/a&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;[KMP 알고리즘의 한계]&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 KMP 알고리즘은 하나의 긴 패턴을 대량의 텍스트에서 검색할 경우에 효과적이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문자열 알고리즘이라고 해서 떠올려봤지만, KMP 알고리즘은&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리가 금칙어 개수가 N일경우, N마다 O(M+ P)의 시간복잡도가 걸리기에 (P는 금칙어의 길이) &lt;b&gt;전체 시간복잡도는 대략적으로 O(MN)이 된다. ➡ 효과 없음&lt;/b&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;그래서 나는 어떤 것을 사용하는지 찾아보았고, &lt;b&gt;Trie 자료구조&lt;/b&gt;를 사용한다면 문자열 검색에 효율적인 것을 알 수 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가로, &lt;b&gt;아호코라식(Aho-Corasick) 알고리즘&lt;/b&gt;이라는 것을 알게되었고, 이번에 공유해 보고자 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Trie 자료 구조란&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트라이는 트리의 일종으로 문자열을 다루는 자료구조이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 노드는 문자 하나와 문자열 완성 여부를 나타내며, 트라이를 어떤 경로로 타고 내려가는지에 따라 문자열이 조합된다.&lt;/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;450&quot; data-origin-height=&quot;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckMzYL/btsHpa1GtOa/1l4em233NnRcVFfgd2j9I0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckMzYL/btsHpa1GtOa/1l4em233NnRcVFfgd2j9I0/img.png&quot; data-alt=&quot;Tree 자료구조 / 출처 : https://www.javatpoint.com/tree&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckMzYL/btsHpa1GtOa/1l4em233NnRcVFfgd2j9I0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckMzYL%2FbtsHpa1GtOa%2F1l4em233NnRcVFfgd2j9I0%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;213&quot; height=&quot;215&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Tree 자료구조 / 출처 : https://www.javatpoint.com/tree&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Tree자료구조에서 이진 검색으로 특정 노드를 검색하는 것은 O(logN)의 시간복잡도가 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 정수가 아닌 문자열을 검색하면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드에 있는 문자열의 길이가 M일때, 시간복잡도는 O(MlogN)이 된다. (정렬된 문자를 찾고, 문자열을 순회해야 하기 때문에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해서 &lt;b&gt;Trie라는 자료구조&lt;/b&gt;를 이용할 수 있는데,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHv5oS/btsHqO3UQ4Q/50qlTXKwYZARWB9xlG3kQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHv5oS/btsHqO3UQ4Q/50qlTXKwYZARWB9xlG3kQ0/img.png&quot; data-alt=&quot;Trie 자료구조 / 출처 : https://en.wikipedia.org/wiki/Trie&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHv5oS/btsHqO3UQ4Q/50qlTXKwYZARWB9xlG3kQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHv5oS%2FbtsHqO3UQ4Q%2F50qlTXKwYZARWB9xlG3kQ0%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;250&quot; height=&quot;234&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Trie 자료구조 / 출처 : https://en.wikipedia.org/wiki/Trie&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trie 자료구조는 하나의 노드가 26개(알파벳)*2(대소문자) + 숫자 (0~9) 총 62개 노드를 가질 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 저장해놓는다면&lt;b&gt;, 문자열의 길이가 M일때, 검색을 O(M + K)의 시간으로 찾을 수 있다. (K는 입력 문자열에 포함된 금칙어 총 길이)&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;이 Trie 자료구조를 사용한다면, 다수의 금칙어를 한번의 입력 문자열 스캔만으로 검사을 할 수 있다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서 이러한 정보를 바탕으로 우리 서비스의 금칙어 처리 기능에 Trie 자료구조를 도입했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  아호 코라식 알고리즘이란? &lt;b&gt;(Aho-Corasick Algorithm) &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아호 코라식 알고리즘이 뭔지 찾아보니, KMP 알고리즘이 일대일 문자열 패턴 매칭 알고리즘이였다면, &lt;b&gt;아호 코라식 알고리즘이란 일대다 문자열 패턴매칭에 사용되는 알고리즘이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아호 코라식 알고리즘은 &lt;b&gt;KMP 알고리즘의 확장&lt;/b&gt;이며, 메커니즘은 비슷하다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;KMP 알고리즘이 문자열과 문자열간의 매칭이라면, 아호 코라식은 문자열과 트라이 간의 매칭이다.&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;원리는, 다음과 같은 트라이가 있을 때, (S는 입력받는 문자열, W가 금칙어 목록들이라 생각하면 된다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아래와 같이 문자열 S를 돌면서 금칙어 여부를 판별하는데, 매칭에 실패 했을 경우 포인터를 실패한 문자 전 까지의 일치한 문자열에 대해 접두사와 접미사가 같은 지점으로 이동시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3V33t/btsHq6wBraJ/K5GsrH25AW1WdXXtOxywK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3V33t/btsHq6wBraJ/K5GsrH25AW1WdXXtOxywK1/img.png&quot; data-alt=&quot;출처: https://pangtrue.tistory.com/305&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3V33t/btsHq6wBraJ/K5GsrH25AW1WdXXtOxywK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3V33t%2FbtsHq6wBraJ%2FK5GsrH25AW1WdXXtOxywK1%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;570&quot; height=&quot;271&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://pangtrue.tistory.com/305&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT5WlG/btsHoJp3xwM/Vqjjr9ePFqQ0AzQwCQOZNK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT5WlG/btsHoJp3xwM/Vqjjr9ePFqQ0AzQwCQOZNK/img.gif&quot; data-alt=&quot;트라이에서 매칭 과정 ❘ 출처: https://pangtrue.tistory.com/305&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT5WlG/btsHoJp3xwM/Vqjjr9ePFqQ0AzQwCQOZNK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bT5WlG/btsHoJp3xwM/Vqjjr9ePFqQ0AzQwCQOZNK/img.gif&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;569&quot; height=&quot;292&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;트라이에서 매칭 과정 ❘ 출처: https://pangtrue.tistory.com/305&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csFWTM/btsHqmtr5as/Zw4gQQAkCnKWQ9kko7e6HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csFWTM/btsHqmtr5as/Zw4gQQAkCnKWQ9kko7e6HK/img.png&quot; data-alt=&quot;트라이에서 매칭 중 실패한 상황 ❘ 출처: https://pangtrue.tistory.com/305&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csFWTM/btsHqmtr5as/Zw4gQQAkCnKWQ9kko7e6HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsFWTM%2FbtsHqmtr5as%2FZw4gQQAkCnKWQ9kko7e6HK%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;588&quot; height=&quot;301&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;트라이에서 매칭 중 실패한 상황 ❘ 출처: https://pangtrue.tistory.com/305&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 경우 매칭이 실패 했을 경우 가리키는 지점을 이동시키는 fail 함수를 통해 포인터를 접두사와 접미사가 같은 지점인 a (5번 인덱스)로 이동시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간복잡도를 따져보자면, Trie가 만들어져 있는 상황에서,&lt;b&gt; 입력 문자열의 길이가 M이고, 문자열에서 발견된 패턴(금칙어) 발생 횟수가 Z일때, 시간복잡도는 O(M+Z)가 된다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Trie 라이브러리 선택하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그럼 이걸 프로젝트에 적용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니&lt;b&gt;, 자바에서는 Trie에 대한 자료구조를 기본적으로 제공하지 않는다.&lt;/b&gt; (Python 경우 제공한다고 한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 구현할 수도 있겠으나 매우 효율이 낮을 것이다. 그래서 다른 사람들이 잘 만들어 놓은 라이브러리를 가져다 쓰면 된다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 팀원이 프로젝트 진행 당시 사용한 라이브러리는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/npgall/concurrent-trees&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/npgall/concurrent-trees&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715683476464&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - npgall/concurrent-trees: Concurrent Radix and Suffix Trees for Java&quot; data-og-description=&quot;Concurrent Radix and Suffix Trees for Java. Contribute to npgall/concurrent-trees development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/npgall/concurrent-trees&quot; data-og-url=&quot;https://github.com/npgall/concurrent-trees&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/KfknS/hyV2zlAgXk/xK4r1mSSphTRzm6iQjmjpk/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_118_1066_226&quot;&gt;&lt;a href=&quot;https://github.com/npgall/concurrent-trees&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/npgall/concurrent-trees&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/KfknS/hyV2zlAgXk/xK4r1mSSphTRzm6iQjmjpk/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_118_1066_226');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - npgall/concurrent-trees: Concurrent Radix and Suffix Trees for Java&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Concurrent Radix and Suffix Trees for Java. Contribute to npgall/concurrent-trees development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 라이브러리는 &lt;b&gt;Concurrent Radix Tree&lt;/b&gt;와 &lt;b&gt;Concurrent Suffix Tree&lt;/b&gt;를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README와 document를 보니 쓰기작업에 대해서는 lock 걸기에 thread의 blocking이 일어나지만, 읽기작업에 대해서는 lock을 걸지않는다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxfmyR/btsHo8DeMKx/AvVZ2nSkAjxxZtzH8O3RzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxfmyR/btsHo8DeMKx/AvVZ2nSkAjxxZtzH8O3RzK/img.png&quot; data-alt=&quot;쓰기 작업 발생 시 일어나는 일 / 출처 : https://github.com/npgall/concurrent-trees?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxfmyR/btsHo8DeMKx/AvVZ2nSkAjxxZtzH8O3RzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxfmyR%2FbtsHo8DeMKx%2FAvVZ2nSkAjxxZtzH8O3RzK%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;764&quot; height=&quot;352&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;쓰기 작업 발생 시 일어나는 일 / 출처 : https://github.com/npgall/concurrent-trees?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Trie 구조에 대해서 추가할때 구조를 알아서 바꿔주고, 동시성까지 보장해준다니 동적으로 운영중인 트라이에 금칙어를 추가 할 수도 있겠다는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Radix Tree와 Suffix Tree는 대체 뭘까? &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;  &lt;/span&gt;Radix Tree에 대해서&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qYi3U/btsHpyVJfsb/cWwnyx4We1SeKKwGPAYzdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qYi3U/btsHpyVJfsb/cWwnyx4We1SeKKwGPAYzdK/img.png&quot; data-alt=&quot;Trie vs Radix Tree (출처 : https://www.youtube.com/watch?v=E8ZGt2i3xkw)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qYi3U/btsHpyVJfsb/cWwnyx4We1SeKKwGPAYzdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqYi3U%2FbtsHpyVJfsb%2FcWwnyx4We1SeKKwGPAYzdK%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;694&quot; height=&quot;434&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Trie vs Radix Tree (출처 : https://www.youtube.com/watch?v=E8ZGt2i3xkw)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Radix Tree는 위 그림 처럼, 공통 접두사를 사용하는 키들을 하나의 경로로 압축해 저장해 메모리 사용율을 줄인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node의 깊이가 얕아지기에, 검색 경로가 짧아지고 검색 시간이 단축된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 라이브러리에 가보면, Radix Tree도 Radix Tree, Reveresed Radix Tree, Inverted Radix Tree로 나뉘는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 예시와 document를 보고 어느 경우에 어떤 자료구조를 사용하면 좋은지 알 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R22bZ/btsHqzlO4Jj/LhRZvBedwp3KABNFy6kWn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R22bZ/btsHqzlO4Jj/LhRZvBedwp3KABNFy6kWn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R22bZ/btsHqzlO4Jj/LhRZvBedwp3KABNFy6kWn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR22bZ%2FbtsHqzlO4Jj%2FLhRZvBedwp3KABNFy6kWn0%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;716&quot; height=&quot;289&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Radix Tree&lt;/b&gt;는 key와 정확히 일치하는지, 그리고 특정 키로 시작하는 단어를 찾을때 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Reversed Radix Tree&lt;/b&gt;는 반대로 어떤 키로 끝나는지 알고싶을때 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Inverted Radix Tree&lt;/b&gt;는 외부 문서에서 포함된 특정 키워드를 찾고 싶을 때 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 내가 사용하는 입력 받은 문자열에 포함된 금칙어들을 찾으려 한다면, &lt;b&gt;Inverted Radix Tree&lt;/b&gt;를 사용하면 된다. (docs에 잘 예시가 나와있다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;Suffix Tree에 대해서&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dm05at/btsHo7YDYJV/8t7yMC2KCSigziCDFGUQQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dm05at/btsHo7YDYJV/8t7yMC2KCSigziCDFGUQQ1/img.png&quot; data-alt=&quot;출처 : https://github.com/npgall/concurrent-trees/blob/master/documentation/ConcurrentSuffixTreeUsage.md&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dm05at/btsHo7YDYJV/8t7yMC2KCSigziCDFGUQQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdm05at%2FbtsHo7YDYJV%2F8t7yMC2KCSigziCDFGUQQ1%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;332&quot; height=&quot;326&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://github.com/npgall/concurrent-trees/blob/master/documentation/ConcurrentSuffixTreeUsage.md&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Suffix Tree는 위와같이 단어의 모든 접미사를 저장한다. &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;따라서 Radix Tree에 비해 상대적으로 많은 메모리를 사용한다. (단점)&lt;/span&gt;&lt;/b&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;그러면 왜 이렇게 모든 단어의 경우의수(접미사)를 저장할까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이유는 문자열에 대한 검색 시 필요하기 때문인데, 기본 Radix Tree 자료구조상 위 TOAST 문자열에서 TO, TOA로를 TOAST를 찾을 수 있지만, OA, OAS 같은 문자로는 TOAST를 찾을 수 없는데, 이 문제를 Suffix Tree를 사용하면 해결할 수 있다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&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;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;따라서 Suffix Tree는&amp;nbsp; 특정 부분 문자열이 포함된 문자들을 구하는데 사용할 수 있다. (장점)&lt;/span&gt;&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  팀원이 짜 놓은 코드를 분석하고 고쳐보자!&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private final InvertedRadixTree&amp;lt;String&amp;gt; TRIE = new ConcurrentInvertedRadixTree&amp;lt;&amp;gt;(new DefaultCharSequenceNodeFactory());&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 InvertedRadixTree에 금칙어를 애플리케이션 로드시 PostConstruct를 메서드에서 메모리에 업로드 후 &amp;rarr; 문자열 입력시 contain하는지 검증한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 공식문서에 따르면, DefaultCharSequenceNodeFactory는 suffix tree 사용시 메모리 효율을 높혀 줄 수 있다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 우리는 Inverted Radix Tree를 사용하기 때문에 가장 추천되는 SmartArrayBasedNodeFactory로 바꿔주자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1013&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vy9Wp/btsHqqiniuX/gpP9LI1ypH2XCTBmWBV1xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vy9Wp/btsHqqiniuX/gpP9LI1ypH2XCTBmWBV1xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vy9Wp/btsHqqiniuX/gpP9LI1ypH2XCTBmWBV1xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvy9Wp%2FbtsHqqiniuX%2FgpP9LI1ypH2XCTBmWBV1xK%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;1013&quot; height=&quot;368&quot; data-origin-width=&quot;1013&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;외부 라이브러리를 사용하는 경우, 팀 내 규칙으로 학습테스트를 작성하고, 노션으로 해당 라이브러리에 대한 문서화를 진행해 팀원들에게 사용한 의도를 납득시키는 과정이 필요할 것 같다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  이 라이브러리가 최선일까..?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지금 사용한 라이브러리는 Trie 자료구조를 만드는 데 사용한 라이브러리이다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이는 List와 비교하면 시간복잡도를 줄일 수는 있지만, 아호 코라식 알고리즘을 사용하면 더 좋지 않을까?&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2편에서 계속&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://juno-juno.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크 :  &amp;zwj; &lt;/a&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://juno-juno.tistory.com/118&quot;&gt;금&lt;/a&gt;&lt;a href=&quot;https://juno-juno.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;칙어 필터링 기능을 분석하고 리팩토링 해보자! - (2) 실제 성능을 측정해보자!&lt;/a&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;b&gt;Reference&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아호 코라식 알고리즘 :&amp;nbsp;&lt;a href=&quot;https://pangtrue.tistory.com/305&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pangtrue.tistory.com/305&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트라이 자료구조 : &lt;a href=&quot;https://www.youtube.com/watch?v=TohdsR58i3Q&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=TohdsR58i3Q&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Suffix Tree / Radix Tree:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=N70NPX6xgsA&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/watch?v=N70NPX6xgsA&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cd4761.blogspot.com/2017/02/suffix-tree.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cd4761.blogspot.com/2017/02/suffix-tree.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://comdolidol-i.tistory.com/149#%EC%A0%91%EB%AF%B8%EC%82%AC%20%ED%8A%B8%EB%9D%BC%EC%9D%B4(Suffix%20trie)-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://comdolidol-i.tistory.com/149#%EC%A0%91%EB%AF%B8%EC%82%AC%20%ED%8A%B8%EB%9D%BC%EC%9D%B4(Suffix%20trie)-1&lt;/a&gt;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming</category>
      <category>금칙어</category>
      <category>아호코라식</category>
      <category>트라이</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/117</guid>
      <comments>https://juno-juno.tistory.com/117#entry117comment</comments>
      <pubDate>Wed, 15 May 2024 01:12:30 +0900</pubDate>
    </item>
    <item>
      <title>  [프로그래머스 백엔드 데브코스] Space Club 최종 프로젝트 회고</title>
      <link>https://juno-juno.tistory.com/116</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/veiDI/btsHjtZ98lX/MumsTdlE9WjHk4g1tYKZ2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/veiDI/btsHjtZ98lX/MumsTdlE9WjHk4g1tYKZ2K/img.png&quot; data-alt=&quot;리팩토링을 진행할때의 내 심정..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/veiDI/btsHjtZ98lX/MumsTdlE9WjHk4g1tYKZ2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FveiDI%2FbtsHjtZ98lX%2FMumsTdlE9WjHk4g1tYKZ2K%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;344&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;344&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;늦게 진행하는 이유는 조금씩 리팩토링을 진행한게, 이제서야 거의 끝맺음을 지은 것 같기 때문이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1️⃣ 프로젝트는 어떤 프로젝트였는지?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Space-Club/Backend&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Space-Club/Backend&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715219138424&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Space-Club/Backend: Backend Repository For Space Club&quot; data-og-description=&quot;Backend Repository For Space Club. Contribute to Space-Club/Backend development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Space-Club/Backend&quot; data-og-url=&quot;https://github.com/Space-Club/Backend&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4ePPy/hyVZkhVlCl/7BU4shWxTAXyNDwf5ZH3EK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Space-Club/Backend&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Space-Club/Backend&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4ePPy/hyVZkhVlCl/7BU4shWxTAXyNDwf5ZH3EK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Space-Club/Backend: Backend Repository For Space Club&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Backend Repository For Space Club. Contribute to Space-Club/Backend development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누구나 동아리를 쉽게 만들고, 관리하며, 행사를 주최할 수 있는 서비스를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://spaceclub.vercel.app/login&quot;&gt;https://spaceclub.vercel.app&lt;/a&gt;(지금도 사용가능할 것이다.. 아마?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사이트에서 사용할 수 있는데, 기획의도는 괜찮다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 대학생들이 동아리 주점을 개최하더라도, 홍보는 에브리타임을 통해서 진행하고, 폼 작성은 구글 폼을 사용하는데, 사람이 많이 몰리는 선착순 기능을 구글폼은 제공하지 않는것으로 알고 있기 때문에, 괜찮은 가치가 있는 프로젝트라고 생각했다.&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;b&gt;2️⃣ 어떻게 진행했는가?  &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 이렇게 제대로된 프로젝트 경험을 하는게 처음이라, 되돌아보면 정말 힘들기도 했지만, 재밌기도 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n0psg/btsHg6d7bYg/zmlkWkBeVSoHwXTgyK1j50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n0psg/btsHg6d7bYg/zmlkWkBeVSoHwXTgyK1j50/img.png&quot; data-alt=&quot;백엔드 팀규칙&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n0psg/btsHg6d7bYg/zmlkWkBeVSoHwXTgyK1j50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn0psg%2FbtsHg6d7bYg%2FzmlkWkBeVSoHwXTgyK1j50%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;694&quot; height=&quot;289&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;618&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;1568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IJnLo/btsHidRjrO8/9rJn1kYeG65WmuXIdVXOH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IJnLo/btsHidRjrO8/9rJn1kYeG65WmuXIdVXOH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IJnLo/btsHidRjrO8/9rJn1kYeG65WmuXIdVXOH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIJnLo%2FbtsHidRjrO8%2F9rJn1kYeG65WmuXIdVXOH1%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;707&quot; height=&quot;647&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;1568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;총 11차례의 스프린트&lt;/b&gt;를 거치며 회고를 진행하기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프린트 기간은 짧게 잡아 최대한 빠르고 타이트하게 개발 진행을 할 수 있도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짧게 스프린트를 10차례 이상 가져가니, 프로젝트 기간동안 핫식스와 같은 에너지드링크를 달고 살았고, 나중에 프로젝트가 끝날 시점에는 진짜 내가 좀비가 된 것 같았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그 결과 60개 가까운 api를 뽑아냈고, qa를 진행하면서 다른팀보다 완성도 있는 결과물을 낸 것 같기도 하다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(API docs: &lt;b&gt;&lt;a href=&quot;https://space-club.site/docs/index.html&quot;&gt;https://space-club.site/docs/index.html&lt;/a&gt;&lt;/b&gt;)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beDQps/btsHjUpHpSC/AFNF1SidajTqEtlKZMvEZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beDQps/btsHjUpHpSC/AFNF1SidajTqEtlKZMvEZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beDQps/btsHjUpHpSC/AFNF1SidajTqEtlKZMvEZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeDQps%2FbtsHjUpHpSC%2FAFNF1SidajTqEtlKZMvEZ1%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;501&quot; height=&quot;145&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1644&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJvTH4/btsHiqiImrl/8c8Ct5C6W1JkObxd1PQp0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJvTH4/btsHiqiImrl/8c8Ct5C6W1JkObxd1PQp0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJvTH4/btsHiqiImrl/8c8Ct5C6W1JkObxd1PQp0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJvTH4%2FbtsHiqiImrl%2F8c8Ct5C6W1JkObxd1PQp0K%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;499&quot; height=&quot;226&quot; data-origin-width=&quot;1644&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VAARE/btsHjcqJiGs/SYLn6rjuMYi8obffW1F4JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VAARE/btsHjcqJiGs/SYLn6rjuMYi8obffW1F4JK/img.png&quot; data-alt=&quot;기술공유 문서의 일부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VAARE/btsHjcqJiGs/SYLn6rjuMYi8obffW1F4JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVAARE%2FbtsHjcqJiGs%2FSYLn6rjuMYi8obffW1F4JK%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;509&quot; height=&quot;481&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1218&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;b&gt;기획서부터 개인 일정까지 모든 내용들을 투명하게 공유&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 기술 공유를 통해 새로운 기술을 학습하고 프로젝트에 적용하려고 한다면, 팀원들이 이해할 수 있게 공유하기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서화가 생명이라고 생각했고, 일주일에서 두번정도 만나는 요일을 제외한 모든 날들은 각자의 주둔지(?)에서 개발을 진행했기 때문에 슬렉과 지라를 통한 비동기적인 소통의 중요성을 알고, 적응하려고 노력했던 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Uiy0/btsHhRAYVbb/yFLLp8Wwq16GZi8ggN5nhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Uiy0/btsHhRAYVbb/yFLLp8Wwq16GZi8ggN5nhK/img.png&quot; data-alt=&quot;이 시간은 와글와글이라 했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Uiy0/btsHhRAYVbb/yFLLp8Wwq16GZi8ggN5nhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Uiy0%2FbtsHhRAYVbb%2FyFLLp8Wwq16GZi8ggN5nhK%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;676&quot; height=&quot;491&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1140&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3️⃣ 협업을 통해 배운 것&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트와 협업하는 부분에서도 많은 것을 배웠는데, 떠오르는 것을 써 보자면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. API는 도메인 담당자들 끼리 지라로 소통하자. 그리고 최대한 진행사항을 투명하게 공유해 협업에 차질이 나지 않게 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 소프트스킬의 중요성.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 하나의 컨트롤러당 필요한 메서드는 4~5개 정도밖에 안된다. (create, get, getAll, update, delete). 기능 구현 시 백엔드 api 여러개를 조합하는 방식으로 프론트에서 기능구현할 수 있도록 타협점을 찾고 설득하자. 프론트에 모든 것을 맞춰준 개발은 컨트롤러를 괴물로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적인 인격적인 부족함에 대해서도 스스로 많이 느끼고, 자책하기도 했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 내가 모든 것을 알고 팀을 리드할 수 없다는 것에대한 괴로움이 컸다. 내가 이런 경험이 많았다면 좀 더 착착 진행할 수 있었을 텐데.. 하는 아쉬움 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도 이런 제대로된 협업 경험을 하는 프로젝트가 처음이였고, 다른 팀원들도 마찬가지였을 것이다. 그래서 좀 아쉬움이 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적인 것에 대해서 아쉬움도 있지만, 내가 좀더 팀원들과 소통을 잘 할걸, 더 좋게 대할걸 하는 아쉬움 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 예비군이 있어 4일정도를 개발하지 못한 기간이 있었는데, 그때 스스로 예비군 다녀오니 힘들기도 해서 코드리뷰도 제대로 안하고, 개발도 거의 못했었는데, 그 기간동안 좀 더 힘을 내서 책임감있게 할 걸이라는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 내가 너무 지쳐서 이걸 사람들이 사용하지 않을 것 같은데 무슨 의미가 있는지 잘 모르겠다고 솔직하게 팀원들에게 이야기한 적이있는데, 결과적으로 봤을 때 팀원들의 사기또한 떨어뜨리는 행동이였던 것 같고, 되돌아 보면 좀 책임감 없는 언행이였던 것 같아 후회가 되기도 한다. 하지만 팀원들에 이끌려 어떻게든 프로젝트를 끝냈으니 돌아보면 팀원들에게 고마울 따름이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4️⃣ 프로젝트의 결과..&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리 팀 프로젝트는 어떻게 되었냐고?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SxBfX/btsHg5TPHW2/VUt7ud2PmJqU7y6fO4RagK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SxBfX/btsHg5TPHW2/VUt7ud2PmJqU7y6fO4RagK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SxBfX/btsHg5TPHW2/VUt7ud2PmJqU7y6fO4RagK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSxBfX%2FbtsHg5TPHW2%2FVUt7ud2PmJqU7y6fO4RagK%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;522&quot; height=&quot;403&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;956&quot;/&gt;&lt;/span&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;5차례의 QA를 대면으로 진행했고, 버그없는 서비스를 만드는데 집중했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 코드 퀄리티 보다 각자 맡은 도메인에서 맡은 기능 구현에 집중했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과, 발표 시 다른 데브코스 팀원들에게 우리 프로젝트를 소개하고 사용하게했다. &lt;b&gt;40명이 넘는 유저가 가입해서 사용했고, 피드백을 받았다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3124&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tmaX8/btsHjukuQ0l/Q8STebEdNYR5xo6kNDfr61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tmaX8/btsHjukuQ0l/Q8STebEdNYR5xo6kNDfr61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tmaX8/btsHjukuQ0l/Q8STebEdNYR5xo6kNDfr61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtmaX8%2FbtsHjukuQ0l%2FQ8STebEdNYR5xo6kNDfr61%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;3124&quot; height=&quot;894&quot; data-origin-width=&quot;3124&quot; data-origin-height=&quot;894&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;12월 데브코스가 끝나고, 한 2주동안은 진짜 쉬어도 쉬는 것 같지 않고, 불안했다. 기억도 잘 안난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무너진 수면패턴으로 불면증도 와서 아침 해가 뜰때까지 잠이오지 않았던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README도 작성하지 않고, 그냥 내팽겨치고 그렇게 우리 프로젝트는 끝이 났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5️⃣ 추후 ...&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정신을 차려보니 1월, 나는 이 프로젝트를 이렇게 마무리 짓고 싶지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 고도화도 충분히 못했고, 하고싶은 것들을 아직 못했다 생각했기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라를 내가 맡지 않아 인프라에 대한 이해도 부족했고, 전체 프로세스를 이해하는 것이 아닌, 내가 맡지 않은 부분에 대해서는 이해가 부족했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 프론트 팀원 한 분과 덧붙히고 싶은 게시판 기능을 추가적으로 만들고, 나는 개인적으로 고도화 작업과 리팩토링 작업을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 환경을 구축하고, 프로젝트 전체 도메인에 대한 이해를 하고자 노력했고, 어느정도 하고싶은 것들은 괴로움을 꾹꾹 눌러담으며 진행했고, 완료했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6️⃣ 리팩토링을 하면서 느낀 점 / 아쉬운 점&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. DB - CS의 기본기의 중요성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 대해서 팀원들이 익숙하지 못했던 것 같아 테이블 설계 과정에서 아쉬움이 많이 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 시작 전 or 적어도 첫 번째 스프린트 끝날 때에는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;DB 테이블 정규화를 통해 테이블 분리가 이루어졌어야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그리고 그것들을 팀원들이 전제 공유를 하고 중복되는 컬럼이 없는지, null 값이 안들어가게끔 테이블 설계를 할 수 없는지, 정규화 대신 비정규화를 하는 방식이 왜 더 나은지 등 충분한 논의가 이루어졌어야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 책임감&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트는 기간안에 끝내는 것이 가장 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘토님들이 항상 말 했던 것이고, 스프린트를 성공적으로 끝내는것에 포커스를 맞추어 마감을 맞추는 것이 가장 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 끝나고, 나는 개발자는 자기 프로덕트에 대해서 끝까지 완성도를 책임지는 사람이 되고 싶었다. 이대로 내팽겨치기에는 너무 아쉬운 부분이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 끝났다 해서 미완의 상태로 프로젝트를 남긴다거나, 제대로 동작하지 않는 부분이나 문제가 있음에도 마감이 끝났고 기한을 못맞췄다고 그냥 내팽겨치는 것은 내 성격상 받아들이기 힘들엇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 내 코드에 자아를 너무 투영시켜서도 안되겠지만, 특정 퀄리티까지 반드시 완수는 시키는 사람이 되고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 비전공자라고 기죽지 말기!&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 컴공을 졸업한 사람들과 협업하는, 제대로된 첫 프로젝트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 팀원(프론트 + 백) 중 유일한 비전공자였고, 컴공 졸업생들과 함께 하는 프로젝트에서 주변들로 부터 배울 것이 많을것이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사실 내 의견을 필사적으로 피력하기 보다는, 다른 사람들의 개발 스타일이나 방식을 존중하면서 크게 터치를 하지 않고 개발 했던 것 같다. 사실 누군가 나를 좀 이끌어 주길 바랬던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 냉정하게 프로젝트가 끝난 시점에서 돌아본다면, 내가 수동적이면 안되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닌건 아니라고 말할 수 있어야 했고, 더 나은 방식이 있다면 설득하기를 피해서는 안되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 주변의 사람들이 뛰어나 내가 배울점이 많고 따라가기만 한다면 더없이 좋겠지만, 디폴트는 의존할 사람이 전혀 없는것이여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 전공자 중에서도 뛰어나고 진짜 레벨이 다르고 배울 점이 많은 사람들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 나도 생명과학을 전공했지만 생명과학에 대해서 잘 모르듯 너무 큰 전공자에 대한 환상은 접어두는게 필요한 것 같다. 대부분의 사람들에게는.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 모든것은 하고 나면 별거 아니다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI CD, 인프라 부분은 클론프로젝트 때도, 최종프로젝트때도 모두 내가 맡지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DevOps 쪽에 관심있는 팀원과 이미 프로젝트를 통해 CI/CD 경험있는 팀원이 있어 최종프로젝트까지 모든 인프라 부분을 책임졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링을 진행하면서 기존 인프라 구조를 이해하고, 개편하는 작업을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 인프라 쪽이라 하면 엄청 복잡해 보이고, 나는 범접하기 힘들어 보이지만, 막상 하고나면 별것 아니라는 것을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발이라는게 엄청난 머리를 가지고 있어야 하는 것은 아닌것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것 보다 개발하는데 중요한 덕목이라고 한다면, 끝까지 문제를 포기하지 않고 물고 늘어져 해결하는 그 끈기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 어디서 발생했는지 찾고 끝내 해결해 내고야 마는 인내심과 참을성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뛰어난 두뇌보다 그러한 가치들이 더 중요한 것이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 참을성과 끈질김으로 계속 밀어 붙이자. 그리고 좀 자신감을 가지자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 심리적 안정감의 중요성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심리적인 안정감의 중요성을 요즘 느끼고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 나는 불안감과 부정적인 생각에 대해서 별 생각이 없었다. 너무 긍정적인 것을 오히려 부정적으로 봤었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 최근 느낀 것이 불안감과 부정적인 생각은 삶에 독소 같은 것이라는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심리적인 안정감을 깨고 마음이 불안한 상태에서는 좋은 성과가 나올 수 없다는 것이 내 결론이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행할 때, 나는 말 그대로 혼돈의 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 달 동안 진행되었지만, 팀원들과 협업하면서 몰아닥치는 지라 티켓을 쳐내기에 바빴던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때는 몰랐는데, 리팩토링을 하면서 느낀 점이 아... 이런 더 좋은 방법이 있는데 하는 것들이 너무 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 프로젝트를 하게 된다면, 좀 마음의 안정을 취하고 넓은 시각에서 바라보면서 기능 구현에만 너무 매몰되지 않아야 겠다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 정리하자면, 프로젝트가 첫 프로젝트라 미숙했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 스스로 돌아보자면, 말을 곱게 해야 겠다는 생각이 들고, 실수를 하더라도 비난하고 공격하는게 아니라 같이 협업하고 편안한 환경을 만드는 능력을 키워야 겠다는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서는 개발 실력, 팀을 리딩할 수 있을 만큼의 개발 실력이 우선되어야 할 것이고, 팀원간 마찰이 생길 경우 팀원을 비난하고 공격할 것이 아니라 나랑 안맞는 팀원이라도 같이 조율 해 나가는 것이 필요할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 팀에 안정적인 분위기를 형성하진 못해도, 적어도 나 때문에 팀 내의 안정적인 분위기가 깨져서는 안되는 것을 목표로 해야겠다.&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;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/shorts/C0L02X18Ubs&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/CERqW/hyVVJ1XusI/OtroeWcSBDslWhDRKkr1bk/img.jpg?width=480&amp;amp;height=360&amp;amp;face=201_103_343_258,https://scrap.kakaocdn.net/dn/60aIR/hyVVxNYJR0/WTDMtPs6QDtO38Rw4qOUv0/img.jpg?width=480&amp;amp;height=360&amp;amp;face=201_103_343_258&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/C0L02X18Ubs&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 고생했다! 내 스스로에게도, 다른 팀원들에게도.&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming</category>
      <category>프로젝트 회고</category>
      <category>회고</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/116</guid>
      <comments>https://juno-juno.tistory.com/116#entry116comment</comments>
      <pubDate>Thu, 9 May 2024 11:57:06 +0900</pubDate>
    </item>
    <item>
      <title>왜? 라는 질문을 하는 개발자가 되자. 아니, 왜? 라고 질문하는 사람이 되자.</title>
      <link>https://juno-juno.tistory.com/115</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;취준을 하면서 느낀 것이 하나 있는데, 부정적인 감정을 컨트롤하는게 정말 중요함을 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부정적인 감정은 학습 효율을 떨어뜨리고, 그러한 부정적인 감정이 객관적이고 이성적인 판단을 흐린다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 같이 취준하는 동료들을 보면서 느낀 점이 하나 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남들 하는 것은 무조건 다 하려는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남들 다 하는걸 안하면 괜히 불안하다. 그러니 갈피를 못잡고 방황을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 왜?라는 질문을 해야하는 것에 대해서 스스로 생각하고 올바른 답을 내릴 줄 아는 것이 필요함을 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜? 라는 질문을 하는 것이 이 막연한 불안감과 불확실함으로부터 나를 휘둘리지 않게 만들어 줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜? 라는 질문을 왜 하느냐는 이 때문이다. 답을 찾지 않으면, 안개속을 계속 헤매게 되는 것이기 때문에 왜라는 질문을 하고 답을 구하고, 찾아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(나는 이것을 최근 멘토님으로 부터 배우고 느꼈다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안그러면, 주체적으로 살 수 없고 그냥 사회 분위기에 휩쓸려 이리갔다 저리갔다하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누군가 저게 좋다하면 흔들릴 것이고, 누군가 나의 가치관을 공격하면 또 흔들릴 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무 생각 없이, 그냥 깊은 바다 속의 청어 떼 중 한마리 처럼, 움직이고 살게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 취업을 하려고하는가?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 개발자가 되려고 하는가? 왜 수많은 직업 중 백엔드 개발자가 되려고 하는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돈을 벌기 위해서? 사회적 지위나 평판을 위해서?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떠한 회사에 가고 싶은지, 어떤 커리어를 쌓고 싶은지? 왜 그 회사에 가고 싶은지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 어떤 회사는 가기 싫은지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러한 회사에 가기 위해서는 어떤 것들이 필요하지? 그리고 어떤 것들이 불필요하지? 그리고 그렇게 결론을 내린 근거는 무엇인지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사는 왜 사람을 채용하는지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사는 왜 신입을 채용하는지? 경력자대신 왜 신입인 나를 채용해야 하는지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사는 비전공자인 나를 왜 뽑아야 하는 것인지? 회사 입장에서 왜 나를 좋은 대학나온 전공자들보다 뽑아야 하는지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 답변들에대해 명확하게 답을 할 수 없다면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 1. 남들이 다 취업하기 때문에 '그냥' 취업을 하려고 하는 것은 아닌지, 2. 이유없이 남과의 비교를 통해 불평만 하고 있는 것은 아닌지, 3. 현실을 잘 못보지 못하고 부족함의 원인을 엉뚱한 곳에서 찾고 있는 것은 아닌지 를 면밀히 검토해 봐야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 마지막으로 내가 생각하기에 가장 조심해야 할 부분이, 남들의 피드백, 의견, 가치관을 무조건 수용하는 것이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유명한 석학이 쓴 책을 읽고 나면, 마치 그 사람의 지혜를 내가 모두 흡수한 것 같은 '착각'에 빠진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 사람의 가치관이 다른 가치관에 비해서 옳은 가치관이라 생각하고, 그러한 가치관을 스스로에게 이유없이 주입시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 그냥 책한권 읽은 것 뿐인데, 그 사람이 수년, 수십년에 걸쳐 깨달은 것을 마치 내가 깨달은 것 마냥 '착각'하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것이 바로 이데올로기, 이념에 빠지는 것이라고 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이념에 빠지지 말자. 권위때문에 '왜'라는 질문을 걷어내지 말자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>생각들</category>
      <category>why</category>
      <category>왜</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/115</guid>
      <comments>https://juno-juno.tistory.com/115#entry115comment</comments>
      <pubDate>Fri, 3 May 2024 18:07:02 +0900</pubDate>
    </item>
    <item>
      <title>일에 대해서</title>
      <link>https://juno-juno.tistory.com/114</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;내가 군대에 있을 때, 그 시기는 나에게 정말 소중한 시기였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝없는 방황을 하던 내가, 주체적으로 스스로 생각하고, 사색하는 소중한시간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 관심있는 분야의 책들을 읽으면서 어떻게 살아갈 것이고 어떠한 가치관으로 살 것인지에 대해서 많이 생각했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주변 사람들을 관찰하고, 사람들과 대화를 나누면서 사람들의 가치관이 다양하다는 것을 느꼈고, 주체적인 한 개인으로 전역 후 삶을 살아가기 위해서 내가 어떠한 가치들을 중요시 하는지 스스로에게 묻는 시간도 가졌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 한 가지 질문, 나는 어떤 사람을 존경하는가?에 대해 생각해 보았다.&lt;/p&gt;
&lt;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;그리고 한 분은 나와 같이 일하고 있는 운영과장님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 군대에서 행정병으로 근무하면서 부대에 대부분의 사람들이 어떠한 일이 주어졌을 때, 일이라는 것은 피하고, 하기 싫은 것이라는 인식이 만연해있다는 것을 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들은 일을 적게 할 수록, 힘든 일을 피하면 피할 수록 승리자가 되는 게임을 하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 내가 이때까지 살면서 존경했던 분들을 보면, 그 게임과는 완전히 반대의 마인드로 살아가고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 당시 같이 일하던 운영과장님은 카리스마 넘치는 여군이셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같이 보안감사때 일을 하면서 봤던 꼼꼼함과, 상급자의 감사에도 절대 기죽지 않는 모습이 멋있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병사들에게 최대한 일을 적게 시키려고 하셨고, 많은 일들을 자신이 떠 맡길 원하셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 분은 내가 전역할 때 쯤, 그러한 성과를 주변에서도 인정을 받았는지 1차만에 대위에서 소령으로 진급을 하셨고 다른 부대로 전출가셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로, 고등학교때 수학선생님은 학생들을 위하는 교사셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부 잘하는 사람들만 특별하게 돌보는 것이 아닌, 반에서 소외되는 사람 없이 모두가 수업에 참여하길 원하셨던것이 보이는 거의 유일한 선생님이였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 나온 고등학교는 사립고등학교였고, 나를 가르친 선생님이 큰엄마의 선생님일 정도로 한 학교에서 몇십년을 근무한 선생님들이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그 분들은 그 학생이 그 학생이니 소홀해지고 다른 대부분의 선생님들처럼 크게 에너지를 쏟지 않는 것이 보였지만 그분은 다르셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 정말 중요한 것 중 하나가, 그 수학선생님은 정말 수학을 좋아하시는것이 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기계적으로 수학을 푸는게 아니라 생각을 해서 수학문제를 풀 수 있는 방법을 가르쳐 주셨다. (학교에 있는게 아까울 정도로 웬만한 유명 인강강사 수업보다 더 얻을게 많았고, 그래서인지 그 선생님이 보충 수업을 개설하면 제일 먼저 마감되었다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 내가 존경하고 멋있어 보였던 분들에게서 뽑아낸 가치는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;진정으로 일을 사랑하고 좋아하는 사람은 언젠가 눈에 띌 수 밖에 없다.&lt;/li&gt;
&lt;li&gt;더 많은 책임을 지려고 하며 권위에 굴복하지 않는 사람은 존경과 존중을 받을 수 밖에 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 나는 군대를 전역하고 사회적 지위보다 내가 좋아하는 일을 찾기 위해 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 일을 할때 내가 진정으로 즐거운지를 많이 고민했었다. 내가 무엇을 하고 살면 내 삶과 에너지를 바칠만한 가치가 있을지 궁금했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 찾은게 프로그래밍이다. 복잡한 문제를 해결하는 사람.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 지금 까지 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적의 길을 찾기 위해 노력했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떠한 문제를 더 효율적으로 해결하기 위해서, 그리고 내가 만들고 싶은 것을 만들기 위해서 이 일 해야겠다고 마음을 먹었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 최근들어 돈 많이 받는 회사, 사회적으로 인식이 좋은 회사에 가는 주변 사람들을 보면서 가치관이 많이 흔들리고 그냥 돈많이 받고 편하게 살고 싶은 마음이 든다. (물론 돈도 많이 주고, 인식도 좋고, 배울 점도 많은 회사도 있지만, 그만큼 가기 힘들어 보인다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일이란, 피해야하고 하기싫은데 억지로 하는게 아니라, 신성한 것이라 생각한다.&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;27살, 많으면 많지만 아직은 시간이 있다고 느끼기에, 배움과 더 나은 개발자가 되는 것에 초점을 맞추자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;
&lt;div class=&quot;css-1snyc68&quot;&gt;
&lt;div class=&quot;css-7gfopr&quot;&gt;
&lt;div class=&quot;css-1kx128h&quot;&gt;
&lt;div class=&quot;css-pix79b&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>생각들</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/114</guid>
      <comments>https://juno-juno.tistory.com/114#entry114comment</comments>
      <pubDate>Tue, 23 Apr 2024 15:24:24 +0900</pubDate>
    </item>
    <item>
      <title> &amp;zwj;  인프라 개선기 - HTTPS 설정 시 요금 개선 &amp;amp; 무중단 배포</title>
      <link>https://juno-juno.tistory.com/113</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Overview&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 space club 프로젝트를 리팩토링 하며 하나하나 뜯어 고치는 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트때 나는 인프라 부분을 맡지 않았다. 팀원 중 한명이 인프라에 관심을 가지고 있던 친구여서 모든 백엔드 환경을 잘 구성해 줬었기에 스프링 애플리케이션 개발에만 집중할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 리팩토링을 진행하면서 좀 더 나은 방식을 적용할 수 있겠다는 생각이 들었고 이 모든 것을 바로잡고 블로그 글을 쓰기 까지 총 일주일이 넘는 시간이 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이번에 프로젝트 리팩토링을 진행하면서 겪은&lt;b&gt; 문제점&lt;/b&gt;은 두 가지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣ HTTPS 설정 시, EC2에 Application Load Balancer를 통한 과금 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2️⃣ CI/CD 중 배포 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&amp;nbsp; 2-1. CI/CD 중 불필요한 파일이 S3에 업로드 되는 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&amp;nbsp; 2-2.  배포 시 백엔드 서버가 중단되는 문제&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;b&gt;1️⃣ HTTPS 설정 시, EC2에 Application Load Balancer를 통한 과금 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[문제점]&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfS1r1/btsGIOyKztZ/IE1wzBjvWAUxbCNSzRKudK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfS1r1/btsGIOyKztZ/IE1wzBjvWAUxbCNSzRKudK/img.png&quot; data-alt=&quot;출처 : aws 공식 사이트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfS1r1/btsGIOyKztZ/IE1wzBjvWAUxbCNSzRKudK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfS1r1%2FbtsGIOyKztZ%2FIE1wzBjvWAUxbCNSzRKudK%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;717&quot; height=&quot;401&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : aws 공식 사이트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스에서 지원해 주던 서버 비용이, 프로그래머스 데브코스가 종료됨에 따라 데브코스 aws 계정에 접근이 더 이상 불가하게 되면서 &lt;b&gt;개인 서버로 이전 작업을 진행&lt;/b&gt;해야 했다. (이전 작업은 여러 다른 블로그에 잘 나와 있어 EC2와 RDS, S3를 모두 스냅샷을 통해 내 계정으로 옮겼다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때는 사용하고 싶은 기술들을 마음껏 사용할 수 있었지만, 내 개인 서버로 옮기면 내 돈 아닌가!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(가난한 취준생인..)나는 과금 걱정이 들었다.   지난 인프라 학습시 dynamoDB를 계속 켜두니 한달에 8만원 가까운 폭탄을 맞은 적이 있어 더 그랬던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 HTTPS 설정 시 ACM과 Route53, ALB을 통한 HTTPS 설정을 마치고보니, &lt;b&gt;ALB를 사용하면 시간당 0.0225달러가 지출&lt;/b&gt;되는 것을 확인했다. (이는 30일을 계쏙 켜 놓을경우 16달러에 해당된다).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 HTTPS를 설정하는 다른 방식이 없을까 해서 방식을 찾아 보다, 웹서버인 Nginx를 리버스 프록시로 사용해 로드밸런서 기능을 할 수 있게 함으로서 ALB를 대체할 수 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[해결]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 한 고민은, HTTPS 설정 시, SSL 인증서를 제공해주는 CA를 ACM(Amazon Certificate Manager)에서 발급받아 nginx에서 사용할 수는 없는가였다.&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;대신, Nginx는 &lt;span style=&quot;background-color: #ffffff; color: #454545; text-align: start;&quot;&gt;Let&amp;rsquo;s Encrypt이라는 자동화된 무료 개방형 인증 기관(CA)에서 S SL/TLS 인증서를 무료로 제공 받을 수 있다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;certbot을 통해서 Nginx에서 Let's Encrypt CA의 인증서를 발급 받을 수 있었다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #454545; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;[결과]&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #454545; text-align: start;&quot;&gt;그렇게 기존 ACM과 ALB를 통한 HTTPS 설정에서 Nginx와 certbot을 통해서 HTTPS 설정 방식을 변경함으로서 ELB에 들어가는 요금을 줄일 수 있었다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #454545; text-align: start;&quot;&gt;물론, 트래픽이 몰려 nginx가 버티지 못하거나, 로드밸런싱 시 오토스케일링 기능이 필요하다면 ELB를 선택하는것이 합리적이겠지만, space club 프로젝트 상으로는 nginx를 사용하는 방식이 더 합리적이라고 판단하였다.&lt;/span&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;b&gt;2️⃣ CI/CD 중 배포 문제 - 서버가 중단되는 문제&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. CI/CD 중 불필요한 파일이 S3에 업로드 되는 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[문제점]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 space club의 전체적인 파이프라인은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmFeM3/btsGLuysWO3/n4ea2FTomyZRv8bO3fsfq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmFeM3/btsGLuysWO3/n4ea2FTomyZRv8bO3fsfq1/img.png&quot; data-alt=&quot;CI/CD 파이프라인 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmFeM3/btsGLuysWO3/n4ea2FTomyZRv8bO3fsfq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmFeM3%2FbtsGLuysWO3%2Fn4ea2FTomyZRv8bO3fsfq1%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;540&quot; height=&quot;255&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CI/CD 파이프라인 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;github action을 통해 CI 과정 및 Continuous Delivery 과정을 진행한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로젝트 전체 파일을 빌드 후 jar 파일로 압축한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AWS IAM으로 AWS 권한 인증을 완료하고&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;S3에 jar 파일을 올린다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;S3의 jar 파일을 code deploy를 통해 배포 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방식에서, jar 파일을 압축할 때 기존에서는 전체 프로젝트를 압축했었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SXgYQ/btsGKepKywP/m2t79rHnbP10KPINXIru10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SXgYQ/btsGKepKywP/m2t79rHnbP10KPINXIru10/img.png&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;128&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.6252%; margin-right: 10px;&quot; data-widthpercent=&quot;50.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SXgYQ/btsGKepKywP/m2t79rHnbP10KPINXIru10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSXgYQ%2FbtsGKepKywP%2Fm2t79rHnbP10KPINXIru10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;614&quot; height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd41Ve/btsGJoGqzLU/JuUoCYF3rCamyKWIlu3dp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd41Ve/btsGJoGqzLU/JuUoCYF3rCamyKWIlu3dp1/img.png&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;288&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.2121%;&quot; data-widthpercent=&quot;49.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd41Ve/btsGJoGqzLU/JuUoCYF3rCamyKWIlu3dp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd41Ve%2FbtsGJoGqzLU%2FJuUoCYF3rCamyKWIlu3dp1%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;1370&quot; height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;기존 workflow - 변경된 workflow&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[해결]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 스프링 애플리케이션을 실행하는데, 테스트 파일이나 프로덕션 코드까지 압축할 필요는 없다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하는데는 jar파일과 code deploy 실행시 필요한 appspec.yml, code deploy가 실행하는 script들과 secret 폴더에 있는 비속어 목록들 밖에 없었다. (애플리케이션 로드 시 필요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[결과]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 필요한 파일만 압축하니, &lt;b&gt;3MB정도의 공간이 줄었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 큰 차이는 아니지만, &lt;b&gt;불필요한 S3 용량과 EC2의 디스크 공간을 줄일 수 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SqN91/btsGK3abH2J/emkKKjuymgPv4Jh04fLNEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SqN91/btsGK3abH2J/emkKKjuymgPv4Jh04fLNEk/img.png&quot; data-alt=&quot;code deploy시 사용하는 s3 bucket&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SqN91/btsGK3abH2J/emkKKjuymgPv4Jh04fLNEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSqN91%2FbtsGK3abH2J%2FemkKKjuymgPv4Jh04fLNEk%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;563&quot; height=&quot;165&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;code deploy시 사용하는 s3 bucket&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp; 2-2.  배포 시 백엔드 서버가 중단되는 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[문제점]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 CI/CD 파이프라인 구조를 보면 한 가지 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식대로 배포를 한다면, 배포 시 code deploy에서 기존 애플리케이션이 돌아가고 있는 프로세스를 kill하고 다시 스프링 애플리케이션을 띄우는 동안, &lt;b&gt;약 20초에서 30초 정도의 시간 동안 백엔드 서버가 아예 꺼지게 된다.&lt;/b&gt;&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;&lt;b&gt;[기존 배포 스크립트]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713445869756&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;if [ -z $CURRENT_PID ] # 기존 애플리케이션 있다면 종료
then
  echo &quot;&amp;gt; 종료할 애플리케이션이 없습니다&quot;
else
  echo &quot;&amp;gt; 실행 중인 애플리케이션 종료 $CURRENT_PID&quot;
  kill -15 $CURRENT_PID
  sleep 5
fi

echo &quot;&amp;gt; 배포 - $JAR_PATH&quot;

# 새로운 애플리케이션 배포 시작
chmod +x $JAR_PATH
sudo nohup java -jar $JAR_PATH --spring.profiles.active=develop --jasypt.encryptor.password=${encrypt} &amp;gt; /home/ubuntu/log/nohup_log.out 2&amp;gt; /home/ubuntu/log/nohup_error.out &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면, 프론트에서 애플리케이션 개발에 있어서 차질이 생길 수 밖에 없다.(실제로 이를 프론트에서 불편한 내색을 보인 경우도 있었다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 나는 무중단 배포 방식으로 인프라 구조 변경을 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무중단 배포에도 여러 방식이 있지만, 하나의 EC2에서 가장 효율적으로 어떻게 하면 down time을 최소화 할 수 있을지를 고민하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 EC2를 띄우기에는 비용문제가 발생했고, docker를 통한 무중단 배포를 진행하기에는 학습 비용도 발생할 뿐 아니라, 크게 장점을 현 프로젝트에서는 찾기 힘들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[해결]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 내가 한 방식은 profile을 통한 blue green 무중단 배포이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.blue, green 프로파일을 추가한다. 그리고 blue는 8081 포트로, green은 8082 포트로 설정한다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PYmiJ/btsGJlplpG3/YlnzqDsCDU6KceunKZ7x9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PYmiJ/btsGJlplpG3/YlnzqDsCDU6KceunKZ7x9K/img.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;130&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;41.84&quot; style=&quot;width: 40.868%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PYmiJ/btsGJlplpG3/YlnzqDsCDU6KceunKZ7x9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPYmiJ%2FbtsGJlplpG3%2FYlnzqDsCDU6KceunKZ7x9K%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;130&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biTydB/btsGK2h0fH3/Y64KckV4nya8esgHtrVnl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biTydB/btsGK2h0fH3/Y64KckV4nya8esgHtrVnl1/img.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;198&quot; data-is-animation=&quot;false&quot; style=&quot;width: 28.4032%; margin-right: 10px;&quot; data-widthpercent=&quot;29.08&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biTydB/btsGK2h0fH3/Y64KckV4nya8esgHtrVnl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiTydB%2FbtsGK2h0fH3%2FY64KckV4nya8esgHtrVnl1%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;434&quot; height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qWxtw/btsGLCXqRva/ZltRiFjFkSODh6Jj9yFnj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qWxtw/btsGLCXqRva/ZltRiFjFkSODh6Jj9yFnj1/img.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;198&quot; data-is-animation=&quot;false&quot; style=&quot;width: 28.4032%;&quot; data-widthpercent=&quot;29.08&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qWxtw/btsGLCXqRva/ZltRiFjFkSODh6Jj9yFnj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqWxtw%2FbtsGLCXqRva%2FZltRiFjFkSODh6Jj9yFnj1%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;434&quot; height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. health check를 위한 현재 profil을 확인 할 수 있는 api를 하나 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api&quot;)
public class ProfileController {

    private static final List&amp;lt;String&amp;gt; BLUE_GREEN = List.of(&quot;blue&quot;, &quot;green&quot;);

    private final Environment env;

    @GetMapping(&quot;/profile&quot;)
    public String profile() {
        return Arrays.stream(env.getActiveProfiles())
                .filter(BLUE_GREEN::contains)
                .findAny()
                .orElseGet(() -&amp;gt; &quot; &quot;);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 위 api를 사용해 현재 8081포트, 즉 blue에 애플리케이션이 동작하고 있다면 8082 포트, green으로 애플리케이션을 배포한다. (반대도 마찬가지, 아무 애플리케이션 동작 안하고 있다면 blue에 배포)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. health check를 통해 배포가 잘 완료되었는지 확인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. nginx가 포트포워딩을 지금 배포한 애플리케이션으로 변경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 기존 실행중이던 애플리케이션 프로세스 kill ☠️&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[변경된 배포 스크립트]&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1713456930064&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

source /home/ubuntu/action/scripts/properties.sh # jasypt 암호키 설정

PROJECT_NAME=space-club-backend
REPOSITORY=/home/ubuntu
PACKAGE=$REPOSITORY/action/build/libs/
JAR_NAME=$(ls -tr $PACKAGE | grep 'SNAPSHOT.jar' | tail -n 1) # snapshot jar 파일
JAR_PATH=$PACKAGE$JAR_NAME # action/build/libs/*SNAPSHOT.jar
echo &quot;&amp;gt; build 파일명: $JAR_NAME&quot;

# 금칙어 리스트 복사 (최신화)
PROFANITY_LIST_LOCATION_TO_STORE=$REPOSITORY/bad_word_list.txt
PROFANITY_LIST_LOCATION=$REPOSITORY/action/src/main/resources/secrets/bad_word_list.txt
echo &quot;&amp;gt; 금칙어 list 복사&quot; # 덮어 쓰기
cp -f $PROFANITY_LIST_LOCATION $PROFANITY_LIST_LOCATION_TO_STORE 

echo &quot;&amp;gt; build 파일 복사&quot; # 덮어 쓰기
DEPLOY_PATH=$REPOSITORY/jar/
cp -f $JAR_PATH $DEPLOY_PATH

echo &quot;&amp;gt; 현재 구동중인 SET 확인&quot;
PROFILE_BLUE=$(curl -s http://localhost:8081/api/profile)
PROFILE_GREEN=$(curl -s http://localhost:8082/api/profile)
echo &quot;&amp;gt; BLUE : $PROFILE_BLUE GREEN : $PROFILE_GREEN&quot;

# 쉬고 있는 set 찾기: blue가 사용중이면 green이 쉬고 있고, 반대면 blue가 쉬고 있음
if [ $PROFILE_BLUE == &quot;blue&quot; ]
then
  IDLE_PROFILE=green
  IDLE_PORT=8082
elif [ $PROFILE_GREEN == &quot;green&quot; ]
then
  IDLE_PROFILE=blue
  IDLE_PORT=8081
else
  echo &quot;&amp;gt; 일치하는 Profile이 없습니다. Profile: $PROFILE_BLUE $PROFILE_GREEN&quot;
  echo &quot;&amp;gt; BLUE를 할당합니다. IDLE_PROFILE: blue&quot;
  IDLE_PROFILE=blue
  IDLE_PORT=8081
fi

echo &quot;&amp;gt; application.jar 교체&quot;
IDLE_APPLICATION=$IDLE_PROFILE-SpaceClub.jar
IDLE_APPLICATION_PATH=$DEPLOY_PATH$IDLE_APPLICATION

echo &quot;&amp;gt; jar 파일과 blue/green-SpaceClub.jar와 심볼릭 링크 생성&quot;
ln -Tfs $DEPLOY_PATH$JAR_NAME $IDLE_APPLICATION_PATH

echo &quot;&amp;gt; $IDLE_PROFILE 에서 구동중인 애플리케이션 pid 확인&quot;
IDLE_PID=$(pgrep -f $IDLE_APPLICATION)
if [ -z $IDLE_PID ]
then
  echo &quot;&amp;gt; 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다.&quot;
else
  echo &quot;&amp;gt; kill -15 $IDLE_PID&quot;
  sudo kill -15 $IDLE_PID
  sleep 5
fi

echo &quot;&amp;gt; $IDLE_PROFILE 배포&quot;
sudo nohup java -jar $IDLE_APPLICATION_PATH --spring.profiles.active=develop,$IDLE_PROFILE --jasypt.encryptor.password=${encrypt} --bad-word.path=$PROFANITY_LIST_LOCATION_TO_STORE &amp;gt; /home/ubuntu/log/nohup_log.out 2&amp;gt; /home/ubuntu/log/nohup_error.out &amp;amp;

echo &quot;&amp;gt; $IDLE_PROFILE 10초 후 Health check 시작&quot;
echo &quot;&amp;gt; curl -s http://localhost:$IDLE_PORT/actuator/health &quot;
sleep 10

for retry_count in {1..10}
do
  response=$(curl -s http://localhost:$IDLE_PORT/actuator/health)
  up_count=$(echo $response | grep 'UP' | wc -l)

  if [ $up_count -ge 1 ]
  then # $up_count &amp;gt;= 1 (&quot;UP&quot; 문자열이 있는지 검증)
      echo &quot;&amp;gt; Health check 성공&quot;
      break
  else
      echo &quot;&amp;gt; Health check의 응답을 알 수 없거나 혹은 status가 UP이 아닙니다.&quot;
      echo &quot;&amp;gt; Health check: ${response}&quot;
  fi

  if [ $retry_count -eq 10 ]
  then
    echo &quot;&amp;gt; Health check 실패. &quot;
    echo &quot;&amp;gt; Nginx에 연결하지 않고 배포를 종료합니다.&quot;
    exit 1
  fi

  echo &quot;&amp;gt; Health check 연결 실패. 재시도...&quot;
  sleep 10
done

echo &quot;&amp;gt; nginx의 port를 변경합니다&quot;
echo &quot;&amp;gt; 현재 구동중인 Port 확인&quot;

# blue가 사용중이면 green이 쉬고 있고, 반대면 blue가 쉬고 있음
if [ $IDLE_PROFILE == &quot;blue&quot; ]
then
  IDLE_PORT=8081
elif [ $IDLE_PROFILE == &quot;green&quot; ]
then
  IDLE_PORT=8082
else
  echo &quot;&amp;gt; 일치하는 Profile이 없습니다. Profile: $PROFILE_BLUE $PROFILE_GREEN&quot;
  echo &quot;&amp;gt; 8081을 할당합니다.&quot;
  IDLE_PORT=8081
fi

echo &quot;&amp;gt; 전환할 Port: $IDLE_PORT&quot;
echo &quot;&amp;gt; Port 전환&quot;
echo &quot;set \$service_url http://127.0.0.1:${IDLE_PORT};&quot; |sudo tee /etc/nginx/conf.d/service-url.inc

echo &quot;&amp;gt; Nginx Reload&quot;
sudo service nginx reload

echo &quot;&amp;gt;  배포 후 기존 실행하던 애플리케이션을 종료합니다.&quot;
echo &quot;&amp;gt; $IDLE_PROFILE와 반대 애플리케이션을 종료합니다.&quot;
if [ $IDLE_PROFILE = &quot;blue&quot; ] # idle profile이 blue이면 green을 종료
then
  IDLE_PID=$(pgrep -f green-SpaceClub.jar)
  if [ -z &quot;$IDLE_PID&quot; ]
  then
    echo &quot;&amp;gt; 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다.&quot;
  else
    echo &quot;&amp;gt; blue가 실행되었고 green을 종료합니다.  kill -15 $IDLE_PID&quot;
    sudo kill -15 $IDLE_PID
  fi
elif [ $IDLE_PROFILE == &quot;green&quot; ] # idle profile이 green이면 blue를 종료
then
  IDLE_PID=$(pgrep -f blue-SpaceClub.jar)
  if [ -z &quot;$IDLE_PID&quot; ]
  then
    echo &quot;&amp;gt; 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다.&quot;
  else
    echo &quot;&amp;gt; green이 실행되었고 blue를 종료합니다. kill -15 $IDLE_PID&quot;
    sudo kill -15 $IDLE_PID
  fi
fi&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[결과]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;20~30초 정도의 downtime을 0.2초로 줄였다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그래서 현재 무중단 배포 방식의 문제점은 없는가❓ &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(1) 완벽한 무중단 배포가 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 애플리케이션이 두 개가 켜져있을 때가 있어 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;EC2의 CPU 사용량이 튈때가 있지만, 그래도 중단시간을 최소화 할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;찾아보니, &lt;b&gt;Nginx가 포트포워딩 8081에서 8082로 (혹은 반대로) 전환하고 reload 하면서 0.2초간의 down time이 발생한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;따라서 완벽한 무중단 배포라고는 할 수 없을 것같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(2) stateful한 애플리케이션일 경우 적합하지 못하다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;현재 스프링 애플리케이션은 JWT로 인증 처리를 하고 있어서 상관은 없지만, 세션을 통한 인증방식 같이 메모리에 데이터를 저장해 사용하는 stateful한 애플리케이션일 경우는, 현재의 방식이 적합하지 않다는 생각이 든다.&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;size18&quot;&gt;&lt;b&gt;(3) 요청이 처리가 안될 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 0.2초의 down time이 발생하기 때문에 당연하지만, 스프링 애플리케이션이 바뀌면서, 기존 처리하던 요청들이 nginx 포트 포워딩에 따라 응답을 받지 못할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이것을 해결하는 방식으로 spring에서 지원하는 graceful shutdown을 사용하여 해결 할 수도 있을 것 같다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xFjZQ/btsGI6eUqAp/U1xiPytMf9ONXoKmi3cjrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xFjZQ/btsGI6eUqAp/U1xiPytMf9ONXoKmi3cjrK/img.png&quot; data-alt=&quot;출처 : 스프링 공식문서(https://docs.spring.io/spring-boot/reference/web/graceful-shutdown.html)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xFjZQ/btsGI6eUqAp/U1xiPytMf9ONXoKmi3cjrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxFjZQ%2FbtsGI6eUqAp%2FU1xiPytMf9ONXoKmi3cjrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1198&quot; height=&quot;739&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : 스프링 공식문서(https://docs.spring.io/spring-boot/reference/web/graceful-shutdown.html)&lt;/figcaption&gt;
&lt;/figure&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;b&gt;  끝맺으며...&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라 작업을 진행하면서 느낀건, 진짜 참을성이 전부였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수많은 예기치 못한 상황들을 해결하고, 끝내 해내는 것이 중요했다.&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;b&gt;인프라 적용 흔적들, 참고한 Reference 블로그&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;노션 링크&quot; href=&quot;https://www.notion.so/HTTPS-d4a3dc9524014be2a78fbc08b184bfa3?pvs=4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[노션링크]&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713458529211&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;실제 HTTPS 적용기 | Notion&quot; data-og-description=&quot;위 블로그를 참고해서 space club 프로젝트의 HTTPS 설정을 진행했다.&quot; data-og-host=&quot;kaput-trombone-343.notion.site&quot; data-og-source-url=&quot;https://www.notion.so/HTTPS-d4a3dc9524014be2a78fbc08b184bfa3?pvs=4&quot; data-og-url=&quot;https://kaput-trombone-343.notion.site/d4a3dc9524014be2a78fbc08b184bfa3&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JO1r3/hyVPUXOhB1/1Ppy1rgj19aarJKJ1QqIN1/img.png?width=2000&amp;amp;height=1461&amp;amp;face=0_0_2000_1461,https://scrap.kakaocdn.net/dn/eN5TV/hyVPUjckTT/yA8lzCKkokBNMn9mTMqKq0/img.png?width=2000&amp;amp;height=1461&amp;amp;face=0_0_2000_1461&quot;&gt;&lt;a href=&quot;https://www.notion.so/HTTPS-d4a3dc9524014be2a78fbc08b184bfa3?pvs=4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.notion.so/HTTPS-d4a3dc9524014be2a78fbc08b184bfa3?pvs=4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JO1r3/hyVPUXOhB1/1Ppy1rgj19aarJKJ1QqIN1/img.png?width=2000&amp;amp;height=1461&amp;amp;face=0_0_2000_1461,https://scrap.kakaocdn.net/dn/eN5TV/hyVPUjckTT/yA8lzCKkokBNMn9mTMqKq0/img.png?width=2000&amp;amp;height=1461&amp;amp;face=0_0_2000_1461');&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;실제 HTTPS 적용기 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;위 블로그를 참고해서 space club 프로젝트의 HTTPS 설정을 진행했다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kaput-trombone-343.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Network</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/113</guid>
      <comments>https://juno-juno.tistory.com/113#entry113comment</comments>
      <pubDate>Fri, 19 Apr 2024 01:42:36 +0900</pubDate>
    </item>
    <item>
      <title>  동시성 문제 해결을 위해 어떤 Lock을 사용해야 할 까? - (2) DB Lock의 Shared Lock, Exclusive Lock Deep Dive  </title>
      <link>https://juno-juno.tistory.com/111</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  Overview&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Shared Lock과 Exclusive Lock&lt;/b&gt;은&lt;b&gt; S-Lock, X-Lock&lt;/b&gt;이라고도 하며, (&lt;b&gt;읽기 락, 쓰기 락&lt;/b&gt;), (&lt;b&gt;공유 락, 배타 락&lt;/b&gt;)이라고 불리기도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 이 lock에 대한 내용을 학습하면서 여러 블로그들을 찾아봤는데, 잘못된 내용들이 정말 많았다!&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 style=&quot;color: #000000;&quot;&gt;그래서 그 내용들을 실제 내가 MySQL 쿼리를 통해 비교하면서 바로잡고, 파헤쳐 보기 위해서 글을 써야겠다는 결심을 했다.&lt;/span&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;color: #000000;&quot;&gt;&lt;b&gt; Shared Lock과 Exclusive Lock&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;a style=&quot;color: #009a87;&quot; title=&quot;앞 글&quot; href=&quot;https://juno-juno.tistory.com/110&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;앞 글&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;에서도 언급했지만, InnoDB 스토리지 엔진 레벨에서의 Lock을 설명하면서 Shared Lock, Exclusive Lock의 개념이 나왔다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;특정 row에 lock을 걸면 레코드락, 여러 row에 range로 lock을 걸면 넥스크 키 락, 그리고 그 row들 중간에 걸리는 갭 락 이 있었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;InnoDB 스토리지 엔진이 제공하는 락을 취득하는 방법에는&lt;b&gt; 1️⃣ UPDATE 쿼리, 2️⃣ DELETE 쿼리, 3️⃣ Shared Lock / Exclusive Lock&lt;/b&gt; 획득 시에 발생한다고 언급했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;그 중, Shared Lock과 Exclusive Lock에 대해서 알아보자.&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. Shared Lock (공유 락)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Shared Lock은 MySQL 8.0 버전 부터 SELECT .. FOR SHARE 을 통해 획득할 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Shared Lock은 읽기 잠금이라고 말한다. 그래서 리소스를 여러 사용자가 &lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동시에 레코드를 읽을 수&lt;/span&gt;는 있지만, 다른 사용자가 변경 하는 것은 차단한다고 여러 블로그들에서 말한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 Exclusive Lock에서도 동일하게 리소스를 여러 사용자가 동시에 레코드를 읽을 수는 있지만, 변경하는 것을 차단한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;뿐만 아니라, shared, exclusive lock이 아닌 update, delete 쿼리에서도 동일하게 다른 트랜잭션에서 동시에 변경하는 것은 차단한다.&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 style=&quot;color: #000000;&quot;&gt;무슨말이냐면, 사원번호가 10001번이고 연봉이 60117인 row에 레코드 락, 그 중 shared lock을 한 트랜잭션 내에서 걸었다. (예시는 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;a style=&quot;color: #009a87;&quot; title=&quot;업무에 바로쓰는 SQL 튜닝 책&quot; href=&quot;https://kaput-trombone-343.notion.site/SQL-359b8db618a547fc8a6b25839eb14d2b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;업무에 바로쓰는 SQL 튜닝 책&lt;/a&gt;&lt;/span&gt;의 데이터를 사용했다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;autocommit을 끄고, 트랜잭션을 시작했다. 그리고 shared lock을 for share 키워드를 통해 획득했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tdYPE/btsFm1sg6PC/y5Sm9cazVsCKPhbbsWYT00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tdYPE/btsFm1sg6PC/y5Sm9cazVsCKPhbbsWYT00/img.png&quot; data-alt=&quot;transaction 1번 - shared lock 획득&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tdYPE/btsFm1sg6PC/y5Sm9cazVsCKPhbbsWYT00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtdYPE%2FbtsFm1sg6PC%2Fy5Sm9cazVsCKPhbbsWYT00%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;698&quot; height=&quot;248&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;transaction 1번 - shared lock 획득&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;shared lock을 위에서 얻고, 다른 창을 열어 다른 트랜잭션에서 해당 row를 update해 보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QlWt1/btsFsdydvDb/ZJmF2RTFLBnFNcvPbWNgzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QlWt1/btsFsdydvDb/ZJmF2RTFLBnFNcvPbWNgzK/img.png&quot; data-alt=&quot;transaction 2번 - update 통한 row 변경 시도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QlWt1/btsFsdydvDb/ZJmF2RTFLBnFNcvPbWNgzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQlWt1%2FbtsFsdydvDb%2FZJmF2RTFLBnFNcvPbWNgzK%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;727&quot; height=&quot;138&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;transaction 2번 - update 통한 row 변경 시도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러니 무한 대기 상태에 빠졌다. spin lock형태로 계속 시도하는 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러고 transaction 1번에서 rollback 시켰더니, 대기중이던 transaction 2번에서 update 쿼리가 정상 실행되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ee3E75/btsFmgiVbiy/gJjawwOTkffesGcVDS4Et1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ee3E75/btsFmgiVbiy/gJjawwOTkffesGcVDS4Et1/img.png&quot; data-alt=&quot;transaction 2번 - transaction 1번에서 rollback 후 실행 (44초 걸림)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ee3E75/btsFmgiVbiy/gJjawwOTkffesGcVDS4Et1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fee3E75%2FbtsFmgiVbiy%2FgJjawwOTkffesGcVDS4Et1%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;741&quot; height=&quot;83&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;transaction 2번 - transaction 1번에서 rollback 후 실행 (44초 걸림)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 SHOW PROCESSLIST;로 다른 창을 하나 더 열어 프로세스가 어떤 상태인지 비교를 해 보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 Transaction 1번이 rollback을 통해 lock 반환 전 모습이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1799&quot; data-origin-height=&quot;215&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxUo7K/btsFm7slvF9/8Y9UkmyxQGi2mxaavmXfx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxUo7K/btsFm7slvF9/8Y9UkmyxQGi2mxaavmXfx1/img.png&quot; data-alt=&quot;Transaction 3 - 프로세스 상태 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxUo7K/btsFm7slvF9/8Y9UkmyxQGi2mxaavmXfx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxUo7K%2FbtsFm7slvF9%2F8Y9UkmyxQGi2mxaavmXfx1%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;985&quot; height=&quot;118&quot; data-origin-width=&quot;1799&quot; data-origin-height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 3 - 프로세스 상태 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Id가 77번인 프로세스에서 update 쿼리를 계속해서 실행하고 있다.&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 style=&quot;color: #000000;&quot;&gt;아래는 Transaction 1번이 rollback 후 lock을 반환 하고 난 모습이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwisPj/btsFmiVb8Re/QCqfNkvoWlxqD7K6YookK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwisPj/btsFmiVb8Re/QCqfNkvoWlxqD7K6YookK0/img.png&quot; data-alt=&quot;Transaction 3 - 프로세스 상태 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwisPj/btsFmiVb8Re/QCqfNkvoWlxqD7K6YookK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwisPj%2FbtsFmiVb8Re%2FQCqfNkvoWlxqD7K6YookK0%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;1128&quot; height=&quot;210&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 3 - 프로세스 상태 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Id가 77번인 프로세스에서 update를 마치고 state가 바뀌었다.&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;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데 이렇게 lock을 획득하고 다른 트랜잭션에서 update 쿼리 실행 시 대기하는 현상은 shared lock, exclusive lock이 아닌 update나 delete에서도 동일하다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WEYaB/btsFoDdpNka/gxICFZQX0xssJMzn7ZinE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WEYaB/btsFoDdpNka/gxICFZQX0xssJMzn7ZinE1/img.png&quot; data-alt=&quot;Trasaction 1번 - delete 쿼리 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WEYaB/btsFoDdpNka/gxICFZQX0xssJMzn7ZinE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWEYaB%2FbtsFoDdpNka%2FgxICFZQX0xssJMzn7ZinE1%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;661&quot; height=&quot;114&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Trasaction 1번 - delete 쿼리 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/phS3p/btsFmjNoolx/3p5dLZA92XUuK1DrfR9dTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/phS3p/btsFmjNoolx/3p5dLZA92XUuK1DrfR9dTK/img.png&quot; data-alt=&quot;Transaction 2번 - delete 쿼리 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/phS3p/btsFmjNoolx/3p5dLZA92XUuK1DrfR9dTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FphS3p%2FbtsFmjNoolx%2F3p5dLZA92XUuK1DrfR9dTK%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;661&quot; height=&quot;106&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 2번 - delete 쿼리 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 쿼리를 보면, 결국 &lt;b&gt;transaction 1번이 rollback을 하고 나서야 16초 만에 delete 쿼리가 실행되는 것을 확인할 수 있었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이건&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;&lt;b&gt;동시에 레코드를 읽을 수는 있지만, 다른 사용자가 변경 하는 것은 차단하는 것이 shared lock의 특징이라고 나오는데,&amp;nbsp;&lt;/b&gt;&lt;b&gt;이는 shared lock에서의 특징이라기 보다는 InnoDB 스토리지 엔진 상에서 특정 row에 레코드락이 걸리기 때문이라는 것을 알 수 있었다.&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 개인적으로 shared lock에 대해서 학습하다 헷갈린 부분이 아래 부분인데, 특정 row에 대해서 shared lock을 얻은 트랜잭션내에서 update를 통한 row에 대한 변경이 가능하다! &lt;b&gt;shared lock이라고 무조건 lock이 걸린 row에 대해 쓰기작업이 불가능한것 아니라, 동일한 트랜잭션이면 가능하다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7uVhH/btsFofKrnhz/k1qsMf6LBa6yIFhfxiygPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7uVhH/btsFofKrnhz/k1qsMf6LBa6yIFhfxiygPk/img.png&quot; data-alt=&quot;동일한 트랜잭션일 경우 shared lock 획득 후 데이터 변경 가능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7uVhH/btsFofKrnhz/k1qsMf6LBa6yIFhfxiygPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7uVhH%2FbtsFofKrnhz%2Fk1qsMf6LBa6yIFhfxiygPk%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;740&quot; height=&quot;401&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;동일한 트랜잭션일 경우 shared lock 획득 후 데이터 변경 가능&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러면 Shard Lock의 진짜 특징은 뭘까?&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 style=&quot;color: #000000;&quot;&gt;&lt;b&gt;아래에서 볼 수 있듯, 한 트랜잭션에서 공유 락을 획득하면, 해당 트랜잭션이 커밋 / 롤백을 통해 락을 반환할 때 까지 다른 트랜잭션에서 획득 할 수 있는 락을 말한다. 단, 배타 락은 획득 불가능 하다. 따라서  다른 트랜잭션에서 배타 락 을 획득하려 하면 대기 상태에 빠진다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpOtXJ/btsFoeLzBvm/UJTIPOBSyVNiZonvk4Htrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpOtXJ/btsFoeLzBvm/UJTIPOBSyVNiZonvk4Htrk/img.png&quot; data-alt=&quot;Transaction 1번 - 트랜잭션 시작 후 공유락 획득&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpOtXJ/btsFoeLzBvm/UJTIPOBSyVNiZonvk4Htrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpOtXJ%2FbtsFoeLzBvm%2FUJTIPOBSyVNiZonvk4Htrk%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;779&quot; height=&quot;199&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 1번 - 트랜잭션 시작 후 공유락 획득&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caMUdx/btsFm4JkE0b/SQe4AEzWpPFtqIuWHA6WTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caMUdx/btsFm4JkE0b/SQe4AEzWpPFtqIuWHA6WTk/img.png&quot; data-alt=&quot;Transaction 2번 - 트랜잭션 시작 후 공유락 획득 가능 but 배타 락 획득 불가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caMUdx/btsFm4JkE0b/SQe4AEzWpPFtqIuWHA6WTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaMUdx%2FbtsFm4JkE0b%2FSQe4AEzWpPFtqIuWHA6WTk%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;794&quot; height=&quot;255&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 2번 - 트랜잭션 시작 후 공유락 획득 가능 but 배타 락 획득 불가&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2) Exclusive Lock (배타 락)&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Exclusive Lock은 &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이미 나왔지만&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;SELECT .. FOR UPDATE&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;을 통해 획득할 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배타 락의 특징 또한 다른 트랜잭션에서 읽기가 불가능하다고 알고 있었는데, 이는 잘못되었다고 생각한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drnUn7/btsFm0GUItJ/vgYYSWhBODsSKB81GDpEVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drnUn7/btsFm0GUItJ/vgYYSWhBODsSKB81GDpEVk/img.png&quot; data-alt=&quot;Transaction 1번 - Exclusive Lock 획득&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drnUn7/btsFm0GUItJ/vgYYSWhBODsSKB81GDpEVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrnUn7%2FbtsFm0GUItJ%2FvgYYSWhBODsSKB81GDpEVk%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;780&quot; height=&quot;197&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 1번 - Exclusive Lock 획득&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 Transaction 1번에서 배타락을 획득하면 다른 트랜잭션에서 읽기가 안될것 같지만 아래와 같이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfVLMC/btsFpPkl8JL/IoKiQYEqt6TaBxz5xqHou0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfVLMC/btsFpPkl8JL/IoKiQYEqt6TaBxz5xqHou0/img.png&quot; data-alt=&quot;Transaction 2번 - 동일한 row에 대해서 select 가능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfVLMC/btsFpPkl8JL/IoKiQYEqt6TaBxz5xqHou0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfVLMC%2FbtsFpPkl8JL%2FIoKiQYEqt6TaBxz5xqHou0%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;746&quot; height=&quot;213&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 2번 - 동일한 row에 대해서 select 가능&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같은 select 쿼리를 실행하면 대기 상태에 빠질것 같았지만 바로 조회가 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜그럴까?&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배타락의 특징을 잘못 이해하고 있어서 위와 같은 오해를 했는데, &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배타락의 특징은 한 트랜잭션에서 배타락을 획득하면 커밋 or 롤백을 통해 배타락을 반납하기 전 까지 다른 트랜잭션에서 배타락 AND 공유락 을 취득 할 수 없다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XL1gn/btsFmlqWZWP/O88Q2GQRHeVFICOtmVWWF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XL1gn/btsFmlqWZWP/O88Q2GQRHeVFICOtmVWWF1/img.png&quot; data-alt=&quot;Transaction 1번 - 배타락 획득&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XL1gn/btsFmlqWZWP/O88Q2GQRHeVFICOtmVWWF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXL1gn%2FbtsFmlqWZWP%2FO88Q2GQRHeVFICOtmVWWF1%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;199&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 1번 - 배타락 획득&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AEsNi/btsFm4vPP22/rmSgdhmV5GcTFzZjzdHqfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AEsNi/btsFm4vPP22/rmSgdhmV5GcTFzZjzdHqfK/img.png&quot; data-alt=&quot;Transaction 2번 - 배타락 획득 실패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AEsNi/btsFm4vPP22/rmSgdhmV5GcTFzZjzdHqfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAEsNi%2FbtsFm4vPP22%2FrmSgdhmV5GcTFzZjzdHqfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;99&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 2번 - 배타락 획득 실패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배타락을 Transaction 2번에서 획득 하려고 하면 대기상태에 빠진다. (Transaction 1번이 배타락을 커밋 / 롤백을 통해 반납해야 획득한다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때는 Transaction 2번에서 공유락을 획득 하려고 해도 대기상태에 빠진다&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drOQcm/btsFsczlGwA/MjMdMmAadCQdqqzloSt5mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drOQcm/btsFsczlGwA/MjMdMmAadCQdqqzloSt5mk/img.png&quot; data-alt=&quot;Transaction 2번 - 공유락 획득 실패 (대기상태)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drOQcm/btsFsczlGwA/MjMdMmAadCQdqqzloSt5mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrOQcm%2FbtsFsczlGwA%2FMjMdMmAadCQdqqzloSt5mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;99&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transaction 2번 - 공유락 획득 실패 (대기상태)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;정리하자면, 공유락은 한 트랜잭션에서 획득 시, 다른 트랜잭션에서도 획득 가능하다. 이는 다른 트랜잭션에서 배타락의 획득을 막고, 다른 트랜잭션에서의 update, delete 쿼리 실행을 막는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배타 락은 한 트랜잭션에서 획득 시, 다른 트랜잭션에서 공유락, 배타락 획득 모두를 막는다. 이는 다른 트랜잭션에서의 updaet, delete 쿼리 실행을 막는다.&amp;nbsp;&lt;/b&gt;&lt;/span&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;color: #000000;&quot;&gt;&lt;b&gt;후기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;보통 후기를 잘 쓰지는 않는데, 여러 블로그를 통해 학습을 하는 것도 중요하지만, 직접 실습을 통해 결과를 확인해 보는 과정도 중요하구나..! 라는 것을 느꼈다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;직접 sql 쿼리를 날려보면서, 기존의 내가 가지고 있던 생각들,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;- shared lock == 읽기 락 -&amp;gt; 다른 곳에서 읽는 것은 가능하고 쓰는 것만 막는 구나&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;- exclusive lock == 쓰기 락 -&amp;gt; 다른 곳에서 읽는 것도 안되고 쓰는것도 안되구나&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;라는 잘못된 인식에서 해방시켜 주었다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;[Lock 시리즈]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;a style=&quot;color: #009a87;&quot; title=&quot;동시성 제어를 위한 Lock - DB Lock &quot; href=&quot;https://juno-juno.tistory.com/110&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;동시성 제어를 위한 Lock (1) - DB Lock&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;a style=&quot;color: #009a87;&quot; title=&quot;동시성 제어를 위한 Lock (2) - Shard Lock, Exclusive Lock Deep Dive&quot; href=&quot;https://juno-juno.tistory.com/111&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;동시성 제어를 위한 Lock (2) - Shard Lock, Exclusive Lock Deep Dive&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Real MySQL 8.0 Chapter 5&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@soongjamm/Select-%EC%BF%BC%EB%A6%AC%EB%8A%94-S%EB%9D%BD%EC%9D%B4-%EC%95%84%EB%8B%88%EB%8B%A4.-X%EB%9D%BD%EA%B3%BC-S%EB%9D%BD%EC%9D%98-%EC%B0%A8%EC%9D%B4&quot;&gt;https://velog.io/@soongjamm/Select-%EC%BF%BC%EB%A6%AC%EB%8A%94-S%EB%9D%BD%EC%9D%B4-%EC%95%84%EB%8B%88%EB%8B%A4.-X%EB%9D%BD%EA%B3%BC-S%EB%9D%BD%EC%9D%98-%EC%B0%A8%EC%9D%B4&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709377415508&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;Select 쿼리는  S락이 아니다. (X락과 S락의 차이)&quot; data-og-description=&quot;이 글에서 사용한 dbms는 MySQL 8.x 버전이고 innoDB engine 기준입니다.저는 Real MySQL을 통해 DB를 공부하던 중 S-Lock 과 X-Lock에 대해 알게되었습니다. 그리고 SELECT - FOR UPDATE 쿼리는 해당 레코드의 X-loc&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@soongjamm/Select-%EC%BF%BC%EB%A6%AC%EB%8A%94-S%EB%9D%BD%EC%9D%B4-%EC%95%84%EB%8B%88%EB%8B%A4.-X%EB%9D%BD%EA%B3%BC-S%EB%9D%BD%EC%9D%98-%EC%B0%A8%EC%9D%B4&quot; data-og-url=&quot;https://velog.io/@soongjamm/Select-쿼리는-S락이-아니다.-X락과-S락의-차이&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/be3lLV/hyVquk53vk/HnbkcG3JPHOnhZPKKDiPK1/img.png?width=1024&amp;amp;height=1480&amp;amp;face=0_0_1024_1480,https://scrap.kakaocdn.net/dn/bofqxw/hyVuocqApS/e5fYnTHkVbII2cQv5ekNyK/img.png?width=1024&amp;amp;height=1480&amp;amp;face=0_0_1024_1480,https://scrap.kakaocdn.net/dn/bcLK6i/hyVqmHlWho/yTPaBEB76DkkoGBZHNlRU0/img.png?width=1578&amp;amp;height=1692&amp;amp;face=0_0_1578_1692&quot;&gt;&lt;a href=&quot;https://velog.io/@soongjamm/Select-%EC%BF%BC%EB%A6%AC%EB%8A%94-S%EB%9D%BD%EC%9D%B4-%EC%95%84%EB%8B%88%EB%8B%A4.-X%EB%9D%BD%EA%B3%BC-S%EB%9D%BD%EC%9D%98-%EC%B0%A8%EC%9D%B4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@soongjamm/Select-%EC%BF%BC%EB%A6%AC%EB%8A%94-S%EB%9D%BD%EC%9D%B4-%EC%95%84%EB%8B%88%EB%8B%A4.-X%EB%9D%BD%EA%B3%BC-S%EB%9D%BD%EC%9D%98-%EC%B0%A8%EC%9D%B4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/be3lLV/hyVquk53vk/HnbkcG3JPHOnhZPKKDiPK1/img.png?width=1024&amp;amp;height=1480&amp;amp;face=0_0_1024_1480,https://scrap.kakaocdn.net/dn/bofqxw/hyVuocqApS/e5fYnTHkVbII2cQv5ekNyK/img.png?width=1024&amp;amp;height=1480&amp;amp;face=0_0_1024_1480,https://scrap.kakaocdn.net/dn/bcLK6i/hyVqmHlWho/yTPaBEB76DkkoGBZHNlRU0/img.png?width=1578&amp;amp;height=1692&amp;amp;face=0_0_1578_1692');&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;Select 쿼리는 S락이 아니다. (X락과 S락의 차이)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서 사용한 dbms는 MySQL 8.x 버전이고 innoDB engine 기준입니다.저는 Real MySQL을 통해 DB를 공부하던 중 S-Lock 과 X-Lock에 대해 알게되었습니다. 그리고 SELECT - FOR UPDATE 쿼리는 해당 레코드의 X-loc&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming/Database</category>
      <category>exclusive lock</category>
      <category>S-Lock</category>
      <category>shared lock</category>
      <category>X-Lock</category>
      <category>공유 락</category>
      <category>배타 락</category>
      <category>쓰기 락</category>
      <category>읽기 락</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/111</guid>
      <comments>https://juno-juno.tistory.com/111#entry111comment</comments>
      <pubDate>Sat, 2 Mar 2024 21:35:24 +0900</pubDate>
    </item>
    <item>
      <title>  동시성 문제 해결을 위해 어떤 Lock을 사용해야 할 까? - (1) DB Lock에는 무엇이 있을까?  </title>
      <link>https://juno-juno.tistory.com/110</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  Overview&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스페이스 클럽 프로젝트를 진행하면서, 아래와 같이 클럽에서 행사를 개최할때, 공연 카테고리의 행사를 신청하면 &lt;b&gt;선착순 기능&lt;/b&gt;을 제공해야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1867&quot; data-origin-height=&quot;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/exypL7/btsEZmvXdlx/0r8mz1DbetYYvUuYKNCuU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/exypL7/btsEZmvXdlx/0r8mz1DbetYYvUuYKNCuU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/exypL7/btsEZmvXdlx/0r8mz1DbetYYvUuYKNCuU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FexypL7%2FbtsEZmvXdlx%2F0r8mz1DbetYYvUuYKNCuU0%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;1867&quot; height=&quot;918&quot; data-origin-width=&quot;1867&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 아래와 같이 행사 개최자가 최대 정원(아래는 100명)을 설정해 놓으면, 예매 장수를 선택해 신청을 해야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 정원이 없는 행사인 경우 최대 999명까지 받을 수 있도록 정책을 설정했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbYCFO/btsE1CrhFoM/xK0Rq5E9PYE0qoRYFCL121/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbYCFO/btsE1CrhFoM/xK0Rq5E9PYE0qoRYFCL121/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbYCFO/btsE1CrhFoM/xK0Rq5E9PYE0qoRYFCL121/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbYCFO%2FbtsE1CrhFoM%2FxK0Rq5E9PYE0qoRYFCL121%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;1910&quot; height=&quot;916&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vuCww/btsEWhQar4C/4Hwv3Xc99ExAUsDR7x6X70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vuCww/btsEWhQar4C/4Hwv3Xc99ExAUsDR7x6X70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vuCww/btsEWhQar4C/4Hwv3Xc99ExAUsDR7x6X70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvuCww%2FbtsEWhQar4C%2F4Hwv3Xc99ExAUsDR7x6X70%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;1070&quot; height=&quot;551&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 발생할 수 있는 문제점이 동시성 문제가 발생할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 행사를 신청하고 취소할때 동시성문제가 발생하지 않도록, &lt;b&gt;비관적 락(Pessimistic lock)&lt;/b&gt;을 사용해 동시성 문제를 해결하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sjJxT/btsE0qYWUtH/Lyxtmx4AKzOkf4DEoZFA61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sjJxT/btsE0qYWUtH/Lyxtmx4AKzOkf4DEoZFA61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sjJxT/btsE0qYWUtH/Lyxtmx4AKzOkf4DEoZFA61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsjJxT%2FbtsE0qYWUtH%2FLyxtmx4AKzOkf4DEoZFA61%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;1498&quot; height=&quot;218&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 api 호출 시 신청이 불가한 경우 발생한 스프링 예외를 변환해서 프론트에게 전달해 주었다.&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 style=&quot;color: #000000;&quot;&gt;프로젝트를 진행할 때는, 일단 lock의 여러 방법이 있다는 것을 알고 있었는데, JPA의 비관적 락을 통해서 DB lock을 간단히 걸 수 있고, DB의 데이터에 접근함에 있어 동시성 보장이 확실히 되었기 때문에 사용하였다.&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 style=&quot;color: #000000;&quot;&gt;하지만 찾아보니, &lt;b&gt;비관적 락을 사용한다면 읽기가 빈번 할 경우 성능 저하의 문제가 있다는 것을 알고 DB와 JPA에서 제공하는 lock의 종류로는 무엇이 있고, 어떠란 lock을 사용하는것이 좋은 선택인지에 대한 고민이 생겼다.&lt;/b&gt;&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DB Lock을 알아보자!&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. 트랜잭션과 Lock의 차이점!&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 DB에서 제공하는 &lt;b&gt;Lock은 동시성을 제어하기 위한 기능&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러면 트랜잭션은? &lt;b&gt;트랜잭션은 데이터의 정합성을 보장하기 위한 기능&lt;/b&gt;이다. 트랜잭션의 Isolation level을 설정하는데 있어서 DB lock을 사용해서 데이터의 정합성을 보장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션의 ACID 원칙에 있어 Atomicity 원칙으로 인해 애플리케이션 개발에서 고민해야 할 문제를 줄여주는 아주 필수적인 DBMS 기능을 제공한다. Partial Update가 발생하면 실패한 쿼리로 인해 남은 레코드를 다시 삭제하는 것을 개발자가 일일이 챙겨줘야 하기 때문이다.&amp;nbsp;&lt;/span&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;color: #000000;&quot;&gt;&lt;b&gt;2. MySQL 엔진의 Lock에 대해서 알아보자!&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL 엔진과 InnoDB 스토리지 엔진의 차이를 모른다면&lt;/span&gt; &lt;a title=&quot;여기&quot; href=&quot;https://juno-juno.tistory.com/106&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를&lt;span style=&quot;color: #000000;&quot;&gt; 참고하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL 엔진 레벨 Lock은 모든 스토리지 엔진에 영향을 미치지만 스토리지 엔진 레벨의 Lock은 스토리지 엔진 간 상호 영향을 미치지는 않는다.&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;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;(1) 글로벌 락 (Global Lock)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;FLUSH TABLES WITH READ LOCK&lt;/span&gt;&lt;/b&gt; &lt;span style=&quot;color: #000000;&quot;&gt;명령으로 획득 가능하며 MySQL에서 제공하는 lock 가운데 가장 범위가 크다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL 서버 전체에 lock을 거는 것이며 실행과 동시에 MySQL 서버에 존재하는 모든 테이블을 닫고 lock을 건다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;FLUSH TABLES WITH READ LOCK&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 은 테이블 읽기 잠금 걸기 전에 먼제 테이블을 플러시 하기 때문에 테이블에 실행중인 모든 종류의 쿼리가 완료되어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;글로벌 락을 통해 백업 또는 데이터 마이그레이션 작업 완료 후에는&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; UNLOCK TABLES&lt;/b&gt;&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;사용해서 Lock을 해제해야한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;사용예시는 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708518739751&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;START TRANSACTION;
FLUSH TABLES WITH READ LOCK;
mysqldump --all-databases &amp;gt; backup.sql;
UNLOCK TABLES;
COMMIT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;언급했다시피, 모든 테이블에 큰 영향을 미치기에&lt;b&gt; 웹서비스용으로 사용되는 MySQL 서버에서는 가급적 사용하지 않는 것이 좋다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;(2) 테이블 락 (Table Lock)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;테이블 락은 개별 테이블 단위로 설정되는 락이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;LOCK TABLES table_name [ READ | WRITE ] &lt;span style=&quot;color: #000000;&quot;&gt;사용해서 락 획득이 가능하다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;아래와 같이 명시적으로 사용가능한데, 테이블 락도 특별한 상황이 아니면 애플리케이션에서 사용할 필요가 거의 없다고 한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708520068818&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LOCK TABLES users WRITE;
# 작업 수행
UNLOCK TABLES;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;명시적으로 테이블을 잠그는 작업은 글로벌 락과 같이 온라인 작업에 상당한 영향을 미치기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;묵시적으로 테이블락은 MyISAM, MEMORY 테이블에 데이터 변경하는 쿼리를 실행하면 발생한다.&lt;/b&gt; 쿼리가 실행되는 동안 자동으로 획득되었다가 쿼리 완료 후 자동 해제 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만&lt;b&gt; InnoDB 테이블 경우, 스토리지 엔진 레벨에서 레코드 기반 lock을 제공하기에 묵시적인 테이블 락 설정되지 않는다.&lt;/b&gt; (명확히는&amp;nbsp; 테이블 락이 걸리지만, DML 쿼리는 무시되고&lt;b&gt; DDL 경우에만 영향을 미침&lt;/b&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;(3) 네임드 락 (Named Lock)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;GET_LOCK()&lt;/b&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수를 이용해서 임의의 문자열에 대해 잠금 설정이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;그냥 단순히 사용자가 지정한 문자열에 대해 획득하고 반납하는 lock이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;이는 나중에 다룰 &lt;b&gt;분산락 구현&lt;/b&gt;에 사용될 수 있다. &lt;b&gt;분산락이란&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;여러서버에서 공유된 데이터를 제어하기 위해 사용하는 기술인데, DB서버가 1대인데 비해 서버가 여러대로 서비스하는 상황에서 데이터 동기화  하기 위해 사용하는 lock을 말한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;select get_lock(key, timeout)&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 통해 &lt;b&gt;lock을 획득&lt;/b&gt; 할 수 있다.&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;select release_lock(key)&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 통해서&lt;b&gt; lock을 해제&lt;/b&gt; 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래는 내가 동시성을 학습하면서 작성한 코드인데, 사용방식의 예는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실무에서는 lock을 획득하고 반납하는데 있어&lt;b&gt; connection 고갈이 일어날 수 있기에 datasource를 분리하는게 좋다고 한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 named lock을 획득하고 반납을 담당하는 repository를 JdbcTemplate을 이용해 생성해 주었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;그 이유는 해당 lock 설정은 특정 entity에 종속되지 않기 때문에 Spring Data JPA를 사용하는 것 보다 JdbcTemplate을 사용하였다.&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Repository
public class NamedLockRepository {

    private final JdbcTemplate jdbcTemplate;

    public NamedLockRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void getLock(String key) {
        jdbcTemplate.execute(&quot;SELECT GET_LOCK('&quot; + key + &quot;', 10)&quot;);
    }

    public void releaseLock(String key) {
        jdbcTemplate.execute(&quot;SELECT RELEASE_LOCK('&quot; + key + &quot;')&quot;);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
public class NamedLockStockFacade {

    private final LockRepository lockRepository;
    private final StockService stockService;

    public NamedLockStockFacade(LockRepository lockRepository, StockService stockService) {
        this.lockRepository = lockRepository;
        this.stockService = stockService;
    }

    @Transactional
    public void decrease(Long id, Long quantity) {
        String lockKey = id.toString();
        try{
            lockRepository.getLock(lockKey);
            // critical section
            stockService.decrease(id, quantity);
        } finally {
            lockRepository.releaseLock(lockKey);
        }
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;(4) 메타데이터 락 (Metadata Lock)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;데이터베이스 객체의 이름이나 구조를 변경하는 경우에 획득하는 lock이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RENAME TABLE table_a TO table_b&lt;/b&gt;&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;같이 테이블 이름 변경하는 경우 &lt;b&gt;자동으로 획득&lt;/b&gt;된다. (두 테이블 모두 lock 설정)&lt;/span&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;color: #000000;&quot;&gt;&lt;b&gt;2.  InnoDB 스토리지 엔진에서의 Lock에 대해서 알아보자!&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;InnoDB 스토리지 엔진은 &lt;b&gt;레코드 기반의 잠금 기능&lt;/b&gt;을 제공하는데, 잠금정보가 상당히 작은 공간으로 관리된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서&lt;b&gt; MyISAM 보다 훨씬 뛰어난 동시성 처리&lt;/b&gt;를 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 InnoDB의 잠금은 레코드를 잠그는 것이 아닌 인덱스를 잠그는 방식으로 처리되기 때문에, 변경해야하는 레코드를 찾기 위해 검색한 &lt;b&gt;인덱스의 레코드를 모두 락을 걸어야 한다는 특징&lt;/b&gt;이 있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스토리지 엔진 잠금은 레코드 락과 레코드와 레코드 사이를 잠그는 갭(Gap)락 등이 존재하는데 하나씩 알아가보자!&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;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;(1) 레코드 락&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;레코드 자체만을 잠그는 것을 레코드 락 (Record Lock)이라고 하며, 다른 상용 DBMS의 레코드 락과 동일한 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 앞서 언급했지만, InnoDB 스토리지 엔진은 레코드 자체가 아닌&lt;b&gt; 인덱스의 레코드를 잠근다는 것이 차이점&lt;/b&gt;이 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스가 없으면 그럼 어쩌는가? -&amp;gt; &lt;b&gt;인덱스가 없더라도 내부적으로 생성된 클러스터 인덱스를 이용해 잠금을 설정한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;레코드 락은 어떻게 그럼 설정하는 걸까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;UPDATE, DELETE 쿼리를 통해 레코드를 업데이트 or 삭제 명령을 실행하면 UPDATE, DELETE하는 동안 레코드 락이 설정된다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 SELECT 할때 lock 설정은 어떻게 할까?&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709369141753&quot; class=&quot;sql&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT name FROM user WHERE id = 1 FOR SHARE; // 공유 락 (읽기 잠금)
SELECT name FROM user WHERE id = 1 FOR UPDATE; // 배타 락 (쓰기 잠금)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같이 &lt;b&gt;shared lock (공유 락)&lt;/b&gt;과&lt;b&gt; exclusive lock (배타 락)&lt;/b&gt;이 존재한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 shared lock과 exclusive lock은 &lt;b&gt;멀티 트랜잭션 환경에서 데이터베이스의 일관성과 무결성을 유지하기 위해서 트랜잭션의 순차적 진행을 보장할 수 있는 장치와 관련&lt;/b&gt;되어 있다 (트랜잭션의 격리 수준에 따른 데이터 정합성 보장에 사용)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 트랜잭션이 시작되어 shared lock과 exclusive lock을 취득하고, 트랜잭션이 커밋되거나 롤백되어 완료가 되면 lock이 반환된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709370043334&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 트랜잭션 시작
BEGIN;

-- Shared Lock 설정
SELECT * FROM 테이블 WHERE id = 1 FOR SHARE;

-- Exclusive Lock 설정
SELECT * FROM 테이블 WHERE id = 2 FOR UPDATE;

-- 데이터 변경
UPDATE 테이블 SET 이름 = '새로운 이름' WHERE id = 1;

-- 트랜잭션 롤백 or 커밋
ROLLBACK or COMMIT;
-- Shared Lock과 Exclusive Lock 모두 해제 (출처 : google gemini)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션을 사용하지 않는 경우,&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; &lt;span style=&quot;text-align: start;&quot;&gt;UNLOCK TABLES; &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 통해 명시적으로 lock을 해제할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;정리 : &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;- shared lock과 exclusive lock은 트랜잭션이 종료시점에 해제되지만, UNLOCK TABLE 키워드를 통해 명시적으로 해제할 수도 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;- 레코드 락 취득 방법은 1️⃣ UPDATE 쿼리, 2️⃣ DELETE 쿼리, 3️⃣ Shared Lock / Exclusive Lock 획득 시에 발생한다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;(2) Gap Lock (갭 락)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;갭 락은&lt;b&gt; 레코드 자체가 아닌 레코드와 바로 인접한 레코드 사이의 간격만을 잠그는 것&lt;/b&gt;으로, 레코드와 레코드 사이의 간격에 &lt;b&gt;새로운 레코드가 생성 (insert) 되는 것을 제어&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;갭락에 대해서는 아래 당근마켓 블로그에서 자세히 다루고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709547846696&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;MySQL Gap Lock 다시보기&quot; data-og-description=&quot;우리가 일반적으로 알고 있는 데이터베이스 서버의 잠금(Lock)은 레코드 자체에 대한 잠금(Record Lock)이에요. 어떤 트랜잭션에서 레코드를 변경하기 위해서는 그 레코드를 잠그고, 그 동안은 다른 &quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/daangn/mysql-gap-lock-%EB%8B%A4%EC%8B%9C%EB%B3%B4%EA%B8%B0-7f47ea3f68bc&quot; data-og-url=&quot;https://medium.com/daangn/mysql-gap-lock-%EB%8B%A4%EC%8B%9C%EB%B3%B4%EA%B8%B0-7f47ea3f68bc&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bVlLFx/hyVuf1tR6q/x95A5Udbr8Jf6ePg71Qfkk/img.png?width=790&amp;amp;height=484&amp;amp;face=0_0_790_484&quot;&gt;&lt;a href=&quot;https://medium.com/daangn/mysql-gap-lock-%EB%8B%A4%EC%8B%9C%EB%B3%B4%EA%B8%B0-7f47ea3f68bc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/daangn/mysql-gap-lock-%EB%8B%A4%EC%8B%9C%EB%B3%B4%EA%B8%B0-7f47ea3f68bc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bVlLFx/hyVuf1tR6q/x95A5Udbr8Jf6ePg71Qfkk/img.png?width=790&amp;amp;height=484&amp;amp;face=0_0_790_484');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL Gap Lock 다시보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;우리가 일반적으로 알고 있는 데이터베이스 서버의 잠금(Lock)은 레코드 자체에 대한 잠금(Record Lock)이에요. 어떤 트랜잭션에서 레코드를 변경하기 위해서는 그 레코드를 잠그고, 그 동안은 다른&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;&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;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;(3) Next Key Lock (넥스트 키 락)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;레코드 락과 갭 락을 합쳐 놓은 형태의 잠금&lt;/b&gt;을 말한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;갭 락, 넥스트 키 락의 목적은 바이너리 로그에 기록되는 쿼리가 replica server에서 실행될 때 source server에서 만들어 낸 결과와 동일한 결과를 만들어 내도록 보장해 주는 것이 주 목적이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를들어, &lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;UPDATE tb_gaplock SET .. WHERE id BETWEEN 1 AND 3&lt;/span&gt; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;쿼리를 실행하면, 1,3번 row + 1,3 row 사이도 잠근다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;따라서 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;실제 업무 프로그램에서는 이런 Next Key Lock이 더 일반적으로 사용 된다고 한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;(4) Auto Increment Lock (자동 증가 락)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AUTO_INCREMENT 칼럼이 적용된 테이블에 동시에 여러 레코드가 insert 되는 경우, 저장되는 각 레코드는 중복되자 않고 순서대로 증가하는 일련번호를 가져야 하기 때문에 내부적으로 auto_increment lock이라는 &lt;b&gt;테이블 수준의 lock&lt;/b&gt;을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 INSERT, REPLACE 문에서만 필요하며, UPDATE, DELETE 쿼리에서는 걸리지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션과 관계 없이 auto_increment 값 가져오는 순간만 락 걸렸다가 즉시 해제되며, 테이블에서 lock은 단 하나만 존재한다.&lt;/span&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;color: #000000;&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Real MySQL 8.0 Chapter 5&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;참고 자료는 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;a style=&quot;color: #009a87;&quot; title=&quot;노션&quot; href=&quot;https://kaput-trombone-343.notion.site/REAL-MySQL-05-5f2b9d264b1f403491f11ccad092be67&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;노션&lt;/a&gt;&lt;/span&gt;에 더 추가적으로 기록해 놓았다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709377482527&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;REAL MySQL - 05. 트랜잭션과 잠금 | Notion&quot; data-og-description=&quot;MySQL의 동시성에 영향을 미치는 Lock, 트랜잭션, 트랜잭션 격리 수준(Isolation level)에 대해 다루는 장이다.&quot; data-og-host=&quot;kaput-trombone-343.notion.site&quot; data-og-source-url=&quot;https://kaput-trombone-343.notion.site/REAL-MySQL-05-5f2b9d264b1f403491f11ccad092be67&quot; data-og-url=&quot;https://kaput-trombone-343.notion.site/5f2b9d264b1f403491f11ccad092be67&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b6kIO7/hyVqst2A5v/DGSInqylc7SKFXgrCPgDsk/img.png?width=2000&amp;amp;height=670&amp;amp;face=0_0_2000_670,https://scrap.kakaocdn.net/dn/HTIng/hyVquyBqhK/COIV3cC5wpE3Zc0VL2ult0/img.png?width=2000&amp;amp;height=670&amp;amp;face=0_0_2000_670&quot;&gt;&lt;a href=&quot;https://kaput-trombone-343.notion.site/REAL-MySQL-05-5f2b9d264b1f403491f11ccad092be67&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kaput-trombone-343.notion.site/REAL-MySQL-05-5f2b9d264b1f403491f11ccad092be67&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b6kIO7/hyVqst2A5v/DGSInqylc7SKFXgrCPgDsk/img.png?width=2000&amp;amp;height=670&amp;amp;face=0_0_2000_670,https://scrap.kakaocdn.net/dn/HTIng/hyVquyBqhK/COIV3cC5wpE3Zc0VL2ult0/img.png?width=2000&amp;amp;height=670&amp;amp;face=0_0_2000_670');&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;REAL MySQL - 05. 트랜잭션과 잠금 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MySQL의 동시성에 영향을 미치는 Lock, 트랜잭션, 트랜잭션 격리 수준(Isolation level)에 대해 다루는 장이다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kaput-trombone-343.notion.site&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.woowahan.com/2631/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.woowahan.com/2631/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709295006976&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;MySQL을 이용한 분산락으로 여러 서버에 걸친 동시성 관리 | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 안녕하세요. 비즈인프라개발팀 권순규입니다. 현재 광고시스템에서 사용하고 있는 MySQL을 이용한 분산락에 대해 설명드리고자 합니다. 분산락을 적용하게된 원인 현재 테이블은 아래&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/2631/&quot; data-og-url=&quot;https://techblog.woowahan.com/2631/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkbx9m/hyVutq7oYE/OEasGK6tdGVVbheO2bL6k0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/n4wAN/hyVulzQ7DR/1fJGw9W9KmtS0fieiFsYEK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/cz714u/hyVqjqa9EP/O8BVKtYR1rkUKq9bT165P1/img.png?width=964&amp;amp;height=374&amp;amp;face=0_0_964_374&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2631/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/2631/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkbx9m/hyVutq7oYE/OEasGK6tdGVVbheO2bL6k0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/n4wAN/hyVulzQ7DR/1fJGw9W9KmtS0fieiFsYEK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/cz714u/hyVqjqa9EP/O8BVKtYR1rkUKq9bT165P1/img.png?width=964&amp;amp;height=374&amp;amp;face=0_0_964_374');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL을 이용한 분산락으로 여러 서버에 걸친 동시성 관리 | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 안녕하세요. 비즈인프라개발팀 권순규입니다. 현재 광고시스템에서 사용하고 있는 MySQL을 이용한 분산락에 대해 설명드리고자 합니다. 분산락을 적용하게된 원인 현재 테이블은 아래&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.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://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709366093264&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: MySQL 8.0 Reference Manual :: 14.14 Locking Functions&quot; data-og-description=&quot;This section describes functions used to manipulate user-level locks. Table&amp;nbsp;14.19&amp;nbsp;Locking Functions GET_LOCK(str,timeout) Tries to obtain a lock with a name given by the string str, using a timeout of timeout seconds. A negative timeout value means infin&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot; data-og-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL :: MySQL 8.0 Reference Manual :: 14.14 Locking Functions&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This section describes functions used to manipulate user-level locks. Table&amp;nbsp;14.19&amp;nbsp;Locking Functions GET_LOCK(str,timeout) Tries to obtain a lock with a name given by the string str, using a timeout of timeout seconds. A negative timeout value means infin&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming/Database</category>
      <category>DB</category>
      <category>Lock</category>
      <category>MYSQL</category>
      <category>동시성</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/110</guid>
      <comments>https://juno-juno.tistory.com/110#entry110comment</comments>
      <pubDate>Sat, 2 Mar 2024 20:05:45 +0900</pubDate>
    </item>
    <item>
      <title>  AOP를 활용해 중복코드를 제거해보자! (feat. AOP란? )</title>
      <link>https://juno-juno.tistory.com/109</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  Overview&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 &lt;a title=&quot;포민 프로젝트&quot; href=&quot;https://github.com/prgrms-be-devcourse/BE-04-PoMin-Owner&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;포민(포장의 민족) 프로젝트&lt;/a&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;포장의 민족 프로젝트에서의 특이점이라고 한다면, 고객팀과 사장팀이 각각 서버를 나누고, 서버를 나눴으니 DB 또한 각각 나눠 진행을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그랬기 때문에,&lt;b&gt; 해당 사장 서버의 DB와 고객 서버의 DB의 정합성을 맞추는 것이 핵심이였고 항상 사장 서버에서 자원이 생성되거나 변경될 때 마다 고객팀 서버로 정보를 전송해 주어야 했다.&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;따라서 아래와 같이 InfoSender 빈을 통해서 RestTemplate을 통해 정보를 고객팀 서버로 전달했다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1a1f30; color: #afb9c3;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class InfoSender {

    private final RestTemplate restTemplate;

    private static final String URL_PREFIX = &quot;http://52.78.144.166:8080/api/v1&quot;; // 고객팀 url

    public void send(Store store) {
        StoreInfo body = StoreInfo.from(store);

        HttpEntity&amp;lt;StoreInfo&amp;gt; storeInfoHttpEntity = new HttpEntity&amp;lt;&amp;gt;(body);
        ResponseEntity&amp;lt;String&amp;gt; response = restTemplate.exchange(URL_PREFIX + &quot;/stores&quot;, POST, storeInfoHttpEntity, String.class);
        validateResponse(response);
    }

    public void send(Menu menu) {
        MenuInfo body = MenuInfo.from(menu);

        HttpEntity&amp;lt;MenuInfo&amp;gt; menuInfoHttpEntity = new HttpEntity&amp;lt;&amp;gt;(body);
        ResponseEntity&amp;lt;String&amp;gt; response = restTemplate.exchange(URL_PREFIX + &quot;/menus&quot;, POST, menuInfoHttpEntity, String.class);
        validateResponse(response);
    }

    public void send(Menu menu, OptionGroup optionGroup) {
        OptionGroupInfoToSend body = OptionGroupInfoToSend.from(optionGroup, menu);
        
        HttpEntity&amp;lt;OptionGroupInfoToSend&amp;gt; optionGroupInfoToSendHttpEntity = new HttpEntity&amp;lt;&amp;gt;(body);
        ResponseEntity&amp;lt;String&amp;gt; response = restTemplate.exchange(URL_PREFIX + &quot;/options&quot;, POST, optionGroupInfoToSendHttpEntity, String.class);
        validateResponse(response);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 빈을 주입 받아서 Service 메서드 마다 변경이 일어날 때마다 해당 엔티티 정보를 고객팀 서버로 보내 주어야 했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다 보니 &lt;b&gt;Cross Cutting Concern, 횡단 관심사가 다음과 같이 발생하게 되었다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;1836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJjiuo/btsEAVS3lpf/1EicVZN85ftgdl82xQnUQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJjiuo/btsEAVS3lpf/1EicVZN85ftgdl82xQnUQ0/img.png&quot; data-alt=&quot;횡단 관심사의 발생..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJjiuo/btsEAVS3lpf/1EicVZN85ftgdl82xQnUQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJjiuo%2FbtsEAVS3lpf%2F1EicVZN85ftgdl82xQnUQ0%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;726&quot; height=&quot;852&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;1836&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;&lt;b&gt;1. InfoSender 빈을 의존하고 있어야 하기 때문에 InfoSender와 StoreService 간의 강한 결합이 발생한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 메서드가 두 가지 일을 하고 있고, 코드의 중복이 발생한다.&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;이를 Spring에서 제공하는 Spring AOP를 사용해 해결 할 수 있었다.&lt;/b&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  AOP란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AOP&lt;/b&gt;(Aspect Oriented Programming)이란, &lt;b&gt;관점 지향 프로그래밍&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Aspect란 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것 전에 AOP를 통해서&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;과 &lt;b&gt;부가 기능&lt;/b&gt;으로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심기능은 해당 객체가 제공하는 고유의 기능&lt;/b&gt;을 말한다. 예를 들어 위의 StoreService에서 핵심 기능은 Store에 대한 정보를 저장하고, 변경하는 것이다.&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;부가 기능을 모듈화 해서 어디에 적용할지를 선택하는 기능을 합해 하나의 모듈로 만든 것을 Aspect&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;이렇게 부가기능과 핵심 기능을 분리함으로서 AOP를 사용하면 정말 메서드가 하나의 일만 할 수 있도록, 더 객체 지향적으로 코딩이 가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;  &lt;/b&gt;Spring에서는 &lt;/b&gt;AOP를 어떻게 구현하는가?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;횡단관심사, cross cutting concern을 처리하기 위해서&lt;b&gt; AOP를 구현하는 방법에는 세 가지가 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 컴파일 시점에 .class 파일로 변환 될때 바이트코드를 덧붙혀 AOP를 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 클래스 로더에 로드 될 때 코드를 덧붙혀 AOP를 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 런타임 시점에 있어서 AOP를 적용할 수 있는데, &lt;b&gt;프록시를 사용해 AOP를 적용할 수 있다.&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;첫번째, 두 번째 방식, 즉 컴파일 시점과 클래스 로더에 로드 되는 시점에 AOP를 적용하는 방식은 AOP에 대한 거의 모든 기능을 지원하는 &lt;b&gt;AspectJ 라이브러리를 통해 구현할 수 있지만, 상당히 학습량도 많고 복잡하다고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&lt;b&gt; Spring에서는 AOP를 프록시를 사용해 런타임에 적용하는 방식&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;그럼 스프링에서는 AOP를 적용할 대상을 프록시를 통해 어떻게 구현할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것에 대해서 설명하기위해 먼저 용어정리를 하자면, AOP를 적용할 대상인 Joinpoint, 적용할 부가 로직인 Advice, 적용 위치를 설정하는 Pointcut, 하나의 Advice와 하나의 Pointcut을 합친 Advisor가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pointcut에 해당하는 메서드가 있는 &lt;b&gt;클래스를 빈 등록 시 CGLIB 라이브러리를 사용해 대상 빈을 프록시로 등록해 Advice를 적용하는 방식으로 프록시 패턴을 통해 AOP를 런타임에 실행할 수 있다.&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;그럼 PointCut으로 설정된 대상에 Proxy적용을 어떻게 하느냐?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빈 후 처리기&lt;/b&gt;(Bean Post Processor)를 통해 다른 설정없이 Bean으로 등록 된 것을 프록시 객체로 바꿀 수 있는데, @Aspect 어노테이션을 사용해 Pointcut과 Advice를 설정하면, AnnotationAwareAspectJAutoProxyCreator가 자동으로 프록시를 빈으로 등록 시켜 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 부가기능과 적용위치 Pointcut을 합친 Aspect를 설정하면, 자동으로 프록시를 만들어 AOP를 런타임에 설정해 주는 것이 Spring에서 제공하는 AOP 기능이다.&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;  &lt;/b&gt;AOP를 적용해보자!&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 처음 위 StoreService에서 InfoSender를 분리해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서 Aspect를 설정해 주어야 하는데, Aspect의 Pointcut 설정하는데 있어 아래와 같이 &lt;span style=&quot;color: #009a87;&quot;&gt;@TransmitData&lt;/span&gt; 어노테이션을 생성해 어노테이션 대상으로 Pointcut을 설정해 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 StoreService, MenuService에 AOP를 설정하고 싶은데, 모든 클래스의 메서드에 적용하는게 아니라 어떤 메서드는 고객팀에 전송기능이 필요하고, 어떤 메서드는 필요 없었기 때문에 사용하기 간편한 어노테이션 기반으로 Pointcut을 설정했다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1a1f30; color: #afb9c3;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TransmitData {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1a1f30; color: #afb9c3;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class DataTransferAspect {

    private final InfoSender infoSender;

    @Pointcut(&quot;@annotation(com.ray.pominowner.store.service.aop.TransmitData)&quot;)
    private void toTransmitDataAnnotation() {}

    @AfterReturning(pointcut = &quot;toTransmitDataAnnotation()&quot;, returning = &quot;store&quot;)
    public void transmitData(Store store) {
        infoSender.send(store);
        log.info(&quot;store info sent&quot;);
    }

    @AfterReturning(pointcut = &quot;toTransmitDataAnnotation()&quot;, returning = &quot;menu&quot;)
    public void transmitData(Menu menu) {
        infoSender.send(menu);
        log.info(&quot;menu info sent&quot;);
    }

    @AfterReturning(pointcut = &quot;toTransmitDataAnnotation()&quot;, returning = &quot;optionInfo&quot;)
    public void transmitData(OptionGroupInfoToSend optionInfo) {
        infoSender.send(optionInfo);
        log.info(&quot;option info sent&quot;);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;color: #000000;&quot;&gt;&lt;b&gt;❓ 왜 @Around 대신 @AfterReturning을 사용했는가?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 위와 같이 @Around 대신 @AfterReturning을 사용한 이유는, ProceedingJoinPoint의 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;proceed()&lt;/span&gt; 메서드 호 출을 해야할 필요성을 못느꼈던것이 가장 컸고,&lt;/b&gt; @AfterReturning을 사용하는것이&lt;b&gt; target메서드의 메서드가 return되고 동작하는 기능이라는 것이 명확하게 보였기 때문이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 return값을 사용해 바로 고객팀 서버에 보내면 되었기 때문에, @AfterReturning을 사용했다.&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;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;b&gt;❓&amp;nbsp;&lt;/b&gt;왜 하나의 advice 메서드에서 처리한 게 아니라 메서드를 각각 만들었는가?&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;returning에서 설정한 값과 동일한 파라미터가 왔을 때, Object로 파라미터를 받아 instanceof 를 통해 패턴 매칭 후 해당 타입에 맞는 메서드를 호출한다면 메서드 세 개가 아닌 하나로 끝나는 문제였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그 이유는, &lt;b&gt;가독성이 떨어지고 지저분해지기 때문이다. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;a title=&quot;https://belief-driven-design.com/looking-at-java-21-switch-pattern-matching-14648/&quot; href=&quot;https://belief-driven-design.com/looking-at-java-21-switch-pattern-matching-14648/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;자바 21의 switch 문을 통한 패턴매칭&lt;/a&gt;을 사용할 수 있다면 리펙토링을 할 수 있겠지만, 자바 17버전을 사용하고 있기도 하고,&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;@AfterReturning 사용시 return 값을 받는 것에 있어서 타입이 다르면 AOP 적용이 무시 되기 때문에 각각 메서드를 만들어 주어 가독성을 높히고 역할을 분리 시킬 수 있었다.&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;위와 같이 Aspect를 설정하고, 대상이 되는 Service 코드를 아래와 같이 리펙토링 해 주었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GiTsc/btsEx62DrZk/yrCZnLwiD99mwFSgz4d3nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GiTsc/btsEx62DrZk/yrCZnLwiD99mwFSgz4d3nk/img.png&quot; data-alt=&quot;MenuService&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GiTsc/btsEx62DrZk/yrCZnLwiD99mwFSgz4d3nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGiTsc%2FbtsEx62DrZk%2FyrCZnLwiD99mwFSgz4d3nk%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;1532&quot; height=&quot;1560&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1560&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MenuService&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;1560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmL9QU/btsEAQR1iRm/6dK7pnCRR9m3K5AZqiEKV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmL9QU/btsEAQR1iRm/6dK7pnCRR9m3K5AZqiEKV0/img.png&quot; data-alt=&quot;StoreService&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmL9QU/btsEAQR1iRm/6dK7pnCRR9m3K5AZqiEKV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmL9QU%2FbtsEAQR1iRm%2F6dK7pnCRR9m3K5AZqiEKV0%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;1640&quot; height=&quot;1560&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;1560&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;StoreService&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 Entity를 반환해 주어야 한다는 점으로 Controller의 코드도 약간 수정되긴 하지만 큰 문제가 되진 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 AOP를 통해 핵심 로직과 공통 로직을 분리 시킴으로서 객체지향적이고 가독성 높은 코드를 작성할 수 있었다!&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/17350624&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://m.yes24.com/Goods/Detail/17350624&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707324300074&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 예스24&quot; data-og-description=&quot;자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량 애플리케이션 프레임워크인 스프링은 자바와 객체 지향이라는 기반 위에 굳건히 세워져 있다. 따라서 스프링을 제대로 이해하고 활용&quot; data-og-host=&quot;m.yes24.com&quot; data-og-source-url=&quot;https://m.yes24.com/Goods/Detail/17350624&quot; data-og-url=&quot;https://m.yes24.com/Goods/Detail/17350624&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dDK16A/hyVjndcgW5/l84MMRKsKHiOSMFQ6q1ij1/img.jpg?width=321&amp;amp;height=400&amp;amp;face=0_0_321_400,https://scrap.kakaocdn.net/dn/wq3U7/hyVf9Ornpz/cA9R7jreSYfQqsY0mLyBK0/img.jpg?width=321&amp;amp;height=400&amp;amp;face=0_0_321_400,https://scrap.kakaocdn.net/dn/cwqlM3/hyVgcqPX9y/TkhzgAIiGDp64ulHr1ft10/img.png?width=308&amp;amp;height=488&amp;amp;face=0_0_308_488&quot;&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/17350624&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://m.yes24.com/Goods/Detail/17350624&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dDK16A/hyVjndcgW5/l84MMRKsKHiOSMFQ6q1ij1/img.jpg?width=321&amp;amp;height=400&amp;amp;face=0_0_321_400,https://scrap.kakaocdn.net/dn/wq3U7/hyVf9Ornpz/cA9R7jreSYfQqsY0mLyBK0/img.jpg?width=321&amp;amp;height=400&amp;amp;face=0_0_321_400,https://scrap.kakaocdn.net/dn/cwqlM3/hyVgcqPX9y/TkhzgAIiGDp64ulHr1ft10/img.png?width=308&amp;amp;height=488&amp;amp;face=0_0_308_488');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량 애플리케이션 프레임워크인 스프링은 자바와 객체 지향이라는 기반 위에 굳건히 세워져 있다. 따라서 스프링을 제대로 이해하고 활용&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;m.yes24.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://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707324279912&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;스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기   수강 &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dFXjqU/hyVgbk7KQ4/9szjErKQQk1uhj8FAXBn6K/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/dabTlg/hyVi8tzzEw/1TreZrERkaceGCvKZMxIbK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/mrpBV/hyVgbenPsM/Vyv0IrhpHDfkWZUFEgjbLK/img.png?width=1200&amp;amp;height=628&amp;amp;face=731_255_826_360&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dFXjqU/hyVgbk7KQ4/9szjErKQQk1uhj8FAXBn6K/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/dabTlg/hyVi8tzzEw/1TreZrERkaceGCvKZMxIbK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/mrpBV/hyVgbenPsM/Vyv0IrhpHDfkWZUFEgjbLK/img.png?width=1200&amp;amp;height=628&amp;amp;face=731_255_826_360');&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;스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기   수강&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.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;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;
&lt;div class=&quot;css-yi4up&quot;&gt;
&lt;div class=&quot;css-7gfopr&quot;&gt;
&lt;div class=&quot;css-1kx128h&quot;&gt;
&lt;div class=&quot;css-pix79b&quot;&gt;
&lt;div class=&quot;css-1d6xttj&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming/Spring &amp;amp; Hibernate</category>
      <category>aop</category>
      <category>데코레이터</category>
      <category>프록시</category>
      <category>횡단 관심사</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/109</guid>
      <comments>https://juno-juno.tistory.com/109#entry109comment</comments>
      <pubDate>Thu, 8 Feb 2024 01:49:31 +0900</pubDate>
    </item>
    <item>
      <title>  디자인 패턴 적용기 - 팩토리 메서드 패턴, 전략 패턴  ✨ (feat. 템플릿 메서드, 템플릿 콜백)</title>
      <link>https://juno-juno.tistory.com/108</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  들어가기에 앞서...&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저, 이 포스트에서는 내가 얼마나 디자인 패턴에 대해서 열심히 공부했는지, 얼마나 많은 디자인 패턴을 아는지 보다 디자인 패턴으로 어떤 문제를 해결 했는지에 포커스를 맞춰 보고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;왜냐하면 디자인 패턴을 잘 알고 학습하는 것도 중요하지만, &lt;u&gt;디자인 패턴으로 어떤 문제를 해결 했는지&lt;/u&gt;가 더 중요하다고 생각하기 때문이다&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;디자인 패턴 각각에 대한 자세한 설명은 노션에 상세히 기록을 해 놓았다..! &lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt; &lt;/span&gt;&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #1b711d;&quot; title=&quot;디자인 패턴 학습 노션 링크&quot; href=&quot;https://kaput-trombone-343.notion.site/23-6221c6d311df4415b9eb6ec07f388cde?pvs=4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;디자인 패턴에 대해서 학습하고 정리한 노션 링크&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706940343888&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;23가지 디자인 패턴에 대해서 | Notion&quot; data-og-description=&quot;디자인 패턴이란&quot; data-og-host=&quot;kaput-trombone-343.notion.site&quot; data-og-source-url=&quot;https://kaput-trombone-343.notion.site/23-6221c6d311df4415b9eb6ec07f388cde?pvs=4&quot; data-og-url=&quot;https://kaput-trombone-343.notion.site/6221c6d311df4415b9eb6ec07f388cde&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dWvRc9/hyVb1DlZpX/2pZJ7MHdKCAVcQIIkUWQR1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cj8AJk/hyVb7KnTJm/C7PPkeEeZdNKExfk3194T0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://kaput-trombone-343.notion.site/23-6221c6d311df4415b9eb6ec07f388cde?pvs=4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kaput-trombone-343.notion.site/23-6221c6d311df4415b9eb6ec07f388cde?pvs=4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dWvRc9/hyVb1DlZpX/2pZJ7MHdKCAVcQIIkUWQR1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cj8AJk/hyVb7KnTJm/C7PPkeEeZdNKExfk3194T0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;23가지 디자인 패턴에 대해서 | Notion&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;kaput-trombone-343.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. 디자인 패턴 이란&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;디자인 패턴이란 소&lt;b&gt;프트웨어 디자인 과정에서 자주 발생하는 문제들에 대한 해결책&lt;/b&gt;을을 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SOLID 원칙&lt;/b&gt;에 따라 객체지향적으로 프로그래밍하는 데 있어서&lt;b&gt; 여러 상황에 대한 공통점들을 모아 놓은것이 디자인 패턴&lt;/b&gt;이라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;디자인 패턴을 처음에 나도 접했을 때는, 이런 패턴이 이전부터 존재했구나?! 라고 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 학습을 하고 보니, 고대 많은 개발자 분들이 비즈니스 요구사항을 프로그래밍으로 처리하며 &lt;b&gt;마주친 문제들에 대한 해결 책 중 많은 사람들이 인정한 Best Practice를 정리한 것&lt;/b&gt;으로, 1994년 GoF(Gang of Four)라고 불리는 Erich Gamma, Richard Helm, Ralph Johnson, John Vissides가 집필한&lt;/span&gt; &lt;a title=&quot;GoF 디자인 패턴&quot; href=&quot;https://m.yes24.com/Goods/Detail/17525598&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GoF 디자인 패턴 &lt;/a&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&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 style=&quot;color: #000000;&quot;&gt;&lt;b&gt;디자인 패턴은 객체지향의 특성과 객체지향 설계 원칙(SOLID)를 기반으로 구현된 설계 문제를 해결해주는 패턴이기 때문에 학습이 필요하다는 것을 느꼈다..!&lt;/b&gt;&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. 나는 디자인 패턴으로 어떤 문제를 해결 했는가?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;✨ 1️⃣ 팩토리 메서드 패턴(Factory Method Pattern) 적용 사례&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  팩토리 메서드 패턴에 대한 간단한 설명&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;팩토리 메서드 패턴이란 부모 클래스에서 객체들을 생성할 수 있는 &lt;b&gt;인터페이스를 제공&lt;/b&gt;하고, 자식 클래스들이 &lt;b&gt;생성될 객체들의 유형을 변경할 수 있도록 하는 생성패턴&lt;/b&gt;을 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHgDWL/btsEmPes1Ov/PH5EXWiQYOKqkeLKp4EQm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHgDWL/btsEmPes1Ov/PH5EXWiQYOKqkeLKp4EQm0/img.png&quot; data-alt=&quot;출처: https://refactoring.guru/ko/design-patterns/factory-method&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHgDWL/btsEmPes1Ov/PH5EXWiQYOKqkeLKp4EQm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHgDWL%2FbtsEmPes1Ov%2FPH5EXWiQYOKqkeLKp4EQm0%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;684&quot; height=&quot;423&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://refactoring.guru/ko/design-patterns/factory-method&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 구조에서 볼 수 있듯, Creator가 최상위 팩토리 클래스로 팩토리 메서드를 추상화해 ConcreteCreatorA, ConcreteCreatorB과 같은 서브 클래스가  Product를 구현하도록 해 &lt;b&gt;객체간의 결합도를 낮추고 유지보수를 용이하게 해준다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  언제 팩토리 메서드 패턴을 사용했는가?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데브코스 최종프로젝트 Space Club 프로젝트에서&lt;b&gt; 알림 기능을 구현&lt;/b&gt; 하고 있을 때다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저, 알림 기능 구현은 의존성 사이클을 줄이기 위해서 &lt;b&gt;이벤트 publish/listen 하는 방식으로 처리&lt;/b&gt; 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 notification 패키지가 아닌 외부에서 메일 기능을 사용하고 싶다면,&amp;nbsp; &lt;b&gt;ApplicationEventPublisher&lt;/b&gt; 객체의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;publishEvent()&lt;/b&gt;&lt;/span&gt; 메서드의 인자로 &lt;b&gt;MailEvent&lt;/b&gt; 객체를 생성해서 넘겨 주면 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데 &lt;b&gt;MailEvent&lt;/b&gt; 객체를 생성해 메일 이벤트를 발생시키는데, 알림이 필요한 경우는 두 경우 였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. 회원가입이 완료 되었을 경우 웰컴 메일&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. 사용자가 행사 신청을 하고 관리자가 승인 or 행사 취소 했을 때 관리자가 취소확인 한 경우 행사 신청 상태 변경 메일&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&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 style=&quot;color: #000000;&quot;&gt;그러면 메일 발송을 담당하는 &lt;b&gt;MailService&lt;/b&gt;에서 아래와 같이 &lt;b&gt;각 이벤트 마다 메일 발송 메서드를 만들어 주어야 한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금 당장 필요한 메일 기능은 회원가입과 행사 신청 상태 변경, 두 가지 경우이지만&lt;u&gt;&lt;b&gt; 확장성에 있어서는 좋지 못하다는 생각이 들었다.&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #afb9c3;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 확장성에 있어 좋지 못한 이유
@Service
@RequiredArgsConstructor
public class MailService {

    private final JavaMailSender emailSender;
    private final SpringTemplateEngine templateEngine;
    
    @Async
    @Transactional(propagation = REQUIRES_NEW)
    @TransactionalEventListener
    public void sendEmail(MailEvent1 mailEvent1) {
    // MailEvent1에 대한 메일 전송
    }
    
    @Async
    @Transactional(propagation = REQUIRES_NEW)
    @TransactionalEventListener
    public void sendEmail(MailEvent2 mailEvent1) {
    // MailEvent2에 대한 메일 전송
    }
    ...
    // 이벤트 처리 하는 sendEmail() 메서드 개수 == 발행하는 이벤트 종류 개수&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 메일을 처리하는 &lt;b&gt;이벤트는 동일한 이벤트 MailEvent 객체로 발행되어야 했다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데, 문제점이 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;1. 회원가입이 완료 되었을 경우 웰컴 메일&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;2. 사용자가 행사 신청을 하고 관리자가 승인 or 행사 취소 했을 때 관리자가 취소확인 한 경우 행사 신청 상태 변경 메일&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 두 경우에 따라 필요한 정보가 달랐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&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 style=&quot;color: #000000;&quot;&gt;하지만 2번 행사 상태 변경 고지해주는 이메일은?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자의 이메일도 필요할 뿐 아니라, &lt;u&gt;행사를 주최하는 클럽이름, 행사이름, 그리고 행사 상태가 어떻게 변경했는지가 추가적으로 필요했다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 것을 어떻게 구현할까 생각하다.. 도입한 것이 &lt;b&gt;팩토리 메서드 패턴&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메일을 보내기 위해서 &lt;b&gt;Spring Mail&lt;/b&gt;을 사용할때 메일을 전송하는 방법으로 &lt;b&gt;MimeMessage&lt;/b&gt;를 생성해서 &lt;b&gt;JavaMailSender&lt;/b&gt;에인 아래 &lt;b&gt;emailSender&lt;/b&gt; 객체에 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;send()&lt;/span&gt;&lt;/b&gt;함수의 인자로 넘겨줬어야 했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 &lt;b&gt;MailInfo&lt;/b&gt;안에는 &lt;b&gt;이메일 주소의 배열&lt;/b&gt;과 이메일의 제목과&lt;b&gt; 템플릿 이름&lt;/b&gt;이 존재해야 했다.&lt;/span&gt;&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;1384&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8pUmD/btsEkhwrMb6/fStT7xaBMcf1tVgJYdsUVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8pUmD/btsEkhwrMb6/fStT7xaBMcf1tVgJYdsUVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8pUmD/btsEkhwrMb6/fStT7xaBMcf1tVgJYdsUVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8pUmD%2FbtsEkhwrMb6%2FfStT7xaBMcf1tVgJYdsUVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1384&quot; height=&quot;649&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메일을 보낼 때는&lt;b&gt; 항상 동일한 MailInfo를 통해 처리해야 했다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;부가적으로 어느 html 템플릿을 사용할지, 제목은 어떻게 보낼지는 각 이벤트마다 다르게 설정해줘야 했다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 &lt;b&gt;MailInfo&lt;/b&gt;를 다음과 같이&lt;b&gt; 추상 클래스&lt;/b&gt;로 만들었다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #afb9c3;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public abstract class MailInfo {

    private final String[] addresses;

    public MailInfo(String[] addresses) {
        this.addresses = addresses;
    }

    public String[] email() {
        return this.addresses;
    }

    public abstract String title();

    public abstract String templateName();

    @Override
    public String toString() {
        return new StringJoiner(&quot;, &quot;, MailInfo.class.getSimpleName() + &quot;[&quot;, &quot;]&quot;)
                .add(&quot;addresses=&quot; + Arrays.toString(addresses))
                .toString();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;일단 &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;TemplateName이라는 enum을 만들어 &lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;WELCOME, EVENT_STATUS_CHANGED 같이 템플릿 이름과 템플릿 제목, DB의 html 템플릿 id 를 모아 관리함으로서 응집도를 높혔다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #afb9c3;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum TemplateName {

    WELCOME(&quot;[Space Club] 스페이스 클럽에 가입해주셔서 감사합니다.&quot;, &quot;welcome&quot;, 1L),
    EVENT_STATUS_CHANGED(&quot;[Space Club] 신청한 행사 상태 변경 알림&quot;, &quot;event-status-change&quot;, 2L),
    ;

    private final String title;

    private final String templateName;

    private final Long templateId;
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;span style=&quot;color: #000000;&quot;&gt;그리고 다형성을 이용해 각 보내는 메일 종류에 따라서 구체화를 시켜주었다.&lt;/span&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: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 회원가입 시 &lt;b&gt;웰컴 메일을 보내는 메일 정보&lt;/b&gt;이다&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706946176760&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class WelcomeMailInfo extends MailInfo {

    public WelcomeMailInfo(String[] addresses) {
        super(addresses);
    }
    
    @Override
    public String title() {
        return WELCOME.getTitle();
    }

    @Override
    public String templateName() {
        return WELCOME.getTemplateName();
    }
...&lt;/code&gt;&lt;/pre&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 &lt;b&gt;행사 상태 변경을 고지하는 메일 정보&lt;/b&gt;는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #afb9c3;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class EventStatusChangeMailInfo extends MailInfo {

    private final String clubName;
    private final String eventName;
    private final String eventStatus;
    
    public EventStatusChangeMailInfo(String[] addresses, String clubName, String eventName, String eventStatus) {
        super(addresses);
        this.clubName = clubName;
        this.eventName = eventName;
        this.eventStatus = eventStatus;
    }
    
    @Override
    public String title() {
        return EVENT_STATUS_CHANGED.getTitle();
    }

    @Override
    public String templateName() {
        return EVENT_STATUS_CHANGED.getTemplateName();
    }
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 만든 각각의 MailInfo에 대한 구현체는 MailEvent에서 정적 팩토리 메서드를 통해서 각 메일을 보내는 상황에 맞게 이벤트를 생성할 수 있도록 메서드 오버로딩(overloading)를 통해 생성하도록 해주었다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #afb9c3;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public record MailEvent(MailInfo mailInfo) {

    public static MailEvent createMailEvent(String email) {
        WelcomeMailInfo welcomeMailInfo = WelcomeMailInfo.from(email);
        return new MailEvent(welcomeMailInfo);
    }

    public static MailEvent createMailEvent(String email, String clubName, String eventName, String eventStatus) {
        EventStatusChangeMailInfo mailInfo = EventStatusChangeMailInfo.from(email, clubName, eventName, eventStatus);
        return new MailEvent(mailInfo);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;다시 위 팩토리 메서드 패턴을 간단히 설명할때 구조로 돌아가본다면,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;MailInfo가 Product가 되고, EventStatusChangeMailInfo,WelcomeMailInfo가 concrete product가 된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;MailEvent가 Creator로서&amp;nbsp; 정적 팩토리 메서드를 통해  각 concrete product를 생성하도록 한다.&lt;/b&gt;&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;span style=&quot;color: #000000;&quot;&gt;다만 전통적인 팩토리 메서드 패턴에서는 &lt;b&gt;Creator&lt;/b&gt;가 인터페이스로서&lt;b&gt; Concrete Creator&lt;/b&gt;를 통해 &lt;b&gt;Concrete Product&lt;/b&gt;를 생성하도록 하지만, 나는&lt;b&gt; Concrete Creator&lt;/b&gt; 대신 &lt;b&gt;Creator&lt;/b&gt;에서 바로&lt;u&gt;&lt;b&gt; 정적 팩토리 메서드를 통해 Product를 생성&lt;/b&gt;&lt;/u&gt;하고, 그것을 &lt;b&gt;Creator&lt;/b&gt;로 한번 감싸는 과정을 거치는 차이가 있다.&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;OCP와 DIP원칙을 지키고 있고, 추가적으로 메일을 보내는 상황이 생기더라도 응집도가 높기 때문에 유지보수하기 용이한 코드를 작성할 수 있었다.&lt;/b&gt;&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;span style=&quot;color: #000000;&quot;&gt;새로운 메일을 보내는 상황이 생기면 &lt;b&gt;MailEvent&lt;/b&gt;에서 정적 팩토리 메서드를 추가적으로 생성하고, &lt;b&gt;MailInfo&lt;/b&gt;를 override해서 새로운 &lt;b&gt;Concrete Product&lt;/b&gt;를 생성해주고 &lt;b&gt;MailService&lt;/b&gt;의&lt;/span&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;sendMail()&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;은 재사용 할 수 있기 때문이다.&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;span style=&quot;color: #000000;&quot;&gt;현재 프로젝트를 다시 보니 좀 더 리팩토링 할 상황들이 보이긴한다  &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;b&gt;⭐️ 관련 PR&lt;/b&gt;: &lt;a href=&quot;https://github.com/Space-Club/Backend/pull/300&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Space-Club/Backend/pull/300&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706947442472&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;feat: 스페이스 클럽 메일 시스템 구현  by juno-junho &amp;middot; Pull Request #300 &amp;middot; Space-Club/Backend&quot; data-og-description=&quot; ️ 작업 내용 resource 아래의 html파일로 관리 하던 방식에서 DB에 html 파일 저장하는 방식으로 변경 factory method pattern을 통해 MailService 리펙토링 관리자가 이벤트 상태 변경 시 다음과 같은 메일 &quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Space-Club/Backend/pull/300&quot; data-og-url=&quot;https://github.com/Space-Club/Backend/pull/300&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fb2DN/hyVb83Be3n/Q09WPRd4t7uJ8en7nYgp5K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Space-Club/Backend/pull/300&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Space-Club/Backend/pull/300&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fb2DN/hyVb83Be3n/Q09WPRd4t7uJ8en7nYgp5K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;feat: 스페이스 클럽 메일 시스템 구현 by juno-junho &amp;middot; Pull Request #300 &amp;middot; Space-Club/Backend&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; ️ 작업 내용 resource 아래의 html파일로 관리 하던 방식에서 DB에 html 파일 저장하는 방식으로 변경 factory method pattern을 통해 MailService 리펙토링 관리자가 이벤트 상태 변경 시 다음과 같은 메일&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p 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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; &amp;nbsp; 템플릿 메서드 패턴(Template Method Pattern)과 팩토리 메서드 패턴(Factory Method Pattern)의 차이&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;템플릿&amp;nbsp; 메서드 패턴에 대한 간단한 설명&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;템플릿 메서드 패턴이란 부모 클래스에서 알고리즘 골격을 정의하고, 해당 알고리즘 구조를 변경하지 않고 자식 클래스들이 알고리즘의 특정 단계를 재정의(override)할 수 있도록 하는 패턴이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6pfoT/btsEjEL8uu3/dgOq6qa8EMLYgEJlMbeVN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6pfoT/btsEjEL8uu3/dgOq6qa8EMLYgEJlMbeVN0/img.png&quot; data-alt=&quot;출처: https://refactoring.guru/ko/design-patterns/template-method&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6pfoT/btsEjEL8uu3/dgOq6qa8EMLYgEJlMbeVN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6pfoT%2FbtsEjEL8uu3%2FdgOq6qa8EMLYgEJlMbeVN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;435&quot; height=&quot;469&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://refactoring.guru/ko/design-patterns/template-method&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;상위 추상 클래스의 템플릿 메서드에서 하위 클래스가 재정의(override)한 메서드를 호출하는 패턴을 말한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;rarr; 팩토리 메서드 패턴은 템플릿 메서드 패턴의 특수화라고 생각 할 수 있다. &lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;대규모&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;템&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;플&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;릿&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;메&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;서&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;드&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;의 한 단계의 역할을&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;팩&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;토&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;리&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;메&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;서&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;드&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;가 할 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;✨&amp;nbsp; &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2️⃣ &lt;/b&gt;&lt;/span&gt;전략 패턴(Strategy Pattern) 적용 사례&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;  &amp;nbsp;&lt;/b&gt;전략 패턴에 대한 간단한 설명&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;전략 패턴은 디자인 패턴의 꽃&lt;/b&gt;이라고 불릴만큼 자주 사용하는 패턴이라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;전략 패턴&lt;/b&gt;은&lt;b&gt; 런타임에 전략을 선택해 객체 동작을 동적으로 바뀌도록 할 수 있는 디자인 패턴&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjtdJz/btsEpqrpPLR/CI0JdvUeDm6nqybUa5oQt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjtdJz/btsEpqrpPLR/CI0JdvUeDm6nqybUa5oQt1/img.png&quot; data-alt=&quot;출처: https://refactoring.guru/ko/design-patterns/strategy&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjtdJz/btsEpqrpPLR/CI0JdvUeDm6nqybUa5oQt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjtdJz%2FbtsEpqrpPLR%2FCI0JdvUeDm6nqybUa5oQt1%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;1734&quot; height=&quot;904&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://refactoring.guru/ko/design-patterns/strategy&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위에서 볼 수 있듯, &lt;b&gt;전략은&lt;/b&gt; &lt;b&gt;interface&lt;/b&gt;로 정의되어 있고, 구체적인 &lt;b&gt;ConcreteStrategy들은 각각 인터페이스를 구현&lt;/b&gt;하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 &lt;b&gt;Context는 특정 동작을 실행할 때 마다 해당 동작과 연결된 &lt;/b&gt;&lt;b&gt;ConcreteStrategy의 메서드를 호출&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Client는 &lt;/b&gt;&lt;b&gt;ConcreteStrategy 특정 전략 객체를 컨텍스트에 전달&lt;/b&gt; 함으로서 전략을 등록하거나 변경해 전략 알고리즘을 실행한 결과를 얻는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt; &amp;nbsp;&lt;/b&gt;언제 전략 패턴을 사용했는가?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때까지 코딩을 하면서 알게 모르게 많이 전략 패턴을 사용해 왔을 거라고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 기억에 남는 전략 패턴 사용 예시가 있는데, 작년 NEXTSTEP 교육기관에서 진행하는 &lt;b&gt;TDD, 클린 코드 with Java 16기 과정&lt;/b&gt;을 진행할 때였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그때 &lt;b&gt;레이싱 게임 미션&lt;/b&gt;을 진행하면서 전략 패턴에 대해서 처음 접하고 학습해 미션에 적용했는데, 구현해야할 상황은 다음과 같았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 자동차 이름 리스트와 시도 횟수를 입력 받는다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 시도 횟수만큼 계산을 하는데, 0부터 9까지의 난수를 생성해 해당 값이 4이상이면 한칸 전진, 4미만이면 전진하지 못한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 해당 횟수만큼 게임을 진행 후, 가장 많이 전진 한 자동차 이름 (여러대일 경우 자동차 리스트)의 이름을 출력하는 것이였다.&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 style=&quot;color: #000000;&quot;&gt;이 때 여러 Car라는 도메인 클래스를 만들고, Car 클래스 안에서 전진 할 수 있는지 없는지를 판단해 자동차 위치 값을 증가 시켜야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706949567530&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Car {
    private final Position position;
    private final CarName carName;

    public static final int MOVABLE_LOWER_BOUND = 4;
    
    public static boolean isMovable() {
        Random random = new Random();
        int value = random.nextInt(10);
        return value &amp;gt;= MOVABLE_LOWER_BOUND;
    }
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서&lt;/span&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;isMovable()&lt;/b&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메서드 내부에서 랜덤 값을 생성해 4보다 큰지 안큰지 확인을 해줬는데, 이렇게 코드를 작성하는 것에 대한 문제점으로 해당 메서드를 테스트 하기 힘들어 진다는 문제가 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&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 style=&quot;color: #000000;&quot;&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 style=&quot;color: #000000;&quot;&gt;&lt;b&gt;따라서 나는 전략 패턴을 사용해 외부에서 랜덤 값을 주입해 줌(DI)으로서 테스트 하기 쉬운 코드로 Car 도메인 객체를 변환할 수 있었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706949832514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@FunctionalInterface
public interface NumberGenerator {
    int generate();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1706949851311&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RandomNumberGenerator implements NumberGenerator{

    private static final Random random = new Random();
    public static final int RANDOM_BOUND_NUMBER = 10;

    @Override
    public int generate() {
        return random.nextInt(RANDOM_BOUND_NUMBER);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1706949862833&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Car {
    private final Position position;
    private final CarName carName;

    public static final int MOVABLE_LOWER_BOUND = 4;
    
    public static boolean isMovable(NumberGenerator numberGenerator) {
        int value = numberGenerator.generate();
        return value &amp;gt;= MOVABLE_LOWER_BOUND;
    }
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;그리고 게임을 진행하는 외부 RacingCarGame이라는 객체 안에서 Car를 전진시켜 주었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706949955884&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RacingCarGame {

   private final NumberGenerator numberGenerator;
   ...
   
   private void execute(Car car) {
        if (Car.isMovable(numberGenerator)) {
            car.move();
        }
        OutputView.printCarNameAndStatus(car);
    }
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;그리고 NumberGenerator에 대한 구현체인 RandomNumberGenerator는 외부 Application에서 주입해 주었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706950013483&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Application {
    public static void main(String[] args) {
        RacingCarGame racingCarGame = new RacingCarGame(new RandomNumberGenerator());
        racingCarGame.run();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 전략 패턴의 구조로 돌아가 내 구조를 설명하자면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;NumberGenerator&lt;/b&gt;는 &lt;b&gt;Strategy 인터페이스&lt;/b&gt;, &lt;b&gt;RandomNumberGenerator&lt;/b&gt;는 &lt;b&gt;ConcreteStrategy&lt;/b&gt;가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구현체를 주입해주는 &lt;b&gt;Client&lt;/b&gt;는 &lt;b&gt;Application&lt;/b&gt;이, &lt;b&gt;Context&lt;/b&gt;는 &lt;b&gt;RacingCarGame&lt;/b&gt;이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이렇게 전략 패턴을 사용해 strategy가 확장가능한 OCP 원칙를 지키며, DIP 원칙을 통해 구체적인 것이 아닌 인터페이스에 의존해 동적으로 strategy가 주입될 수 있었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;테스트 하기 힘든 strategy를 외부에서 주입해 주어 관계를 끊어냄으로서 도메인 객체에 대한 테스트를 용이하게 만들어 주었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;⭐️ &lt;/b&gt;관련 레포지토리 링크 :&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://github.com/juno-junho/java-racingcar/blob/step5/src/main/java/study/racingcar/strategy/RandomNumberGenerator.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/juno-junho/java-racingcar/blob/step5/src/main/java/study/racingcar/strategy/RandomNumberGenerator.java&lt;/a&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  전략 패턴(Strategy Pattern)과 템플릿 콜백 패턴(Template Callback Pattern) 차이&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿 콜백 패턴은 전략 패턴의 변형된 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;Client&lt;/b&gt; 역할을 하는 &lt;b&gt;Application&lt;/b&gt;이 &lt;b&gt;전략 객체인 RandomNumberGenerator&lt;/b&gt;를&lt;b&gt; Context인 RacingCarGame 객체&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;Context가 Template&lt;/b&gt;이 되고, &lt;b&gt;Strategy를 RandomNumberGenerator 처럼 만들어 두는 것이 아닌 익명 클래스나 람다로 정의해 client에서 호출 시 바로 정의해 주는 패턴&lt;/b&gt;을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 위 레이싱 게임의 코드를 템플릿 콜백 패턴으로 변경해 보자면, 아래와 같이 변경할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1706964451776&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Application {
    public static void main(String[] args) {
    // 익명 클래스 사용
        RacingCarGame racingCarGame = new RacingCarGame(
                new NumberGeneratePolicy() {
                    @Override
                    public int generate() {
                        return new Random.nextInt(10);
                    }
                });
        racingCarGame.run();
    }
}

public class Application {
    public static void main(String[] args) {
    // 람다식 사용
        RacingCarGame racingCarGame = new RacingCarGame(() -&amp;gt; new Random().nextInt(10));
        racingCarGame.run();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;단위 테스트시 NumberGenerator 인터페이스를 테스트 더블 dummy 객체로 만들어 테스트를 진행했는데,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이때도 사용했던 패턴이 지금 보니 템플릿 콜백 패턴였다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706965240623&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Nested
    @DisplayName(&quot;Movable 메서드 검증&quot;)
    class MoveableValidationTest {
        public static final int lowerBoundNum = Car.MOVABLE_LOWER_BOUND;

        @DisplayName(&quot;차가 움직일 수 없는 경우 false를 반환한다&quot;)
        @ParameterizedTest(name = &quot;{0}을 넣었을때 false를 반환한다&quot;)
        @ValueSource(ints = {lowerBoundNum - 1, lowerBoundNum - 2, lowerBoundNum - 3, lowerBoundNum - 4})
        void when_NumberUnderLowerBound_Expects_False(int num) {
            // given, when
            boolean actualResult = Car.isMovable(() -&amp;gt; num);

            // then
            assertThat(actualResult).isFalse();
        }

        @DisplayName(&quot;Moveable 할 경우 true를 반환한다.&quot;)
        @ParameterizedTest(name = &quot;{0}을 넣었을때 true를 반환한다&quot;)
        @ValueSource(ints = {lowerBoundNum, lowerBoundNum + 1, lowerBoundNum + 2, lowerBoundNum + 3})
        void when_NumberIsEqualToOrOverLowerBound_Expects_True(int num) {
            // given, when
            boolean actualResult = Car.isMovable(() -&amp;gt; num);

            // then
            assertThat(actualResult).isTrue();
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://github.com/juno-junho/java-racingcar/blob/step5/src/test/java/study/racingcar/domain/CarTest.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/juno-junho/java-racingcar/blob/step5/src/test/java/study/racingcar/domain/CarTest.java&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  끝맺음..&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;디자인 패턴에 대해서 프로그래머스 데브코스 과정동안 학습하고, 적용도 해보기도 해서 뿌듯했다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;실무에 가서는 디자인 패턴을 적재적소에 자유자재로 사용할 수 있도록 하고 싶다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&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 style=&quot;color: #000000;&quot;&gt;&lt;b&gt;추가적으로 꾸준히 학습과 적용이 필요해 보인다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://refactoring.guru/ko/design-patterns/what-is-pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://refactoring.guru/ko/design-patterns/what-is-pattern&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706940936209&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;디자인 패턴이란? 디자인 패턴은 소프트웨어 디자인 과정에서 자주 발생하는 문제들에 대한 전형적인 해결책입니다. 이는 코드에서 반복되는 디자인 문제들을 해결하기 위해 맞춤화할 수 있는 &quot; data-og-host=&quot;refactoring.guru&quot; data-og-source-url=&quot;https://refactoring.guru/ko/design-patterns/what-is-pattern&quot; data-og-url=&quot;https://refactoring.guru/ko/design-patterns/what-is-pattern&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b6sZOX/hyVb8P2O4t/2pZVrGgkLFhOFalOGsN801/img.png?width=200&amp;amp;height=242&amp;amp;face=0_0_200_242&quot;&gt;&lt;a href=&quot;https://refactoring.guru/ko/design-patterns/what-is-pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://refactoring.guru/ko/design-patterns/what-is-pattern&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b6sZOX/hyVb8P2O4t/2pZVrGgkLFhOFalOGsN801/img.png?width=200&amp;amp;height=242&amp;amp;face=0_0_200_242');&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;refactoring.guru&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://product.kyobobook.co.kr/detail/S000001628116&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://product.kyobobook.co.kr/detail/S000001628116&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706943069337&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;스프링 입문을 위한 자바 객체 지향의 원리와 이해 | 이 책은 자바에서 스프링으로 나아가기 위한 연결 고리를 제공한다.&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000001628116&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000001628116&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/clBF0P/hyVbZ6DReN/jNVRKkkjsH8kvX3wXPxpXK/img.jpg?width=458&amp;amp;height=572&amp;amp;face=0_0_458_572,https://scrap.kakaocdn.net/dn/xDUvV/hyVf0Jy7Vc/i0htoUY0b0AKeuVonADXA1/img.jpg?width=458&amp;amp;height=572&amp;amp;face=0_0_458_572,https://scrap.kakaocdn.net/dn/rGVAv/hyVb7p2S7P/mjdSAxkuoB7OOqQTAZQGw0/img.png?width=335&amp;amp;height=335&amp;amp;face=0_0_335_335&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001628116&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000001628116&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/clBF0P/hyVbZ6DReN/jNVRKkkjsH8kvX3wXPxpXK/img.jpg?width=458&amp;amp;height=572&amp;amp;face=0_0_458_572,https://scrap.kakaocdn.net/dn/xDUvV/hyVf0Jy7Vc/i0htoUY0b0AKeuVonADXA1/img.jpg?width=458&amp;amp;height=572&amp;amp;face=0_0_458_572,https://scrap.kakaocdn.net/dn/rGVAv/hyVb7p2S7P/mjdSAxkuoB7OOqQTAZQGw0/img.png?width=335&amp;amp;height=335&amp;amp;face=0_0_335_335');&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;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CFactory-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CFactory-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706948272074&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;  팩토리 메서드(Factory Method) 패턴 - 완벽 마스터하기&quot; data-og-description=&quot;Factory Method Pattern 팩토리 메소드 패턴은 객체 생성을 공장(Factory) 클래스로 캡슐화 처리하여 대신 생성하게 하는 생성 디자인 패턴이다. 즉, 클라이언트에서 직접 new 연산자를 통해 제품 객체를 &quot; data-og-host=&quot;inpa.tistory.com&quot; data-og-source-url=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CFactory-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot; data-og-url=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CFactory-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cL6q8M/hyVca79fZC/0Dxy3kDaywSpkduqkSqKbk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/bMkg3V/hyVf6wfZGl/i7Nr77qpoNE9cs8lBA2kUK/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/bo8xUA/hyVcd4TtAq/ovC1oPQXwD3jDBxlxBMXFK/img.png?width=1221&amp;amp;height=715&amp;amp;face=0_0_1221_715&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CFactory-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CFactory-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%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/cL6q8M/hyVca79fZC/0Dxy3kDaywSpkduqkSqKbk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/bMkg3V/hyVf6wfZGl/i7Nr77qpoNE9cs8lBA2kUK/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/bo8xUA/hyVcd4TtAq/ovC1oPQXwD3jDBxlxBMXFK/img.png?width=1221&amp;amp;height=715&amp;amp;face=0_0_1221_715');&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;  팩토리 메서드(Factory Method) 패턴 - 완벽 마스터하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Factory Method Pattern 팩토리 메소드 패턴은 객체 생성을 공장(Factory) 클래스로 캡슐화 처리하여 대신 생성하게 하는 생성 디자인 패턴이다. 즉, 클라이언트에서 직접 new 연산자를 통해 제품 객체를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;inpa.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://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%84%EB%9E%B5Strategy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%84%EB%9E%B5Strategy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706948289781&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;  전략(Strategy) 패턴 - 완벽 마스터하기&quot; data-og-description=&quot;Strategy Pattern 전략 패턴은 실행(런타임) 중에 알고리즘 전략을 선택하여 객체 동작을 실시간으로 바뀌도록 할 수 있게 하는 행위 디자인 패턴 이다. 여기서 '전략'이란 일종의 알고리즘이 될 수 &quot; data-og-host=&quot;inpa.tistory.com&quot; data-og-source-url=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%84%EB%9E%B5Strategy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot; data-og-url=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%84%EB%9E%B5Strategy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ksWvJ/hyVf5c2JYm/TJTC2K1DrGlWVYl07TZf2K/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/R6wL1/hyVb0qUBHt/a6fFb2kcqxkYhk27KSPfd1/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/buyc91/hyVb9BomRR/EOpKONFbTIuGzi6B8Bjq8K/img.png?width=1256&amp;amp;height=704&amp;amp;face=0_0_1256_704&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%84%EB%9E%B5Strategy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%84%EB%9E%B5Strategy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%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/ksWvJ/hyVf5c2JYm/TJTC2K1DrGlWVYl07TZf2K/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/R6wL1/hyVb0qUBHt/a6fFb2kcqxkYhk27KSPfd1/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/buyc91/hyVb9BomRR/EOpKONFbTIuGzi6B8Bjq8K/img.png?width=1256&amp;amp;height=704&amp;amp;face=0_0_1256_704');&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;  전략(Strategy) 패턴 - 완벽 마스터하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Strategy Pattern 전략 패턴은 실행(런타임) 중에 알고리즘 전략을 선택하여 객체 동작을 실시간으로 바뀌도록 할 수 있게 하는 행위 디자인 패턴 이다. 여기서 '전략'이란 일종의 알고리즘이 될 수&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;inpa.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-Template-Callback-%EB%B3%80%ED%98%95-%ED%8C%A8%ED%84%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-Template-Callback-%EB%B3%80%ED%98%95-%ED%8C%A8%ED%84%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706965364439&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;  Template Callback 디자인 패턴&quot; data-og-description=&quot;Template Callback Pattern 탬플릿 콜백 패턴(Template Callback Pattern)은 스프링 프레임워크에서 DI(Dependency injection) 의존성 주입에서 사용하는 특별한 전략 패턴이다. 스프링의 JdbcTemplate, RestTemplate, Transaction&quot; data-og-host=&quot;inpa.tistory.com&quot; data-og-source-url=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-Template-Callback-%EB%B3%80%ED%98%95-%ED%8C%A8%ED%84%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0&quot; data-og-url=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-Template-Callback-%EB%B3%80%ED%98%95-%ED%8C%A8%ED%84%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgcxGw/hyVce2QJhM/Y1eXzX1ZV92JzTYKuXWyL1/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bQO6Kh/hyVb9IezT7/a35N8N84iqrkhgkX1OdVsK/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/YRrrJ/hyVb4z68UJ/5uS7AWv06mBBCdArwcsG6k/img.png?width=1216&amp;amp;height=547&amp;amp;face=0_0_1216_547&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-Template-Callback-%EB%B3%80%ED%98%95-%ED%8C%A8%ED%84%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-Template-Callback-%EB%B3%80%ED%98%95-%ED%8C%A8%ED%84%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgcxGw/hyVce2QJhM/Y1eXzX1ZV92JzTYKuXWyL1/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bQO6Kh/hyVb9IezT7/a35N8N84iqrkhgkX1OdVsK/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/YRrrJ/hyVb4z68UJ/5uS7AWv06mBBCdArwcsG6k/img.png?width=1216&amp;amp;height=547&amp;amp;face=0_0_1216_547');&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;  Template Callback 디자인 패턴&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Template Callback Pattern 탬플릿 콜백 패턴(Template Callback Pattern)은 스프링 프레임워크에서 DI(Dependency injection) 의존성 주입에서 사용하는 특별한 전략 패턴이다. 스프링의 JdbcTemplate, RestTemplate, Transaction&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;inpa.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;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming</category>
      <category>디자인 패턴</category>
      <category>전략 패턴</category>
      <category>템플릿 메서드</category>
      <category>템플릿 콜백</category>
      <category>팩토리 메서드 패턴</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/108</guid>
      <comments>https://juno-juno.tistory.com/108#entry108comment</comments>
      <pubDate>Sat, 3 Feb 2024 18:16:30 +0900</pubDate>
    </item>
    <item>
      <title>  SQL 성능 튜닝 - (2) 실행계획을 분석해 성능 튜닝을 해보자!</title>
      <link>https://juno-juno.tistory.com/107</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;지난 1편&quot; href=&quot;https://juno-juno.tistory.com/106&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 1편&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에서 성능튜닝을 왜 해야하는지와 실행계획을 분석하는 방법에 대해서 살펴보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행계획의 정보를 보면서 어떤 것을 통해 성능을 개선할 수 있는지 약간은 감이 왔을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 이 분석한 실행계획을 바탕으로 정말 성능 개선을 해보자!&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;❓ 그럼 어떻게 실행되고 있는 SQL이 좋고 나쁜지 구분할 수 있을 까?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;2083&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHg8YR/btsEcqGI2MZ/QcDuBPvsukNKAEubxp1MsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHg8YR/btsEcqGI2MZ/QcDuBPvsukNKAEubxp1MsK/img.png&quot; data-alt=&quot;출처: 업무에 바로 쓰는 SQL 튜닝 책&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHg8YR/btsEcqGI2MZ/QcDuBPvsukNKAEubxp1MsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHg8YR%2FbtsEcqGI2MZ%2FQcDuBPvsukNKAEubxp1MsK%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;584&quot; height=&quot;402&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;2083&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: 업무에 바로 쓰는 SQL 튜닝 책&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;명확히 실행 계획 보고 성능 개선이 필요한지 선 그어 구분하긴 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;하지만 각자 상황에 맞게 검토 대상 추출이 필요하다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;자세한 내용은 노션을 참고하자.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 실행계획의 특정 값에 대해서 설명하자면, 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. DEPENDENT SUBQUERY, DEPENDENT UNION (select_type)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;union, union all을 사용하는 서브쿼리가 메인 테이블의 영향을 받는 경우로, union으로 연결된 단위 쿼리들 중 &lt;b&gt;처음으로 작성한 단위쿼리(DEPENDENT SUBQUERY)&lt;/b&gt;, &lt;b&gt;두 번째 단위 쿼리(DEPENDENT UNION)&lt;/b&gt;를 말한다. 메인 테이블로 부터 값을 &lt;b&gt;의존적으로 하나씩 공급받는 구조이기에 성능적으로 불리해 튜닝의 대상이 된다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;2. uncacheable subquery&lt;b&gt;&amp;nbsp;(select_type)&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메모리에 상주하여 재사용되어야 할 서브쿼리가 재사용 되지 못할 때 출력되는 유형이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3. index, all (type)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스 풀 스캔인지, 테이블 풀 스캔인지를 알 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;4. using filesort, using temporary (extra)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;using filesort는 &lt;b&gt;정렬이 필요한 데이터를 메모리에 올리고 정렬 작업을 수행&lt;/b&gt;한다는 의미이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 정렬된 인덱스를 사용하면 추가적인 정렬 작업이 필요 없지만 &lt;b&gt;인덱스를 사용하지 못할때는 정렬을 위해 메모리 영역에 데이터를 올리게 되어 성능 튜닝의 대상&lt;/b&gt;이다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;using temporary는 &lt;b&gt;데이터 중간 결과 저장을 위해 임시테이블 생성하겠다&lt;/b&gt;는 의미다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;❗️ 자, 그럼 진짜로 SQL 튜닝을 한번 해보자!&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;실습을 통한 자세한 성능 튜닝을 통한 성능 개선기는 노션에 잘 정리되어 있으니 아래 4장과 5장을 참고하자.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;4장 노션 링크&quot; href=&quot;https://kaput-trombone-343.notion.site/4-SQL-0b3943c712e247dfb15fe70007b48c5d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;4장 노션 링크&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;5장 노션 링크&quot; href=&quot;https://kaput-trombone-343.notion.site/5-SQL-1a425ac38d704cd2a6cc40abf1ee2e5a?pvs=74&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;5장 노션 링크&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; SQL 튜닝을 진행하면서 느낀점 정리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;위 노션을 보면 5장의 마지막 인덱스 생성과 콜레이션 부분 정도를 제외하고, SQL을 하나하나 작성해가며 설정했었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;그러면서 학습한 내용을 종합적으로 스스로 정리해 보았다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;substring(), length() &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;와 같은 &lt;b&gt;함수로 가공해서 사용하면 인덱스를 사용하지 않게 한다.&lt;/b&gt; 따라서 특정 인덱스를 타게 하고 싶은지 안타게 하고 싶은지 우리가 조절 할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;extra 컬럼에&amp;nbsp;&lt;/span&gt;&lt;b&gt;using temporary&lt;/b&gt;가 존재하면  가상 테이블을 만들지 않고 쿼리를 실행할 수는 없는지 고려해보자&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;묵시적 형변환&lt;/b&gt;을 사용하면 &lt;u&gt;&lt;b&gt;인덱스가 잘 활용되지 못한다.&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;rows&lt;/b&gt;수를 통해 스토리지 엔진이 얼마만큼의 row에 접근했는지, &lt;b&gt;filtered&lt;/b&gt; 통해서 몇퍼센트가 MySQL 엔진을 통해서 필터링 되고 남았는지를 알 수있기에 &lt;b&gt;filtered 컬럼의 수가 낮을 때는 성능 개선을 고려해보자&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;DISTINCT&lt;/b&gt;&lt;/span&gt; 키워드를 쿼리에 작성하는 것 만으로도 정렬 작업이 포함되기에&lt;b&gt; 꼭 사용해야할 곳에만 사용하자.&lt;/b&gt; (따라서 임시테이블을 생성한다)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 정렬된 기본키, 인덱스 활용하면 정렬 작업 부담 덜 수 있다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;UNION&lt;/b&gt;&lt;/span&gt; 키워드는 중복 제거를 위해 가상테이블을 사용하기 때문에 (메모리에 공간이 없으면 디스크에 임시파일을 생성한다) 사용을 지양하자. &amp;rarr; 사용할 일이 있으면 &lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;UNION ALL&lt;/span&gt;을 사용하자&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;다중 컬럼 인덱스가 있을때는 순서도 중요하다. &lt;b&gt;grouping&lt;/b&gt; 할때 설정된&lt;b&gt; 다중 컬럼 인덱스의 컬럼 순서대로 작성해야 가상테이블을 만들지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;엉뚱한 인덱스를 탄다면 &lt;b&gt;힌트&lt;/b&gt;를 통해 인덱스 설정을 해주거나, 데이터 범위를 줄일 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;엉뚱한 인덱스를 타는지에 대한 방법은&lt;b&gt; 전체 row 대비 조건에 해당하는 row가 몇건인지 비교해 비율이 더 적은 쪽의 인덱스를 사용&lt;/b&gt;하는게 낫다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;인덱스를 사용하는 것보다 &lt;b&gt;테이블 풀 스캔&lt;/b&gt;을 사용하는것이 나을 수도 있다. &lt;b&gt;(Random access가 발생 하기 때문)&lt;/b&gt;. 따라서 카디널리티가 낮은 컬럼에 인덱스를 사용한 것은 아닌지, 따라서 전체 데이터 대비 검색 하는 데이터 비율이 상당히 높은 것은 아닌지 따져봐야 한다.&lt;br /&gt;또한, 조회하려는 &lt;b&gt;데이터가 전체 대비 소량의 데이터면 인덱스를 사용하는것이 낫고, 대량의 데이터면 테이블 풀 스캔이 더 나을 수도 있다&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;driven 테이블에서 대량의 데이터에 대해 랜덤 엑세스하면 비효율적이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;driven / driving table 정할 시에는&lt;b&gt; driven 테이블 조회 하는게 더 비싸기에 인덱스가 없는 테이블을 driving으로 정하는게 효율적이다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;driving / driven 테이블을 지정하고 싶을때는 &lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;STRAIGHT_JOIN&lt;/span&gt; 키워드&lt;/b&gt;&lt;/u&gt;를 통해 순서를 쿼리를 통해 설정할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;&lt;b&gt;서브쿼리보다 조인으로 수행하는게 성능 측면에서 유리할 가능성이 높다.&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;값이 있는 여부만 확인이 필요하다면 조인하지 말고&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; EXIST&lt;/span&gt; 키워드 사용을 고려해 봐라&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;페이징 시 데이터를 전부 가져와 페이징 하는게 아니라&lt;b&gt; 페이징 후 조인하는 것을 고려하자&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;update시 인덱스가 존재한다면 update 시간이 오래 걸릴 수 있기에 &lt;b&gt;사용하지 않는 인덱스는 삭제를 고려하자&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대소문자 구분해 조회할 경우&lt;b&gt; 콜레이션 변경&lt;/b&gt;을 고려하자&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;범위 검색의 경우 범위 방식 파티셔닝&lt;/b&gt;을 고려하자&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  SQL 학습을 진행 한 후기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;진짜 SQL 성능 튜닝을 하면서 내가 작성하는 SQL에 대한 검증, 즉 내가 SQL을 제대로 잘 작성하고 있는가? 에 대한 의문점이 해소되었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;REAL MySQL 같은 더 심화적인 책으로 DB에 대한 지속적인 학습은 필요하지만 그래도 성능 튜닝에 첫 걸음을 디딘 느낌이다!&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;https://juno-juno.tistory.com/106&quot; href=&quot;https://juno-juno.tistory.com/106&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;  SQL 성능 튜닝 - (1) 왜 SQL 튜닝을 해야하지? 어떻게 하지? 링크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;https://juno-juno.tistory.com/107&quot; href=&quot;https://juno-juno.tistory.com/107&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;  SQL 성능 튜닝 - (2) 실행계획을 분석해 성능 튜닝을 해보자! 링크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #009a87;&quot; title=&quot;노션링크&quot; href=&quot;https://kaput-trombone-343.notion.site/SQL-359b8db618a547fc8a6b25839eb14d2b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;'업무에 바로 쓰는 SQL 튜닝' 책을 읽고 정리한 노션 링크&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1706688199286&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/onIQE/hyVb6b9bkD/MowWt149ekkRif34oT6Qz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bVVBvd/hyVb3OgoHn/pb9fVmNm3aytxhu6Aop9d1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot; data-og-url=&quot;https://kaput-trombone-343.notion.site/359b8db618a547fc8a6b25839eb14d2b&quot; data-og-source-url=&quot;https://kaput-trombone-343.notion.site/SQL-359b8db618a547fc8a6b25839eb14d2b&quot; data-og-host=&quot;kaput-trombone-343.notion.site&quot; data-og-description=&quot;Built with Notion, the all-in-one connected workspace with publishing capabilities.&quot; data-og-title=&quot;업무에 바로쓰는 SQL 튜닝 | Notion&quot; data-og-type=&quot;article&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kaput-trombone-343.notion.site/SQL-359b8db618a547fc8a6b25839eb14d2b&quot; data-source-url=&quot;https://kaput-trombone-343.notion.site/SQL-359b8db618a547fc8a6b25839eb14d2b&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/onIQE/hyVb6b9bkD/MowWt149ekkRif34oT6Qz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bVVBvd/hyVb3OgoHn/pb9fVmNm3aytxhu6Aop9d1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;업무에 바로쓰는 SQL 튜닝 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;Built with Notion, the all-in-one connected workspace with publishing capabilities.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;kaput-trombone-343.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot;&gt;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706688199287&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baMDxM/hyVb89Qzf4/9bMN33EKoRkFTupBL3XeP0/img.jpg?width=380&amp;amp;height=488&amp;amp;face=0_0_380_488,https://scrap.kakaocdn.net/dn/bjVf3i/hyVcas3xGk/Xp6Pkpnxmsd0c0koIN8r7k/img.jpg?width=380&amp;amp;height=488&amp;amp;face=0_0_380_488,https://scrap.kakaocdn.net/dn/benQ4r/hyVb5kZOyl/6a6HjXfuFDk4Mvi5cr1XGK/img.jpg?width=800&amp;amp;height=1027&amp;amp;face=0_0_800_1027&quot; data-og-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot; data-og-source-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot; data-og-host=&quot;ebook-product.kyobobook.co.kr&quot; data-og-description=&quot;최적의 성능을 위한 MySQL/MariaDB 쿼리 작성과 튜닝 실습, 이 책의 구성 1장_ MySQL과 MariaDB 개요 MySQL과 MariaDB의 배경과 시장점유율 현황을 알아보고 상용 DBMS와의 차이점, 오픈소스 DBMS인 MySQL과 MariaDB&quot; data-og-title=&quot;업무에 바로 쓰는 SQL 튜닝 | 양바른 | 한빛미디어- 교보ebook&quot; data-og-type=&quot;website&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot; data-source-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baMDxM/hyVb89Qzf4/9bMN33EKoRkFTupBL3XeP0/img.jpg?width=380&amp;amp;height=488&amp;amp;face=0_0_380_488,https://scrap.kakaocdn.net/dn/bjVf3i/hyVcas3xGk/Xp6Pkpnxmsd0c0koIN8r7k/img.jpg?width=380&amp;amp;height=488&amp;amp;face=0_0_380_488,https://scrap.kakaocdn.net/dn/benQ4r/hyVb5kZOyl/6a6HjXfuFDk4Mvi5cr1XGK/img.jpg?width=800&amp;amp;height=1027&amp;amp;face=0_0_800_1027');&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;업무에 바로 쓰는 SQL 튜닝 | 양바른 | 한빛미디어- 교보ebook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;최적의 성능을 위한 MySQL/MariaDB 쿼리 작성과 튜닝 실습, 이 책의 구성 1장_ MySQL과 MariaDB 개요 MySQL과 MariaDB의 배경과 시장점유율 현황을 알아보고 상용 DBMS와의 차이점, 오픈소스 DBMS인 MySQL과 MariaDB&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;ebook-product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming/Database</category>
      <category>MYSQL</category>
      <category>SQL</category>
      <category>성능튜닝</category>
      <category>실행계획</category>
      <category>인덱스</category>
      <category>카디널리티</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/107</guid>
      <comments>https://juno-juno.tistory.com/107#entry107comment</comments>
      <pubDate>Wed, 31 Jan 2024 19:31:52 +0900</pubDate>
    </item>
    <item>
      <title>  SQL 성능 튜닝 - (1) 왜 SQL 튜닝을 해야하지? 어떻게 하지?</title>
      <link>https://juno-juno.tistory.com/106</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나는 이번 &lt;b&gt;Space Club 프로젝트&lt;/b&gt;를 진행하면서,&lt;b&gt; SQL문을 작성하는 방식에 대해서 고민&lt;/b&gt;했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQL 쿼리를 통해 같은 데이터  다루더라도 작성하는 방법은 여러 방법이 있는데, 내부적으로 DBMS 안에서 어떻게 작동되는지, 어떻게 SQL을 작성하는 것이 효율적인 방법인지 궁금했다.&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 style=&quot;color: #000000;&quot;&gt;일단 &lt;b&gt;효율&lt;/b&gt;을 논하기 전에, 내가 프로젝트에서 썼던&lt;b&gt; MySQL 구조와 작동 방식&lt;/b&gt;부터 학습하는게 필수였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;구조와 작동 방식보다도, 일단 왜 MySQL을 사용했는지에 대한 이해가 필요했다.&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;  MySQL을 왜 프로젝트에서 사용했는가?&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터베이스를 다루는 DBMS로는 여러 종류가 존재한다. 그 중, 관계형 DB는&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 일반적으로 &lt;b&gt;가장 많이 사용하는 데이터베이스&lt;/b&gt;이고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 테이블 분리를 통해 &lt;b&gt;중복값에 따른 성능 저하 방지 및 유지보수 하기 용이하다는 장점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 두 가지 이유 때문에&lt;b&gt; 관계형 DB를 사용했고, opensource이기에 무료이며, opensource 중에 점유율이 가장 높은 이유로 MySQL RDBMS를 사용했다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 MySQL은 Oracle과 비교했을때의 장점으로는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. MySQL에서는 대부분 &lt;b&gt;중첩 루프 조인 알고리즘(NL 조인)&lt;/b&gt;으로만 풀리며&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 필요한 DBMS를 설정해 사용할 수 있다. (스토리지 엔진이 Oracle에는 없다. 스토리지 엔진의 확장성이 뛰어나다 e.g) MyISAM,InnoDB)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 상대적으로 낮은 메모리 사용으로 저사양 PC에서도 손쉽게 설치 / 개발 할 수 있다.&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 style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이런 종합적인 것을 따져보았을 때, MySQL을 프로젝트에 도입하는것이 합리적이라는 결정을 내릴 수 있었다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ok7Pi/btsEaYDYpv5/tp7HTQjl7PfAk0E5knQDG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ok7Pi/btsEaYDYpv5/tp7HTQjl7PfAk0E5knQDG1/img.png&quot; data-alt=&quot;2024 DB 엔진 랭킹, 출처:&amp;amp;amp;nbsp;https://db-engines.com/en/ranking_trend&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ok7Pi/btsEaYDYpv5/tp7HTQjl7PfAk0E5knQDG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOk7Pi%2FbtsEaYDYpv5%2Ftp7HTQjl7PfAk0E5knQDG1%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;541&quot; height=&quot;300&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2024 DB 엔진 랭킹, 출처:&amp;amp;nbsp;https://db-engines.com/en/ranking_trend&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;⚙️ MySQL의 구조와 작동 방식에 대해서&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL은 크게 MySQL 엔진과 스토리지 엔진으로 나뉜다.&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 style=&quot;color: #000000;&quot;&gt;1. 먼저 우리가 SQL을 통해서 RDBMS인 MySQL에 요청을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 그러면 &lt;b&gt;MySQL엔진&lt;/b&gt;에서 먼저 SQL을 받아서&lt;b&gt; Parser를 통해 MySQL이 이해할 수 있는 최소단위로 구성요소를 분리하고 트리로 만들어 문법 오류를 검사한다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3. 그리고 preprocessor가 생성된 트리 결과를 토대로 이미 만들어진 테이블이나 뷰 등으로 구성되지는 않는지, 존재하지 않는 열 포함하지는 않는지, 조회권한이 없는 테이블 조회하는지 등 유효성 검사를 진행한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 이후, 사용자가 요청한 데이터를 빠르고 효율적으로 &lt;b&gt;optimizer&lt;/b&gt;가 전략적 계획인 &lt;b&gt;실행계획&lt;/b&gt;을 세운다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. 그리고 optimizer가 세운 계획을 토대로 &lt;b&gt;스토리지 엔진&lt;/b&gt;에 위치한 데이터까지 찾아간 뒤 해당 데이터를 MySQL 엔진으로 전달한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6. 그리고&lt;b&gt; MySQL 엔진&lt;/b&gt;은 전달된 데이터에서 &lt;b&gt;불필요한 부분을 필터링하고 필요한 연산을 수행&lt;/b&gt; 한 뒤, 사용자에게 최종 결과를 알려준다.&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1965&quot; data-origin-height=&quot;2059&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Px8nO/btsEd0tOoRg/d7v0bY7hC5QrIn8Y3gqHQk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Px8nO/btsEd0tOoRg/d7v0bY7hC5QrIn8Y3gqHQk/img.jpg&quot; data-alt=&quot;MySQL 수행 프로세스/ 출처: 아래 Reference 책&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Px8nO/btsEd0tOoRg/d7v0bY7hC5QrIn8Y3gqHQk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPx8nO%2FbtsEd0tOoRg%2Fd7v0bY7hC5QrIn8Y3gqHQk%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;386&quot; height=&quot;404&quot; data-origin-width=&quot;1965&quot; data-origin-height=&quot;2059&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MySQL 수행 프로세스/ 출처: 아래 Reference 책&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;즉 정리하자면, 스토리지 엔진은 optimizer가 세운 계획 대로 DB에 저장된 디스크나 메모리에서 필요한 데이터를 가져오는 역할을 한다!&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;그리고 그 데이터를 MySQL 엔진으로 보내준다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;MySQL 엔진은 SQL문 넘겨받아 검사를 진행하고 데이터를 어떻게 빠르게 찾아갈지 경로를 찾는 역할을 수행한다!&lt;/b&gt;&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이 구조에서 튜닝을 해야하는 이유가 나온다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;&amp;nbsp;  SQL 튜닝은 왜 알아야 할까?&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 MySQL 구조에서 볼 수 있듯, MySQL의 &lt;b&gt;optimizer&lt;/b&gt;는 &lt;b&gt;parser tree를 토대로 필요하지 않은 조건을 제거하거나 연산과정을 단순화&lt;/b&gt; 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 순서로 테이블에 접근할지, 인덱스를 사용할지, 사용한다면 어떤 인덱스 사용할지, 정렬할때 인덱스 사용할지 or 임시테이블 (temporary table) 사용할지와 같은&lt;b&gt; 실행계획 수립&lt;/b&gt;하는데, &lt;b&gt;이때 옵티마이저가 선택한 최적의 실행 계획이 최상의 실행 계획이 아닐 가능성이 존재한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;왜냐하면 실행계획 도출 할 수 있는 경우의 수가 너무 많으면? 그리고 연산 과정도 시간과 리소스 제한을 두고 실행 계획을 선정해야 하기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;따라서 이 optimizer의 모든 실행계획이 최적의 실행계획은 아닐수도 있기에 튜닝이 필요하다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;그리고 두 번째로, InnoDB와 같은 스토리지 엔진을 통해 데이터를 실행계획에 따라 읽어왔을 때, &lt;b&gt;MySQL 엔진에서 읽어온 데이터를 정렬 or 조인하고 불필요한 데이터는 필터링 처리하는 추가 작업을 한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;따라&lt;b&gt;서 MySQL 엔진 부하 줄이려면 스토리지 엔진에서 가져오는 데이터양 줄이는게 매우 중요하다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;  그럼 어떻게 SQL 튜닝을 해야 하는가?!&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;바로 Optimizer가 정한 실행계획을 참고해야한다.&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Optimizer의 실행계획을 확인하는 방법은&lt;/span&gt; &lt;span data-token-index=&quot;0&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; EXPLAIN, DESCRIBE, DESC 키워드 + SQL문;&lt;/span&gt;&lt;/b&gt; &lt;span style=&quot;color: #000000;&quot;&gt;을 실행하면된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어느 것도 결과는 같지만 보통&lt;/span&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;EXPLAIN &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;키워드를 사용한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgBCtN/btsEbG33qKi/JhUE4b5A7XEiMVKUitvUHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgBCtN/btsEbG33qKi/JhUE4b5A7XEiMVKUitvUHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgBCtN/btsEbG33qKi/JhUE4b5A7XEiMVKUitvUHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgBCtN%2FbtsEbG33qKi%2FJhUE4b5A7XEiMVKUitvUHK%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;1456&quot; height=&quot;203&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;그럼 위와 같이 &lt;span style=&quot;color: #006dd7;&quot;&gt;무섭게 생긴 표를 하나 보여준다.&lt;/span&gt;  &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;id, select_type, table.. 각각 무슨 뜻일까?&lt;/b&gt;&lt;/span&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;color: #000000;&quot;&gt;&lt;b&gt;  실행계획, 하나하나 분석해보자!&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. id&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;b&gt;실행 순서를 표시하는 숫자&lt;/b&gt;이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;b&gt;id 값이 작을 수록 먼저 수행된 것이고&amp;nbsp;id 값이 같으면 두 테이블의 조인이 이루어 진 것&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;rarr; driving table, driven table 파악이 가능하다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. select_type&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- SQL을 구성하는 &lt;b&gt;select 문의 유형&lt;/b&gt;을 출력하는 항목이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- FROM 절에 위치한 것인지, 서브쿼리인지, Union 절로 묶인 select문인지 등 정보를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;자세한건 내 노션을 참고하자&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3. table&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;-&lt;/b&gt; 테이블 명을 표시하는 항목이다. 서브쿼리나 임시테이블 만들어 별도 작업 수행할 때는 &amp;lt;subquery#&amp;gt;나 &amp;lt;derived#&amp;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;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;4. partitions&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 실행 계획의 부가 정보. 데이터가 저장된 논리적인 영역을 표시하는 단위이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;0&quot;&gt;전체 파티션 중 특정 파티션에 선택적으로 접근하는 것이 SQL 성능 측면에서 유리하다. &lt;b&gt;따라서 너무 많은 영역의 파티션에 접근하는 것으로 출력되면 파티션 정의를 튜닝해봐야한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;5. type&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;- &lt;/b&gt;테이블의 데이터를 어떻게 찾을지에 관한 정보를 제공하는 항목이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&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;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;6. possible_keys&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 옵티마이저가 SQL 문 최적화하고자 사용할 수 있는 인덱스 목록 출력한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; &amp;rarr; 실제 사용한 인덱스가 아닌 사용할 수 있는 후보군의 기본 키와 인덱스 목록만 보여주기에 SQL 튜닝 효용성 없다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;7. key&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;-&lt;/b&gt; 옵티마이저가 SQL 문 최적화하고자 사용한 PK(기본키) 또는 인덱스 명을 말한다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;어느 인덱스로 데이터 검색했는지 확인 가능하기에 비효율적인 인덱스 사용했거나 인덱스 사용 하지 않았으면 SQL 튜닝 대상이 된다. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;8. key_len&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&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;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;9. ref&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- reference의 약자로&amp;nbsp;테이블 조인 시 어떤 조건으로 해당 테이블에 엑세스 되었는지 알려주는 정보이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;10. rows&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;- SQL 문을 수행하고자 접근하는 데이터의 모든 행수를 말한다. 즉, 디스크에서 데이터 파일 읽고 메모리에서 처리해야 할 행 수를 예상하는 값.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #d44c47;&quot; data-token-index=&quot;0&quot;&gt;SQL 문의 최종 결과 건수와 비교해 rows 수가 크게 차이나면 불필요하게 MySQL 엔진까지 데이터 많이 가져온 것이기에 SQL 튜닝 대상이 된다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;11. filtered&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;0&quot;&gt;- SQL문을 통해 MySQL 엔진으로 가져온 데이터 대상으로 필터 조건에 따라&lt;b&gt; 어느 정도의 비율로 데이터 제거했는지 의미하는 항목&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 DB 엔진으로 100건 데이터 가져왔을때 이후 WHERE 사원번호 between 1 and 10 &amp;rarr; 10 건으로 필터링하면 10이 출력된다.&amp;nbsp;&lt;b&gt;(단위 %)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;12. extra&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- SQL문을 어떻게 수행할 것인지에 관한 추가 정보를 보여주는 항목이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 통해서 &lt;b&gt;임시테이블을 생성&lt;/b&gt;했는지 (Using temporary), &lt;b&gt;인덱스만 읽어서 SQL문을 처리&lt;/b&gt;했는지 (using index), 조인 시&lt;b&gt; join buffer를 사용했는지&lt;/b&gt;, (Using join buffer),&lt;b&gt; 필터 조건을 스토리지 엔진으로 전달해 필터링 작업을 MySQL 엔진 부하를 줄였는지&lt;/b&gt; (Using index condition), 조인 시 배치 키 엑세스 조인 방식을 사용했는지 (Using index condition (BKA)) 등이 있다.&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 style=&quot;color: #000000;&quot;&gt;&lt;b&gt;다음 2편에서는 이 실행계획을 토대로 어떻게 SQL문을 개선시킬 수 있는지에 대해서 알아보도록 하자!&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; title=&quot;https://juno-juno.tistory.com/106&quot; href=&quot;https://juno-juno.tistory.com/106&quot;&gt;  SQL 성능 튜닝 - (1) 왜 SQL 튜닝을 해야하지? 어떻게 하지? 링크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; title=&quot;https://juno-juno.tistory.com/107&quot; href=&quot;https://juno-juno.tistory.com/107&quot;&gt;  SQL 성능 튜닝 - (2) 실행계획을 분석해 성능 튜닝을 해보자! 링크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #009a87;&quot; href=&quot;https://kaput-trombone-343.notion.site/SQL-359b8db618a547fc8a6b25839eb14d2b&quot;&gt;'업무에 바로 쓰는 SQL 튜닝' 책을 읽고 정리한 노션 링크&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1706677713301&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;업무에 바로쓰는 SQL 튜닝 | Notion&quot; data-og-description=&quot;Built with Notion, the all-in-one connected workspace with publishing capabilities.&quot; data-og-host=&quot;kaput-trombone-343.notion.site&quot; data-og-source-url=&quot;https://kaput-trombone-343.notion.site/SQL-359b8db618a547fc8a6b25839eb14d2b&quot; data-og-url=&quot;https://kaput-trombone-343.notion.site/359b8db618a547fc8a6b25839eb14d2b&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/onIQE/hyVb6b9bkD/MowWt149ekkRif34oT6Qz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bVVBvd/hyVb3OgoHn/pb9fVmNm3aytxhu6Aop9d1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://kaput-trombone-343.notion.site/SQL-359b8db618a547fc8a6b25839eb14d2b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kaput-trombone-343.notion.site/SQL-359b8db618a547fc8a6b25839eb14d2b&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/onIQE/hyVb6b9bkD/MowWt149ekkRif34oT6Qz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bVVBvd/hyVb3OgoHn/pb9fVmNm3aytxhu6Aop9d1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;업무에 바로쓰는 SQL 튜닝 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Built with Notion, the all-in-one connected workspace with publishing capabilities.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kaput-trombone-343.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706677951528&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;업무에 바로 쓰는 SQL 튜닝 | 양바른 | 한빛미디어- 교보ebook&quot; data-og-description=&quot;최적의 성능을 위한 MySQL/MariaDB 쿼리 작성과 튜닝 실습, 이 책의 구성 1장_ MySQL과 MariaDB 개요 MySQL과 MariaDB의 배경과 시장점유율 현황을 알아보고 상용 DBMS와의 차이점, 오픈소스 DBMS인 MySQL과 MariaDB&quot; data-og-host=&quot;ebook-product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot; data-og-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baMDxM/hyVb89Qzf4/9bMN33EKoRkFTupBL3XeP0/img.jpg?width=380&amp;amp;height=488&amp;amp;face=0_0_380_488,https://scrap.kakaocdn.net/dn/bjVf3i/hyVcas3xGk/Xp6Pkpnxmsd0c0koIN8r7k/img.jpg?width=380&amp;amp;height=488&amp;amp;face=0_0_380_488,https://scrap.kakaocdn.net/dn/benQ4r/hyVb5kZOyl/6a6HjXfuFDk4Mvi5cr1XGK/img.jpg?width=800&amp;amp;height=1027&amp;amp;face=0_0_800_1027&quot;&gt;&lt;a href=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942550&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baMDxM/hyVb89Qzf4/9bMN33EKoRkFTupBL3XeP0/img.jpg?width=380&amp;amp;height=488&amp;amp;face=0_0_380_488,https://scrap.kakaocdn.net/dn/bjVf3i/hyVcas3xGk/Xp6Pkpnxmsd0c0koIN8r7k/img.jpg?width=380&amp;amp;height=488&amp;amp;face=0_0_380_488,https://scrap.kakaocdn.net/dn/benQ4r/hyVb5kZOyl/6a6HjXfuFDk4Mvi5cr1XGK/img.jpg?width=800&amp;amp;height=1027&amp;amp;face=0_0_800_1027');&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;업무에 바로 쓰는 SQL 튜닝 | 양바른 | 한빛미디어- 교보ebook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최적의 성능을 위한 MySQL/MariaDB 쿼리 작성과 튜닝 실습, 이 책의 구성 1장_ MySQL과 MariaDB 개요 MySQL과 MariaDB의 배경과 시장점유율 현황을 알아보고 상용 DBMS와의 차이점, 오픈소스 DBMS인 MySQL과 MariaDB&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ebook-product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming/Database</category>
      <category>MYSQL</category>
      <category>SQL 성능튜닝</category>
      <category>성능튜닝</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/106</guid>
      <comments>https://juno-juno.tistory.com/106#entry106comment</comments>
      <pubDate>Wed, 31 Jan 2024 17:01:14 +0900</pubDate>
    </item>
    <item>
      <title>  Repository 테스트시 auto_increment의 id 컬럼 의존성을 끊을수는 없을까?</title>
      <link>https://juno-juno.tistory.com/105</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Entity에서 key 생성 전략을 IDENTITY로 설정하면 auto_increment로 id가 null로 설정되어 DB에 INSERT이후, DBMS를 통해서 id 값을 받아 온다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데, 이 auto_increment에 의존해 테스트를 위와 같이 작성할 경우,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 테스트 마다 1부터 다시 id값을 생성하는게 아닌, 연속해서 id를 발행하기 때문에, auto_increment로 생성된 id에 의존해 테스트하는것에 문제가 생겼다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래 코드에서 보면, Template에는 id와 해당 template html을 저장한다. (아래는 template에 임의로 template 1,2를 넣어둔 것이다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 각 MailHistory는 어떤 template으로 보낼지 template이름을 가지고 있는데, tempate이름이 WELCOME이면 template id가 1, EVENT_STATUS_CHANGED이면 2로 설정된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootTest
@Transactional
class TemplateNameRepositoryTest {

    @Autowired
    private TemplateRepository templateRepository;
    @Autowired
    private MailTrackerRepository mailTrackerRepository;

    @BeforeEach
    void setUp() {
        Template template1 = Template.builder()
                .template(&quot;template 1&quot;)
                .build();
        Template template2 = Template.builder()
                .template(&quot;template 2&quot;)
                .build();
        templateRepository.saveAll(List.of(template1, template2));

        MailHistory mailHistory1 = MailHistory.builder()
                .addresses(&quot;abcd@naver.com&quot;)
                .title(&quot;title 1&quot;)
                .templateName(TemplateName.WELCOME)
                .sentAt(LocalDateTime.now())
                .isSent(true)
                .build();
        MailHistory mailHistory2 = MailHistory.builder()
                .addresses(&quot;zxcv@naver.com&quot;)
                .title(&quot;title 2&quot;)
                .templateName(TemplateName.EVENT_STATUS_CHANGED)
                .sentAt(LocalDateTime.now())
                .isSent(true)
                .build();
        MailHistory mailHistory3 = MailHistory.builder()
                .addresses(&quot;asdf@naver.com&quot;)
                .title(&quot;title 1&quot;)
                .templateName(TemplateName.WELCOME)
                .sentAt(LocalDateTime.now())
                .isSent(false)
                .build();

        mailTrackerRepository.saveAll(List.of(mailHistory1, mailHistory2, mailHistory3));
    }

    @ParameterizedTest
    @CsvSource({&quot;EVENT_STATUS_CHANGED,template 2&quot;, &quot;WELCOME,template 1&quot;})
    void findByTemplateName(TemplateName templateName, String expectedTemplate) {
        // when
        String actualTemplate = templateRepository.findTemplateByTemplateName(templateName);

        // then
        assertThat(actualTemplate).isEqualTo(expectedTemplate);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CS0Wl/btsD6uv9Ow3/sKfPvQm8KKM44WB7kEk6k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CS0Wl/btsD6uv9Ow3/sKfPvQm8KKM44WB7kEk6k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CS0Wl/btsD6uv9Ow3/sKfPvQm8KKM44WB7kEk6k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCS0Wl%2FbtsD6uv9Ow3%2FsKfPvQm8KKM44WB7kEk6k1%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;716&quot; height=&quot;295&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;b&gt;WELCOME 템플릿은 id가 1, &lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;EVENT_STATUS_CHANGED는 id가 2로 고정&lt;/b&gt;되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이럴때 위 repository 테스트를 실행시키면 테스트가 깨지는데, 이유는 한 application context에서 auto_increment값은 순차적으로 증가된다. 따라서 하나의 application context를 공유하고 있으면, 첫 테스트에서 id값을 1,2로 생성되면 그 다음번에는 3,4의 id값이 생성된다.&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 style=&quot;color: #000000; text-align: start;&quot;&gt;이 문제를 어떻게 해결 할 수 있을까?&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결방법 1️⃣&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@DirtiesContext&lt;/b&gt;를 사용해 아래와 같이 매 테스트 메서드 실행 이후 Application Context를 새로 만들어 주는 방법을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법을 사용하면 매 테스트마다 application context를 재생성하여 auto_increment값이 1로 초기화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방법은  테스트간 격리가 가능하지만, Application Context가 재생성되기에 테스트 성능 저하를 초래할 수 있다.&lt;br /&gt;그래서 테스트 속도가 매우 느렸다!&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zz9sX/btsEdJFfylb/w8DzpaIqHIxHmN25QSd290/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zz9sX/btsEdJFfylb/w8DzpaIqHIxHmN25QSd290/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zz9sX/btsEdJFfylb/w8DzpaIqHIxHmN25QSd290/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzz9sX%2FbtsEdJFfylb%2Fw8DzpaIqHIxHmN25QSd290%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;749&quot; height=&quot;107&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬환경에서 실행했을 때 360ms가 두개의 테스트를 실행하는데 소요되었다.]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결방법 2️⃣&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에 &lt;b&gt;native query를 통해 auto increment 시작 숫자를 재설정&lt;/b&gt; 하여 문제를 해결하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tearDown 메서드를 통해 아래 메서드를 실행시켜줌으로서 테스트가 id값에 의존할 때 auto_increment와 테스트 순서간의 연관관계를 끊을 수 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1706618473173&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Autowired
    private EntityManager entityManager;

    @AfterEach
    void resetAutoIncrementId() {
        entityManager.createNativeQuery(&quot;ALTER TABLE TEMPLATE ALTER COLUMN template_id RESTART WITH 1&quot;)
                .executeUpdate();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 한다면 매번 application context를 재생성할 필요가 없어 실행 시간 또한 &lt;b&gt;@DirtiesContext &lt;/b&gt;를 사용하는 시간보다 빠르다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9L5fO/btsEdBN2Iwn/WV82ksi3OKVwgfJkyWvCf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9L5fO/btsEdBN2Iwn/WV82ksi3OKVwgfJkyWvCf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9L5fO/btsEdBN2Iwn/WV82ksi3OKVwgfJkyWvCf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9L5fO%2FbtsEdBN2Iwn%2FWV82ksi3OKVwgfJkyWvCf1%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;741&quot; height=&quot;108&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬환경에서 실행했을 때 360ms &amp;rarr; 345ms로 테스트 소요시간이 준 것을 확인할 수 있었다.&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;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/HomoEfficio/dev-tips/blob/master/Spring%20Data%20JPA%20%ED%85%8C%EC%8A%A4%ED%8A%B8%20%EC%8B%9C%20auto-increment%20%EB%AC%B8%EC%A0%9C.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/HomoEfficio/dev-tips/blob/master/Spring%20Data%20JPA%20%ED%85%8C%EC%8A%A4%ED%8A%B8%20%EC%8B%9C%20auto-increment%20%EB%AC%B8%EC%A0%9C.md&lt;/a&gt;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;
&lt;div class=&quot;css-o2chx0&quot;&gt;
&lt;div class=&quot;css-7gfopr&quot;&gt;
&lt;div class=&quot;css-1kx128h&quot;&gt;
&lt;div class=&quot;css-pix79b&quot;&gt;
&lt;div class=&quot;css-1d6xttj&quot;&gt;&lt;a class=&quot;css-1700ycd&quot; href=&quot;https://en.dict.naver.com/#/search?query=reference&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;reference&lt;/a&gt;
&lt;div class=&quot;css-fsm7b3&quot;&gt;
&lt;div class=&quot;css-192euok&quot;&gt;
&lt;div class=&quot;css-l0avtt&quot;&gt;
&lt;div class=&quot;css-mgy5lp&quot; role=&quot;button&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;button class=&quot;css-gceylj&quot;&gt;&lt;/button&gt;&lt;/div&gt;
&lt;div class=&quot;css-zwsxzd&quot;&gt;
&lt;div class=&quot;css-1ol4ajo&quot;&gt;
&lt;div class=&quot;css-17ye1k5&quot;&gt;미국∙영국&lt;/div&gt;
&lt;div class=&quot;css-161tqsn&quot;&gt;[ˈrefrəns]&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;css-1kv9gp9&quot;&gt;
&lt;div class=&quot;css-0&quot;&gt;1. (&amp;hellip; 에 대해) 말하기, 언급; 언급 대상, 언급한 것&lt;/div&gt;
&lt;div class=&quot;css-0&quot;&gt;2. (정보를 얻기 위해) 찾아봄, 참고, 참조&lt;/div&gt;
&lt;div class=&quot;css-0&quot;&gt;3. 참고[참조] 표시를 하다, 참조 문헌(목록)을 달다&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;css-yfmmwe&quot;&gt;
&lt;div class=&quot;css-v9kbwr&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;css-11c8vnq&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: -60px; top: 3407.16px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming/Database</category>
      <author>junojuno</author>
      <guid isPermaLink="true">https://juno-juno.tistory.com/105</guid>
      <comments>https://juno-juno.tistory.com/105#entry105comment</comments>
      <pubDate>Tue, 30 Jan 2024 21:47:43 +0900</pubDate>
    </item>
  </channel>
</rss>