<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>공대생 공부일지</title>
    <link>https://2junbeom.tistory.com/</link>
    <description>인생이란 무엇인가</description>
    <language>ko</language>
    <pubDate>Wed, 8 Jul 2026 07:51:55 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>대기업 가고 싶은 공돌이</managingEditor>
    <image>
      <title>공대생 공부일지</title>
      <url>https://tistory1.daumcdn.net/tistory/6740686/attach/ed708ead27294fb4918180e96c99de9f</url>
      <link>https://2junbeom.tistory.com</link>
    </image>
    <item>
      <title>IBK기업은행 2026년도 상반기 IT 신입행원 최종합격 후기 - 1 (필기 후기)</title>
      <link>https://2junbeom.tistory.com/221</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;2026 상반기 죽지않고 무사히 취뽀를 해서 돌아오게 되었습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대기업에 가고 싶은 공돌이 블로그를 만들고 2023년부터 열심히 글을 써왔는데, 블로그 이름에 해당하는 목표를 이룰 수 있어서 감개무량하군요 하핳&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 이번 상반기 서류는 대략 30개 정도를 썼고, 서류는 오직 2개만 붙었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나름 열심히 살아왔고, 스펙도 많이 쌓았다고 생각했는데 서류에서 박살이 나버리니 마음이 무거웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 결국&lt;/p&gt;
&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;보험개발원, IBK기업은행, 현대 오토에버, 코스콤 총 4곳에 최종면접&lt;/b&gt;을 가게 됐고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IBK기업은행은 최종합격!&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;(현대 오토에버와 IBK기업은행은 서류 합격 개념이 없어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서류는 보험개발원, 코스콤 2개만 붙었다고 표현했습니다 !)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스펙 공유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 스펙을 간단히 공유 드리면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학: 인서울 중상위 공대&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학점: 3.94&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수상 경력:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. IBK기업은행 우수 인턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. KB국민은행 해커톤 장려상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. KB 국민은행 개발자 교육 프로젝트 최우수상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. KB 국민은행 개발자 교육 최우수 수료생,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 교내 수상 1회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 연합 해커톤 수상 1회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 연합 동아리 프로젝트 수상 1회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자격증: AWS Certified Developer - Associate, 정보처리기사, sqld&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어학: 오픽 IH&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 운영 경험: MAU 1300 수준 서비스 개발, 배포 및 유지보수 경험&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대외활동 및 인턴: KB 국민은행 개발자 교육, IBK 기업은행 인턴 , IT 연합 동아리 TAVE 회원 및 운영진 활동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정도 되겠습니다 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 한 곳에서 오랜 시간 아르바이트 한 경험도 좋게 봐주셔서, 아르바이트도 어필 많이 했습니다 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;기업은행은 필기에서 10000명 정도를 탈락 시키고, 바로 면접을 들어가기 때문에&lt;/p&gt;
&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;그래서 기업은행 IT 인턴 같이 한 친구들끼리, 인턴 끝나자마자 스터디 만들어서 NCS와 전공필기를 준비했습니다 ㅎㅎ&lt;/p&gt;
&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;NCS 책&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000217351315&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://product.kyobobook.co.kr/detail/S000217351315&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781075262871&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;혼잡(JOB) 은행 NCS를 위한 피셋(PSAT) 300제 | 배현우 - 교보문고&quot; data-og-description=&quot;혼잡(JOB) 은행 NCS를 위한 피셋(PSAT) 300제 | 이 책은 은행권 필기시험에 적합한 핵심 피셋(PSAT) 문항을 엄선한 수험서이다.&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000217351315&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000217351315&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cETvfZ/dJMb8WMxP9V/KGUi2z6f5PqzII77P4KkZk/img.jpg?width=458&amp;amp;height=626&amp;amp;face=0_0_458_626,https://scrap.kakaocdn.net/dn/danuEU/dJMb8ZvJCD2/q5yukqKgBjfed8pkT5VJk0/img.jpg?width=458&amp;amp;height=626&amp;amp;face=0_0_458_626,https://scrap.kakaocdn.net/dn/cKyvH2/dJMb8XknDY4/XBdAK74XT9vGcrUXMm82C0/img.jpg?width=814&amp;amp;height=7793&amp;amp;face=0_0_814_7793&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000217351315&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000217351315&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cETvfZ/dJMb8WMxP9V/KGUi2z6f5PqzII77P4KkZk/img.jpg?width=458&amp;amp;height=626&amp;amp;face=0_0_458_626,https://scrap.kakaocdn.net/dn/danuEU/dJMb8ZvJCD2/q5yukqKgBjfed8pkT5VJk0/img.jpg?width=458&amp;amp;height=626&amp;amp;face=0_0_458_626,https://scrap.kakaocdn.net/dn/cKyvH2/dJMb8XknDY4/XBdAK74XT9vGcrUXMm82C0/img.jpg?width=814&amp;amp;height=7793&amp;amp;face=0_0_814_7793');&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;혼잡(JOB) 은행 NCS를 위한 피셋(PSAT) 300제 | 배현우 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;혼잡(JOB) 은행 NCS를 위한 피셋(PSAT) 300제 | 이 책은 은행권 필기시험에 적합한 핵심 피셋(PSAT) 문항을 엄선한 수험서이다.&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://product.kyobobook.co.kr/detail/S000217178232&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://product.kyobobook.co.kr/detail/S000217178232&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781075291618&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;독끝 NCS 기본서: 독학으로 끝내는 공기업 NCS 통합기본서 (PSAT형+모듈형+피듈형) | 길잡이 연구소 -&quot; data-og-description=&quot;독끝 NCS 기본서: 독학으로 끝내는 공기업 NCS 통합기본서 (PSAT형+모듈형+피듈형) | 이 책은 시중에 나와있는 모듈형 위주의 NCS 기본서들과 달리 실제 다수의 기업에서 출제하는 유형인 PSAT형 개념&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000217178232&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000217178232&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bsWIIL/dJMb82eVbhD/eEyWMIJEhC9Ng0Fkc0gIW0/img.jpg?width=458&amp;amp;height=458&amp;amp;face=0_0_458_458,https://scrap.kakaocdn.net/dn/D7uEn/dJMb81f0OKV/POFwRvmfv5XWfAJG8DKnbK/img.jpg?width=458&amp;amp;height=458&amp;amp;face=0_0_458_458&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000217178232&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000217178232&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bsWIIL/dJMb82eVbhD/eEyWMIJEhC9Ng0Fkc0gIW0/img.jpg?width=458&amp;amp;height=458&amp;amp;face=0_0_458_458,https://scrap.kakaocdn.net/dn/D7uEn/dJMb81f0OKV/POFwRvmfv5XWfAJG8DKnbK/img.jpg?width=458&amp;amp;height=458&amp;amp;face=0_0_458_458');&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;독끝 NCS 기본서: 독학으로 끝내는 공기업 NCS 통합기본서 (PSAT형+모듈형+피듈형) | 길잡이 연구소 -&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;독끝 NCS 기본서: 독학으로 끝내는 공기업 NCS 통합기본서 (PSAT형+모듈형+피듈형) | 이 책은 시중에 나와있는 모듈형 위주의 NCS 기본서들과 달리 실제 다수의 기업에서 출제하는 유형인 PSAT형 개념&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NCS 준비가 처음이라 독끝 기본서로 개념 간단하게 훑고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은피셋 풀면서 은행 NCS 개념 공부 했습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업은행 NCS는 특히 비문학 느낌이 더 많아서, 국어 자신 있던 분들은 수월하게 보실 겁니다&lt;/p&gt;
&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;a href=&quot;https://product.kyobobook.co.kr/detail/S000217021687&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://product.kyobobook.co.kr/detail/S000217021687&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781075462149&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;2026 박미진 컴퓨터일반 이론서 | 박미진 - 교보문고&quot; data-og-description=&quot;2026 박미진 컴퓨터일반 이론서 | ① 이해가 쉽게 될 수 있도록 문장을 쉽게 서술하였습니다. ② 다양한 도표와 계산식, 그림을 추가하여 학습을 보다 용이하게 하였습니다. ③확인학습을 통해 확&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000217021687&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000217021687&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dre6Ao/dJMb8Z3zCAO/k0zkPlRFMnS05m7WVNBVrK/img.jpg?width=458&amp;amp;height=575&amp;amp;face=0_0_458_575,https://scrap.kakaocdn.net/dn/dVnntt/dJMb8XknD1t/xYfxK94mr89OPGKkwnRjxK/img.jpg?width=458&amp;amp;height=575&amp;amp;face=0_0_458_575&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000217021687&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000217021687&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dre6Ao/dJMb8Z3zCAO/k0zkPlRFMnS05m7WVNBVrK/img.jpg?width=458&amp;amp;height=575&amp;amp;face=0_0_458_575,https://scrap.kakaocdn.net/dn/dVnntt/dJMb8XknD1t/xYfxK94mr89OPGKkwnRjxK/img.jpg?width=458&amp;amp;height=575&amp;amp;face=0_0_458_575');&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;2026 박미진 컴퓨터일반 이론서 | 박미진 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2026 박미진 컴퓨터일반 이론서 | ① 이해가 쉽게 될 수 있도록 문장을 쉽게 서술하였습니다. ② 다양한 도표와 계산식, 그림을 추가하여 학습을 보다 용이하게 하였습니다. ③확인학습을 통해 확&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전공 필기는 박미진씨의 컴퓨터 일반을 사서 공부했는데요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NCS 공부하다보니 시간이 없어서 얼마 못 풀었구요&lt;/p&gt;
&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NCS는 은피셋보다 좀 더 쉽게 나왔구요,&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;C, 파이썬, 자바 코딩 문제 많이 나오고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신처기 기출도 2문제 정도 보이더군요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 네트워크부터, 데이터베이스, 운영체제, 컴퓨터 구조에 리눅스 명령어까지 정말 다양하게 나온 시험이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEfT8C/dJMcahdFO2y/lkskLTuwXw4X4z1afGEKf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEfT8C/dJMcahdFO2y/lkskLTuwXw4X4z1afGEKf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEfT8C/dJMcahdFO2y/lkskLTuwXw4X4z1afGEKf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEfT8C%2FdJMcahdFO2y%2FlkskLTuwXw4X4z1afGEKf1%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;966&quot; height=&quot;526&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;526&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;저는 우수인턴 가점 10점을 사용해서 90.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;sqld, 정보처리기사 등등의 가점은 5점이 적용되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 적용은 안된다구 하네욥&lt;/p&gt;
&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;IT는 81점에서 동점자 기준으로 NCS 점수가 더 높으신 분이 합격했다고 하시고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디지털은 82점에서 동점자 기준&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;90점 넘기니까 기분이 좋군요 하핳&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차 실기 면접 후기는 다음 포스트로 가져오겠습니다 ~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>2026 상반기 취준 후기/ibk 기업은행</category>
      <category>IBK기업은행</category>
      <category>금융권 개발</category>
      <category>금융권IT</category>
      <category>기업은행</category>
      <category>기업은행IT</category>
      <category>기은</category>
      <category>은행</category>
      <category>취뽀</category>
      <category>합격후기</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/221</guid>
      <comments>https://2junbeom.tistory.com/221#entry221comment</comments>
      <pubDate>Wed, 10 Jun 2026 16:23:06 +0900</pubDate>
    </item>
    <item>
      <title>[IBK기업은행 청년인턴] 2026년 IBK기업은행 동계 체험형 청년인턴 IT 부서 합격 후기</title>
      <link>https://2junbeom.tistory.com/213</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;올해 하반기에 시험 삼아 10개 정도 서류를 써봤는데 대기업은 전부 떨어지고 인턴은 모조리 붙은 이상한 상황 ㅠㅠ&lt;br&gt;&amp;nbsp;&lt;br&gt;문제가 뭔가 살펴보니 아무래도 합격한 인턴들은 전부 영어 가점 사항이 없어서 붙은 듯 하다...&lt;br&gt;&amp;nbsp;&lt;br&gt;내년 상반기 채용공고가 풀리기 전까지 오픽 IH를 만들어둬야겠다.&lt;br&gt;&amp;nbsp;&lt;br&gt;내가 뭐가 부족한지 알 수 있어서 서류는 미리미리 작성해보는 게 좋은 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그럼 기업은행 인턴 후기를 적어보겠다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이번 2026년 동계 인턴은 9400명 정도가 지원한 역대급으로 많은 인원이 몰린 기수라고 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;겨우 체험형 인턴인데 9400명이라니,,, 나라가 어찌될런지 걱정이 앞선다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;채용 과정 및 우수인턴 혜택&lt;/h3&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3xUg7/dJMcafrJGYh/5oxgkKzyZ863616AGYmEe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3xUg7/dJMcafrJGYh/5oxgkKzyZ863616AGYmEe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3xUg7/dJMcafrJGYh/5oxgkKzyZ863616AGYmEe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3xUg7%2FdJMcafrJGYh%2F5oxgkKzyZ863616AGYmEe1%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;1242&quot; height=&quot;856&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ARQOf/dJMcadN9Oow/xkxOoRRDukvCyQGQ0T1wA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ARQOf/dJMcadN9Oow/xkxOoRRDukvCyQGQ0T1wA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ARQOf/dJMcadN9Oow/xkxOoRRDukvCyQGQ0T1wA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FARQOf%2FdJMcadN9Oow%2FxkxOoRRDukvCyQGQ0T1wA1%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;1246&quot; height=&quot;780&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;우선&lt;br&gt;서류 + AI 면접 -&amp;gt; PT 면접 + 토론 면접 + 인성면접의 순서로 이뤄져 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우수인턴 혜택은 ncs 시험에서 10점 가점이 주어진다고 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;기업은행은 서류는 적부심사로만 이뤄지고 ncs에서 모두 걸러내기 때문에 아주 사소한 차이로&lt;br&gt;탈락한다는 점을 고려하면 상당한 이점이라고 생각한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우수인턴은 절대 평가로 선정되며 과제 + 멘토님들의 평가가 80점 이상이면 선정되는 것으로 알고있다.&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;서류심사&lt;/h3&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;1. *&amp;nbsp;다양한&amp;nbsp;분야의&amp;nbsp;회사들&amp;nbsp;중에서&amp;nbsp;IBK기업은행&amp;nbsp;청년인턴을&amp;nbsp;지원하게&amp;nbsp;된&amp;nbsp;이유가&amp;nbsp;무엇인지&amp;nbsp;자유롭게&amp;nbsp;이야기해&amp;nbsp;주세요.&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;외국인 타겟 전자지갑 서비스를 만들며, I-ONE Bank 글로벌 서비스를 알게 되었다.&lt;br&gt;해당 서비스가 취약계층을 상대로 금융 접근성을 고려하는 것을 보며 기업은행에 대해 관심을 갖게 된 이야기를 풀어냈다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;2. * 지금까지 겪었던 어려움과 극복과정을 설명해 주시고, 이 경험이 지원자님에게 어떤 영향을 미치게 되었는지 전달해 주세요.&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;IT 부서에 지원하니 프로젝트 개발을 하며 겪었던 어려움을 적어야 하나, 아니면 진짜로 살면서 가장 어려웠던 경험을 적어야하나&lt;br&gt;고민하다 그냥 가장 어려웠던 경험을 적기로 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;헬스와 복싱, 각종 운동을 하며 약했던 몸을 발전시킨 경험을 적었다.&lt;br&gt;진짜 대학생 같아 보여서 뽑아준건가? 싶기도 하다&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;3. * 팀을 이루어 목표를 달성한 경험과 그 과정에서 지원자님이 팀에 기여한 바에 대해 구체적으로 말씀해 주세요.&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;국민은행 부트캠프에서 파이널 프로젝트를 할 당시, 팀장으로 팀원들을 이끌었던 경험을 적었다.&lt;br&gt;구체적으로 팀원들에게 업무 분배를 어떤 방식으로 했는지, 협업 구조를 어떻게 개선했는지 등등을 중점으로 자세하게 풀어냈다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;4. * 지원자님이 가진 강점에 대해 알고 싶어요. 앞으로 청년인턴으로 근무하면서 그 강점을 어떻게 활용할 수 있을지 함께 들려주세요.&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;나는 굉장히 집요한 사람이다. 시간이 허락하는 만큼 최대한의 결과를 뽑아내려 노력한다.&lt;br&gt;프로젝트를 진행하면서, 어떻게 결과를 내려고 노력했는지, 만든 결과를 어떻게 검증했는지 등등 내가 실제로&amp;nbsp;&lt;br&gt;해왔던 것들을 자세하게 적어냈다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;AI 면접&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;AI 면접은 몬스터 역량검사라는 사이트에서 이뤄졌다.&lt;br&gt;&amp;nbsp;&lt;br&gt;인공지능을 기반으로 질문과 꼬리 질문들이 이어지는데, 그냥 지피티한테 내 자소서 넘겨주고&lt;br&gt;너가 면접관이라면 뭐가 궁금할 거 같애? 라고 물어본 것과 거의 동일한 질문이 나왔다.&lt;br&gt;&amp;nbsp;&lt;br&gt;내 자소서를 봤을 때 뭐가 궁금할지 생각해보고 그것들에 대해서 잘 준비한다면 아무런 문제없이 수행할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다른 후기글들에서는 카메라를 잘 쳐다보고 웃는 얼굴과 힘찬 목소시로 답변하면 AI가 더 좋게 볼 수도 있을 거라 하던데, 이 부분은 카더라니까 참고만 하시길&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;1554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XhHLM/dJMcadgk1TI/IKs2C3mObawHKdusu5stK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XhHLM/dJMcadgk1TI/IKs2C3mObawHKdusu5stK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XhHLM/dJMcadgk1TI/IKs2C3mObawHKdusu5stK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXhHLM%2FdJMcadgk1TI%2FIKs2C3mObawHKdusu5stK1%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;1308&quot; height=&quot;1554&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;1554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/njGji/dJMb99LOaEu/aOsN2pa4rQ4ppjM5rcx1wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/njGji/dJMb99LOaEu/aOsN2pa4rQ4ppjM5rcx1wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/njGji/dJMb99LOaEu/aOsN2pa4rQ4ppjM5rcx1wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnjGji%2FdJMb99LOaEu%2FaOsN2pa4rQ4ppjM5rcx1wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;112&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;예이후 ~~&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;면접 준비&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;면접은 pt면접 + 토론 면접+ 인성면접 세 가지로 이뤄진다.&lt;br&gt;면접 예상 시간이 안내문에 3시간이라고 적혀있어서 내 눈을 잠깐 의심하긴 했지만 뭐 어쩔 수 있나 하라면 해야지&lt;br&gt;&amp;nbsp;&lt;br&gt;pt면접은 싸피 면접을 준비할 때 조금 했었어서 따로 준비하지 않았고,&lt;br&gt;토론 면접은 내가 토론을 좋아했어서 따로 준비하지 않았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;초등학생이랑 고등학생 때 토론 대회에 나가기도 했었고, 군생활 할 때 상급부대에서 감찰이 오거나, 장병 토론 등등의 요청이 들어오면 무조건 내가 나갔었다,,, 그래도 중대장이 휴가 많이 챙겨줬었으니까 뭐&lt;br&gt;&amp;nbsp;&lt;br&gt;아무튼 인성면접만 대비를 좀 했고, 인성면접 마저도 몬스터 역검 준비하고 가비아 면접 준비할 때 많이 했어서 면접 준비에 많은 시간을 쏟진 않았다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;면접 후기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;우리 조는 총 8명이었다.&lt;br&gt;자리에 앉았을 때 내 옆자리에 앉은 형님이 내가 다니는 헬스장 트레이너랑 너무 똑같이 생기셔서 내적 친밀감이 들었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMSUht/dJMcafL19bB/Ku2lCXDtiFDWh0WdJs1Sjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMSUht/dJMcafL19bB/Ku2lCXDtiFDWh0WdJs1Sjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMSUht/dJMcafL19bB/Ku2lCXDtiFDWh0WdJs1Sjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMSUht%2FdJMcafL19bB%2FKu2lCXDtiFDWh0WdJs1Sjk%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;3000&quot; height=&quot;2250&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;면접비는 수도권 사는 사람은 3만원이다. 이걸로 앞에서 여자친구 밥 한끼 사줬다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우리 조의 토론 주제는 CCTV로 성별을 감지해서 범죄를 예방하자에 대한 찬반 토론이었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;나는 주제 듣자마자 그냥 찬성쪽에서 얘기할 게 더 많을 거 같아서 바로 찬성 팀을 희망했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우리 조는 신기하게 찬반이 정확하게 4:4로 나뉘어서 넘어갈 필요없이 바로 토론이 시작됐다.&lt;br&gt;&amp;nbsp;&lt;br&gt;주어진 자료를 보면서 마인드맵을 정리했고, 모든 주장에 근거를 달 수 있도록 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;발표 파트를 나눌 때 먼저 나서서 교통 정리를 좀 했더니, 옆에 면접관님이 뭘 쓱쓱 적으셨다.&lt;br&gt;(아싸 가점 개이득이라고 생각하며 교통정리를 마저 했다)&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;PT 면접&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;pt 면접은 우리가 작성한 마인드맵을 기준으로 진행해야한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;아까 정리한 순서를 바탕으로 우리의 주장을 피력하고, 상대방의 주장을 듣는다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;토론 면접&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;바로 토론 면접 시작, 상대방의 발표를 들으며 이거 반박해야지 했던 것들을 가감없이 쏟아냈다.&lt;br&gt;&amp;nbsp;&lt;br&gt;하지만 상대방의 의견을 존중하며, 친절한 말투로 반박을 해야한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;~~ 님의 의견 저도 생각을 했던 부분입니다. 몇몇 부분은 상당히 공감이 가는데요, 하지만 ~~ 부분은 어쩌구 저쩌구 하면서&lt;br&gt;말을 이어갔다.&lt;br&gt;&amp;nbsp;&lt;br&gt;토론이 길게 이어지니 더 이상 할 말이 없었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;그 때, 헬스 트레이너 행님이 ~~한 보안적 측면에 대해서도 얘기 나눠보면 좋을 것 같아요 ~라고 새로운 주제를 던져주셔서&lt;br&gt;&amp;nbsp;&lt;br&gt;내가 아는 것들로 최대한 설명을 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;마무리 의견 정리를 할 때 우리팀에서 하실 분이 없다고 하셔서 마무리 의견 정리도 내가 진행했다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;10분 휴식 후 인성 면접&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;50분 정도 진행되는데 8명이라 인당 6-7분 정도가 주어진다고 생각하면 된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;국룰 질문인 자기소개와 마지막으로 하고 싶은 말은 거의 모든 조에서 한 거 같으니 꼭 준비하길 바란다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다른 질문은 궁금하면 댓글&lt;br&gt;&amp;nbsp;&lt;br&gt;자기소개서 기반 질문은 내가 준비했던 거에서 모두 나와서 잘 마무리 했다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;2089&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3yVcP/dJMcabbNtNt/eCa6XkqLVlBSwV6u8UKXz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3yVcP/dJMcabbNtNt/eCa6XkqLVlBSwV6u8UKXz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3yVcP/dJMcabbNtNt/eCa6XkqLVlBSwV6u8UKXz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3yVcP%2FdJMcabbNtNt%2FeCa6XkqLVlBSwV6u8UKXz1%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;2250&quot; height=&quot;2089&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;2089&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;밑에서 팀원들이랑 사진 한 장 찍어주고 끝 ~~&lt;br&gt;&amp;nbsp;&lt;br&gt;같이 한 팀원들 많이 붙었으면 좋겠다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SXHQQ/dJMcadN9OUg/N5odLgkopXMu0bmGXakMjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SXHQQ/dJMcadN9OUg/N5odLgkopXMu0bmGXakMjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SXHQQ/dJMcadN9OUg/N5odLgkopXMu0bmGXakMjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSXHQQ%2FdJMcadN9OUg%2FN5odLgkopXMu0bmGXakMjK%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;858&quot; height=&quot;124&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;흐에 ~!&lt;br&gt;&amp;nbsp;&lt;br&gt;가비아 채용연계형 인턴이랑 기업은행 체험형 인턴 중에 뭘 갈까 많이 고민하다가&lt;br&gt;&amp;nbsp;&lt;br&gt;기업은행 체험형 인턴을 하기로 결정했다.&lt;br&gt;&lt;br&gt;&lt;a href=&quot;https://2junbeom.tistory.com/m/221&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://2junbeom.tistory.com/m/221&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;IBK기업은행 2026년도 상반기 IT 신입행원 최종합격 후기 - 1 (필기 후기)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;안녕하십니까   2026 상반기 죽지않고 무사히 취뽀를 해서 돌아오게 되었습니다... 대기업에 가고 싶은 공돌이 블로그를 만들고 2022년부터 열심히 글을 써왔는데, 블로그 이름에 해당하는 목표&quot; data-og-host=&quot;2junbeom.tistory.com&quot; data-og-source-url=&quot;https://2junbeom.tistory.com/m/221&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GkAVg/dJMb8XknNxe/6i9B377ViIb8MOmejtAXi0/img.png?width=800&amp;amp;height=435&amp;amp;face=0_0_800_435&quot; data-og-url=&quot;https://2junbeom.tistory.com/m/221&quot;&gt;&lt;a href=&quot;https://2junbeom.tistory.com/m/221&quot; target=&quot;_blank&quot; data-source-url=&quot;https://2junbeom.tistory.com/m/221&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GkAVg/dJMb8XknNxe/6i9B377ViIb8MOmejtAXi0/img.png?width=800&amp;amp;height=435&amp;amp;face=0_0_800_435')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;IBK기업은행 2026년도 상반기 IT 신입행원 최종합격 후기 - 1 (필기 후기)&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;안녕하십니까   2026 상반기 죽지않고 무사히 취뽀를 해서 돌아오게 되었습니다... 대기업에 가고 싶은 공돌이 블로그를 만들고 2022년부터 열심히 글을 써왔는데, 블로그 이름에 해당하는 목표&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;2junbeom.tistory.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&lt;/p&gt;</description>
      <category>대외활동/ibk 기업은행 청년인턴</category>
      <category>5대 은행 인턴</category>
      <category>IBK</category>
      <category>IBK 기업은행</category>
      <category>IT</category>
      <category>IT부서</category>
      <category>기업은행</category>
      <category>동계 체험형 인턴</category>
      <category>면접 후기</category>
      <category>인턴</category>
      <category>합격 후기</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/213</guid>
      <comments>https://2junbeom.tistory.com/213#entry213comment</comments>
      <pubDate>Sun, 4 Jan 2026 04:25:41 +0900</pubDate>
    </item>
    <item>
      <title>[SSAFY] 삼성청년SWㆍAI아카데미 싸피 15기 전공자 합격 후기</title>
      <link>https://2junbeom.tistory.com/212</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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;1198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHVUhh/dJMcagxkGWB/ppvGcaCEAKeXA20DauC1Jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHVUhh/dJMcagxkGWB/ppvGcaCEAKeXA20DauC1Jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHVUhh/dJMcagxkGWB/ppvGcaCEAKeXA20DauC1Jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHVUhh%2FdJMcagxkGWB%2FppvGcaCEAKeXA20DauC1Jk%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;1508&quot; height=&quot;1198&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;1198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>대외활동/SSAFY</category>
      <category>15기 합격 후기</category>
      <category>SSAFY</category>
      <category>싸피</category>
      <category>싸피 서울</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/212</guid>
      <comments>https://2junbeom.tistory.com/212#entry212comment</comments>
      <pubDate>Tue, 23 Dec 2025 19:25:35 +0900</pubDate>
    </item>
    <item>
      <title>[가비아 채용연계형 인턴] &amp;lt;서비스 기획 - 클라우드 직무&amp;gt; 합격 후기</title>
      <link>https://2junbeom.tistory.com/211</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;안녕하세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&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;저는 개발자 일을 한 5년 정도 하고, 전반적인 역량을 쌓은 다음에 직접 서비스를 기획해서 만들겠다는 목표가 있었는데요.&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;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지원 프로세스&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CWXdp/dJMcacoaCS9/zKn3Tsz2MB4V7rwhysGso1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CWXdp/dJMcacoaCS9/zKn3Tsz2MB4V7rwhysGso1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CWXdp/dJMcacoaCS9/zKn3Tsz2MB4V7rwhysGso1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCWXdp%2FdJMcacoaCS9%2FzKn3Tsz2MB4V7rwhysGso1%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;904&quot; height=&quot;132&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예 이런 순서로 진행이 됩니다. 우선 서류 전형부터 살펴보죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서류 전형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서류 문항은 다음과 같이 구성됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사회&amp;nbsp;활동&amp;nbsp;경험&amp;nbsp;이력&lt;br /&gt;경험에&amp;nbsp;대한&amp;nbsp;소개와&amp;nbsp;본인이&amp;nbsp;수행한&amp;nbsp;역할을&amp;nbsp;상세히&amp;nbsp;작성해&amp;nbsp;주세요.&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문항에선 제가 수행했던,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KB IT's Your Life 부트캠프의 이야기 - 최우수 수료생 선정과 프로젝트 전체 1위를 했던 경험,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 과정에서 팀장으로 프로젝트를 이끌어갔던 경험 등등을 녹여냈고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TAVE 연합 개발동아리 운영진 이야기 - 회원들을 관리하고 멘토링 해준 경험,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 홈페이지를 구축하고 운영한 경험을 녹여냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이마트 24 아르바이트 경험 - 2년 정도 일을 한 경험을 통해, 저의 성실함을 어필하려 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로젝트&amp;nbsp;수행&amp;nbsp;이력&lt;br /&gt;프로젝트에&amp;nbsp;대한&amp;nbsp;소개와&amp;nbsp;본인이&amp;nbsp;수행한&amp;nbsp;역할을&amp;nbsp;상세히&amp;nbsp;작성해&amp;nbsp;주세요.&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 수행 이력에는 제가 지금까지 프로젝트를 수행해오면서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 품질을 개선한 경험, 문제를 인식하고 해결한 경험 등등을 적었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안을 고려해서 아키텍쳐 설계 역량을 쌓고 아키텍쳐를 보완한 경험,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 알림 파이프라인 구축을 통해 MTTR을 단축한 경험,&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;h4 data-ke-size=&quot;size20&quot;&gt;직무&amp;nbsp;질문&lt;br /&gt;우리&amp;nbsp;회사,&amp;nbsp;해당&amp;nbsp;직무에&amp;nbsp;지원하는&amp;nbsp;이유에&amp;nbsp;대해&amp;nbsp;각각&amp;nbsp;서술해&amp;nbsp;주십시오.&amp;nbsp;(명확하게&amp;nbsp;500자&amp;nbsp;이내로)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 무언가를 기획하고 개발한 다음에, 제가 만든 결과물을 사람들이 좋아해주는 것 자체가 굉장히 즐거웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경험을 지원동기에 녹여냈고, 제가 실제로 사용자들의 니즈를 파악하고 해결한 경험과 함께 적었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;지원하는&amp;nbsp;직무&amp;nbsp;역량&amp;nbsp;개발을&amp;nbsp;위해&amp;nbsp;어떤&amp;nbsp;노력과&amp;nbsp;준비를&amp;nbsp;했는지&amp;nbsp;서술해&amp;nbsp;주십시오.&amp;nbsp;(명확하게&amp;nbsp;500자&amp;nbsp;이내로)&lt;/h4&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;직접 서비스를 기획하고 팀원을 모은 경험도 있고, 팀원들과 함께 서비스 기획을 같이 했던 경험들도 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경험들을 녹여내서 작성하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이력&amp;nbsp;공백기가&amp;nbsp;있다면&amp;nbsp;해당&amp;nbsp;기간의&amp;nbsp;활동을&amp;nbsp;설명해&amp;nbsp;주십시오,&amp;nbsp;이직&amp;nbsp;경험이&amp;nbsp;있다면&amp;nbsp;이유와&amp;nbsp;상황을&amp;nbsp;설명해&amp;nbsp;주십시오.&amp;nbsp;(명확하게&amp;nbsp;500자&amp;nbsp;이내로)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이력 공백기가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 졸업 이후에 부트캠프 활동을 통해, 어떤 역량을 쌓았는지를 자세히 적어냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;우리&amp;nbsp;회사에서&amp;nbsp;맡고&amp;nbsp;싶거나&amp;nbsp;기여하고&amp;nbsp;싶은&amp;nbsp;역할&amp;nbsp;또는&amp;nbsp;업무를&amp;nbsp;자유롭게&amp;nbsp;서술해&amp;nbsp;주십시오.&amp;nbsp;(명확하게&amp;nbsp;500자&amp;nbsp;이내로)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 가비아 백엔드 기획자 Ryan의 사례를 감명 깊게 읽었는데요,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ryan의 AWS 청구 자동화 사례를 보며, 비효율을 개선해 사용자의 편의를 향상시키는 일을 하고 싶다고 작성하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;1248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbCrPy/dJMb99LJ5Bu/vNkFYLtVi1MCk4PJWAFXzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbCrPy/dJMb99LJ5Bu/vNkFYLtVi1MCk4PJWAFXzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbCrPy/dJMb99LJ5Bu/vNkFYLtVi1MCk4PJWAFXzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbCrPy%2FdJMb99LJ5Bu%2FvNkFYLtVi1MCk4PJWAFXzK%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;1850&quot; height=&quot;1248&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;1248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 서류를 합격했고, 온라인 역량검사를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;온라인 역량검사&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 아시는 잡다로 진행했구요,&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;h3 data-ke-size=&quot;size23&quot;&gt;면접&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc1B5v/dJMcagjNqqD/Ck1VCHRToqK7Ik0RTX5Fk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc1B5v/dJMcagjNqqD/Ck1VCHRToqK7Ik0RTX5Fk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc1B5v/dJMcagjNqqD/Ck1VCHRToqK7Ik0RTX5Fk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc1B5v%2FdJMcagjNqqD%2FCk1VCHRToqK7Ik0RTX5Fk1%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;908&quot; height=&quot;250&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대망의 면접 날 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접은 굉장히 편안한 분위기에서 진행됐고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 긴장을 많이 풀어주려고 하셨습니다.&lt;/p&gt;
&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;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;1124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btp4h1/dJMcafd8hqO/gKHiO70bnPh0kaPLGJfGh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btp4h1/dJMcafd8hqO/gKHiO70bnPh0kaPLGJfGh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btp4h1/dJMcafd8hqO/gKHiO70bnPh0kaPLGJfGh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtp4h1%2FdJMcafd8hqO%2FgKHiO70bnPh0kaPLGJfGh0%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;1806&quot; height=&quot;1124&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;1124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예이후 ~~ 여기 저기 면접 보러 다니고 나서 편안하게 기다리고 있다가 합격 메일을 받았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규직으로 전환이 될 지는 모르겠지만 일단 열심히 다녀보고 후기 남기겠습니다. !&lt;/p&gt;</description>
      <category>대외활동/가비아 채용연계형 인턴</category>
      <category>가비아</category>
      <category>서비스 기획</category>
      <category>채용연계형 인턴</category>
      <category>클라우드 기획</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/211</guid>
      <comments>https://2junbeom.tistory.com/211#entry211comment</comments>
      <pubDate>Tue, 23 Dec 2025 17:17:58 +0900</pubDate>
    </item>
    <item>
      <title>[KB IT's Your Life] 부하 테스트 및 성능 최적화 (K6, APM [Scouter], 인덱싱, 쿼리 최적화)</title>
      <link>https://2junbeom.tistory.com/210</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Dolfin 프로젝트&lt;/h2&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;KB It's your life 과정에서 프로젝트를 진행하며, 마지막 주간에 성능 최적화 과정을 진행중이다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 성능 최적화의 목표를 정해보겠다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;성능 최적화 목표치&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리 프로젝트의 1년 이내 목표 시장은&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;7만명이다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;7만명을 반올림하여 10만명의 유저를 수용한다고 가정하자.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물론&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;DAU(일일 활성 사용자)&lt;/b&gt;를 전체 유저로 잡진 않지만 우리는 최대한 보수적으로 잡고 테스트를 진행하기로 했다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DAU를 10만명으로 잡고, 보통 10% 정도의 유저의 동시 접속을 최대 부하로 잡는다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 우리는 10만명의 동시접속을 목표치로 잡았고, 서비스의 메인 시나리오에 대해 성능 최적화를 진행하기로 했다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;서비스 메인 시나리오&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리 서비스의 주된 유저 시나리오는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;로그인&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;메인페이지 접속(총 3개의 api 호출)&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;최근 거래 내역 조회 api 호출&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;송금 api 호출&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;약 2분간 총 6개의 api&lt;/b&gt;가 호출된다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2분간 6개의 api라 함은, 120초 동안 6개의 api를 호출한다는 의미이며,&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유저 한 명당 0.05 TPS를 요구&lt;/b&gt;한다는 의미가 된다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;10만 명의 동시 접속&lt;/b&gt;을 목표로 잡는다면,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5000 TPS&lt;/b&gt;가 우리의 목표치가 된다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리의 시스템 아키텍쳐는 메인서버에 ASG가 설정돼 있고, 최대 5개까지 서버 증설이 자동적으로 이뤄진다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5개의 서버로 로드밸런싱이 이뤄지니,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1개의 서버당 1000TPS&lt;/b&gt;를 목표치로 설정하고 성능 최적화를 진행했다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;에러율 및 목표 응답 시간&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;부트캠프 강사님은 에러율은 무조건 99.9% 이내, 응답 시간은 보통 프론트 + 백엔드를 합쳐 3초 이내라고 하셨다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://odown.com/blog/api-response-time-standards/?utm_source=chatgpt.com&quot;&gt;https://odown.com/blog/api-response-time-standards/?utm_source=chatgpt.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1755879830542&quot; style=&quot;color: #333333; text-align: start;&quot; contenteditable=&quot;false&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/HMwz0/hyZuABlfla/AAAAAAAAAAAAAAAAAAAAAFFBgpnx8QR38--QXES1EhtTq5yiy1697zgk4O0MTkFX/img.jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1756652399&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=uMaWzniItvHJaOdsI2vRNGkZUck%3D&quot; data-og-url=&quot;https://odown.com/blog/api-response-time-standards/?utm_source=chatgpt.com&quot; data-og-source-url=&quot;https://odown.com/blog/api-response-time-standards/?utm_source=chatgpt.com&quot; data-og-host=&quot;odown.com&quot; data-og-description=&quot;Discover industry benchmarks for API response times and learn how to monitor, optimize, and set SLAs for fast, reliable, and scalable API performance.&quot; data-og-title=&quot;API Response Time Standards: What's Good, Bad, and Unacceptable&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://odown.com/blog/api-response-time-standards/?utm_source=chatgpt.com&quot; data-source-url=&quot;https://odown.com/blog/api-response-time-standards/?utm_source=chatgpt.com&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;API Response Time Standards: What's Good, Bad, and Unacceptable&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;Discover industry benchmarks for API response times and learn how to monitor, optimize, and set SLAs for fast, reliable, and scalable API performance.&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;odown.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Odown.com&lt;/b&gt;이라는 API 모니터링&amp;middot;장애 감지 서비스 업체는 다음과 같이 정의했다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;general-api-response-time-categories&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;일반 API 응답 시간 범주&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동급 최고의 성능:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;100~300ms:&lt;span&gt;&amp;nbsp;&lt;/span&gt;매우 우수하고 즉각적으로 인식됨&amp;nbsp;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;300~500ms:&lt;span&gt;&amp;nbsp;&lt;/span&gt;매우 좋음, 대기 인식이 최소 수준임&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;500~800ms:&lt;span&gt;&amp;nbsp;&lt;/span&gt;양호, 눈에 띄지만 복잡한 작업에는 허용 가능&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;800~1000ms:&lt;span&gt;&amp;nbsp;&lt;/span&gt;보통, 느린 것으로 인식됨&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;1000~2000ms:&lt;span&gt;&amp;nbsp;&lt;/span&gt;좋지 않음, 사용자 이탈 위험 있음&amp;nbsp;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;2000ms+:&lt;span&gt;&amp;nbsp;&lt;/span&gt;대부분의 사용 사례에 적합하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1번 즉 1000 TPS에서 95% 이내의 응답이 300ms 안에 처리되는 것을 목표로 잡았다&lt;/b&gt;.&lt;/p&gt;
&lt;p style=&quot;color: #222222; 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;b&gt;커넥션 풀 사이즈 검증 루프&lt;/b&gt;&lt;/h3&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;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;리틀의 법칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;b&gt;TPS = 커넥션 풀 사이즈 / 평균 처리 시간(데이터베이스의)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;오라클에서 실험 후 정의한 스레드 풀 개수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;커넥션 풀 사이즈&lt;/b&gt;&amp;nbsp;= ((CPU 코어 수 * 2) + 유효 스핀들 수)&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&quot;&gt;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure id=&quot;og_1755879830543&quot; style=&quot;color: #333333; text-align: start;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;About Pool Sizing&quot; data-og-description=&quot;光 HikariCP・A solid, high-performance, JDBC connection pool at last. - brettwooldridge/HikariCP&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&quot; data-og-url=&quot;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/lxXXz/hyZuIGn5Jl/AAAAAAAAAAAAAAAAAAAAAM-Q00y6AYZuN8O6eF_EW1t1w4vu3ll2S5J8LjN1a3Td/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1756652399&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=2wzniNdndabwEDTpst1DUX6xx9w%3D&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&quot; data-source-url=&quot;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;About Pool Sizing&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;光 HikariCP・A solid, high-performance, JDBC connection pool at last. - brettwooldridge/HikariCP&lt;/p&gt;
&lt;p style=&quot;color: #909090;&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;커넥션 풀 사이즈는 보통 CPU 코어 수를 따라가야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;운영체제 시간을 복기해보자 -&amp;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;커넥션 풀 사이즈와 CPU 코어 수를 동일하게 가져가면? -&amp;gt; CPU가 작업 후 I/O 블로킹이 걸렸을 때 다른 작업을 수행할 스레드가 없어&lt;br /&gt;손해가 발생한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;리틀의 법칙이 저렇게 나온 이유를 살펴보자&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리틀의 법칙은 데이터베이스의 하드웨어를 고려하지 않고 현재 서비스의 상황에 맞추어 목표로 하는 TPS를 구하기 위해,&lt;br /&gt;잡아야하는 커넥션 수를 알려주는 공식이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;b&gt;커넥션 풀 사이즈&lt;/b&gt;란 동시에 처리하는 최대 요청 수라 의미할 수 있고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;평균 처리 시간&lt;/b&gt;이란 대기 후, 처리되는 데까지 걸리는 시간을 의미한다. 동시에 처리되는 요청수와 평균 응답 시간(초 단위)을 나눈다는 의미는 즉, 1초에 처리할 수 있는 요청 수를 구한다는 의미가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;오라클에서 정의한 공식도 살펴보자&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;cpu *2 인 이유는 I/O 블로킹을 고려하여 CPU의 두 배인 커넥션 풀을 잡은 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;유효 스핀들 수는 원래 하드 디스크에서 스핀들을 돌려가며 데이터를 탐색하는 시간이 오래 걸리기에 그만큼 I/O 블로킹이 늘어난다 가정하고 오라클에서 저렇게 공식을 정의했던 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나, SSD를 사용하는 요즘 데이터를 탐색하는 소요되는 시간이 거의 없어진 추세라 커넥션 풀 사이즈를 CPU 코어 수와 거의 비슷하게 가져간다고 한다. 그래도 블로킹에 소요되는 시간을 무시할 수는 없으니 CPU 코어 수 * 2 정도의 커넥션을 부여하고, 유효 스핀들 수를 무시하는 전략으로 커넥션 풀 사이즈를 조정하도록 하자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;결론&lt;/h4&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;오라클에서 정의한 공식에 기반하여 커넥션 풀사이즈를 정한 후 테스트를 진행하고,&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;나온 평균 시간을 기반으로 리틀의 법칙을 사용하여, 1000TPS를 구하기 위해 필요한 커넥션 수를 구한다.&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 리틀의 법칙을 통해 나온 커넥션 풀 사이즈의 갯수가, 오라클에서 정의한 공식에 의해 나온 커넥션 풀 사이즈 보다 적다면?&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;훌륭하게 최적화를 완료한 것으로 간주할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리 AWS의 RDS는 Vcpu2개 + 버스트 시 cpu 일시적으로 추가 기능을 제공한다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;버스트 기능은 의존하는 게 좋지 않으니 (2 + (0.5) * 2) 로 버스트 기능을 0.5 cpu로 간주하고, 커넥션 풀 사이즈를 5개로 조정하자.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 부하테스트를 시작해보자.&lt;/p&gt;
&lt;p style=&quot;color: #222222; 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;K6를 이용한 부하테스트&lt;/h3&gt;
&lt;div style=&quot;background-color: #1f1f1f; color: #cccccc; text-align: start;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// k6script-stress-1000.js&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'k6/http'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;check&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;}&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'k6'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;htmlReport&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;}&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'&lt;a href=&quot;https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js'&quot;&gt;https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js'&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;scenarios&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;transfer_scn&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;executor&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'ramping-arrival-rate'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;exec&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'transfer'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;startRate&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;timeUnit&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'1s'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;preAllocatedVUs&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;250&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;maxVUs&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;1500&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;stages&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;[&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'1m'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;},&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;400&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'1m'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;},&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;600&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'1m'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;},&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;800&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'1m'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;},&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'2m'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;},&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// 피크 유지&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'30s'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;},&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// 쿨다운&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;],&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;gracefulStop&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'30s'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;},&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;},&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;thresholds&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;http_req_failed&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;[&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'rate&amp;lt;0.01'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;],&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// 에러율 1% 미만이면 OK로 간주&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;http_req_duration&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;[&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'p(95)&amp;lt;300'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;],&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// p95 0.3s 미만이면 OK로 간주&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;},&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;};&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;__ENV&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;BASE_URL&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;/auth/signin`&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;({&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;loginId&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;__ENV&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;LOGIN_ID&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;__ENV&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;PASSWORD&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;}),&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;headers&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'Content-Type'&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'application/json'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;} }&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;check&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;, {&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'login 200'&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;});&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;()?.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;accessToken&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;check&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;, {&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'got accessToken'&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;!!&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;});&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;};&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;authHeaders&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;headers&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;Authorization&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;`Bearer&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'Content-Type'&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'application/json'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'Idempotency-Key'&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;36&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;),&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;},&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// Grafana/Prometheus에서 구분 보기 좋게&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;api&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'unknown'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;},&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;};&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;transfer&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;authHeaders&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;api&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'transfer'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;__ENV&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;BASE_URL&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;/transfer/phone-num`&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;({&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;phoneNumber&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;''&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;amount&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;''&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;1234&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;opts&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;check&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;, {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'transfer ok'&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;201&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;});&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;0.02&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;handleSummary&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;{&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'reports/transfer-test-1.html'&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;htmlReport&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;) };&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 K6 스크립트를 작성하고 점진적으로 부하를 올려&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1000TPS 에서 2분간 유지&lt;/b&gt;하도록 설정했다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; 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;b&gt;테스트 환경 구성&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AWS에 직접 트래픽을 쏴보고 성능을 측정하는 것이 가장 좋겠으나, 가난한 취준생은 그렇게 하지 못한다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; 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;1154&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpI8R7/btsP372JtQg/gNjhgcO4LqeeQG1uasU8SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpI8R7/btsP372JtQg/gNjhgcO4LqeeQG1uasU8SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpI8R7/btsP372JtQg/gNjhgcO4LqeeQG1uasU8SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpI8R7%2FbtsP372JtQg%2FgNjhgcO4LqeeQG1uasU8SK%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;1154&quot; height=&quot;166&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;brew로 cpulimit 툴을 설치하고 20%를 할당해주었다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내 로컬 컴퓨터는 10개의 CPU를 가지고 있고 배포 서버는 2개의 CPU를 가지고 있으니 20%로 설정해주었다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메모리는 배포 서버와 완전히 똑같이 설정해주었다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-Xms512m&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-Xmx512m&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-XX:MaxRAM=1024m&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프리티어 기준으로 1GB중 512를 힙 메모리에 할당해주었다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;cpu와 메모리 모두 제한을 걸었고, 마찬가지로 데이터베이스와 배포 서버와 최대한 비슷하게 환경을 구성해주었다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 K6를 돌려서 부하를 넣어보고 scouter로 성능을 측정해보자&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 툴을 사용하는 방법은 포스트가 너무 길어질 거 같으니 패스하겠다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; 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;b&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;1280&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZyfun/btsP2Grlvdn/Y6Q8vophOwCy2qKXn1MTK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZyfun/btsP2Grlvdn/Y6Q8vophOwCy2qKXn1MTK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZyfun/btsP2Grlvdn/Y6Q8vophOwCy2qKXn1MTK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZyfun%2FbtsP2Grlvdn%2FY6Q8vophOwCy2qKXn1MTK1%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;3428&quot; height=&quot;1662&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;테스트 결과가 너무 좋게 나왔다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1000TPS를 쏟았는데도 응답 시간의 평균치는 13ms가 나왔으며, 95%에 해당하는 응답 시간도 46ms로 아주 준수하다&lt;/b&gt;. 에러도 커넥션을 얻지 못해 실패한 11개만 발생했고, 이는 0.01% 안에 들기 때문에 통과 가능한 수치가 나왔다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;원인은 데이터베이스가 텅텅 비어있기 때문에 성능이 굉장히 좋게 나온 것이었다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리의 목표치에 맞게 더미 데이터를 만들고 다시 트래픽을 쏘기로 했다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;더미 데이터 생성&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리의 목표 회원 수는 10만명이다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉 유저 테이블에 10만 개의 더미데이터, 전자지갑 테이블에 10만개의 더미데이터를 넣어야한다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;거래 내역 테이블엔 100만 개의 데이터를 넣었으며, 회계 테이블엔 200만개의 데이터가 들어갔다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&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;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3390&quot; data-origin-height=&quot;1722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxkrTn/btsPQva9B47/WfnfvwaPKKkWCugKkxDKEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxkrTn/btsPQva9B47/WfnfvwaPKKkWCugKkxDKEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxkrTn/btsPQva9B47/WfnfvwaPKKkWCugKkxDKEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxkrTn%2FbtsPQva9B47%2FWfnfvwaPKKkWCugKkxDKEK%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;3390&quot; height=&quot;1722&quot; data-origin-width=&quot;3390&quot; data-origin-height=&quot;1722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;더미데이터를 넣고나니 확실히 성능이 엄청나게 떨어졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;평균 응답 시간은 498ms&lt;/b&gt;로 늘었으며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;95%에 해당하는 응답 시간은 1.96s&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;이제 95% 이내의 응답이 300ms 안에 처리되도록 성능 최적화를 진행해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;원인 분석&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QjB8G/btsPOoD9t1d/6WQMUjMosO4cjcsFVfwipK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QjB8G/btsPOoD9t1d/6WQMUjMosO4cjcsFVfwipK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QjB8G/btsPOoD9t1d/6WQMUjMosO4cjcsFVfwipK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQjB8G%2FbtsPOoD9t1d%2F6WQMUjMosO4cjcsFVfwipK%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;638&quot; height=&quot;140&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;140&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;1360&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daddSr/btsPOpiEl4p/chQt7zriH8Bb23A2PVYGBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daddSr/btsPOpiEl4p/chQt7zriH8Bb23A2PVYGBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daddSr/btsPOpiEl4p/chQt7zriH8Bb23A2PVYGBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaddSr%2FbtsPOpiEl4p%2FchQt7zriH8Bb23A2PVYGBK%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;1360&quot; height=&quot;210&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2ASMK/btsPO82bgve/8FEX2D3ZYkFcVkTQ0srgSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2ASMK/btsPO82bgve/8FEX2D3ZYkFcVkTQ0srgSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2ASMK/btsPO82bgve/8FEX2D3ZYkFcVkTQ0srgSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2ASMK%2FbtsPO82bgve%2F8FEX2D3ZYkFcVkTQ0srgSk%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;894&quot; height=&quot;226&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;소요 시간을 보면 쿼리의 직접적인 수행 시간은 굉장히 짧게 측정된다. 문제는 커넥션을 얻기까지의 대기 시간이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나가는 쿼리 수 자체를 줄여서 커넥션이 빨리 빨리 돌도록 쿼리 최적화를 하는 방식으로 1차 최적화를 진행해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현재는 api 호출 한 번에 총 12번의 쿼리&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;쿼리 수 줄이기&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;livescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Long firstLockMemberId = (member.getMemberId().compareTo(receiver.getMemberId()) &amp;lt; 0) ? member.getMemberId() :
    receiver.getMemberId();
Long secondLockMemberId =
    (member.getMemberId().compareTo(receiver.getMemberId()) &amp;lt; 0) ? receiver.getMemberId() :
       member.getMemberId();

Wallet firstLockedWallet = walletMapper.findByMemberIdWithLock(firstLockMemberId)
    .orElseThrow(() -&amp;gt; new CustomException(WALLET_NOT_FOUND, LogLevel.WARNING, null, common,
       &quot;조회 실패 ID: &quot; + firstLockMemberId));
Wallet secondLockedWallet = walletMapper.findByMemberIdWithLock(secondLockMemberId)
    .orElseThrow(() -&amp;gt; new CustomException(WALLET_NOT_FOUND, LogLevel.WARNING, null, common,
       &quot;조회 실패 ID: &quot; + secondLockMemberId));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;데드락을 피하기 위해 순차적으로 id가 낮은 전자지갑 먼저 락을 걸어주는 방식을 선택했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;순차적으로 락을 걸기 위해 쿼리를 두 번 날렸었는데, 쿼리 한 번에 순차적으로 락을 걸도록 쿼리를 수정하였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;BigDecimal senderNewBalance = senderWallet.getBalance().subtract(request.amount());
BigDecimal receiverNewBalance = receiverWallet.getBalance().add(request.amount());

walletMapper.updateBalance(senderWallet.getWalletId(), senderNewBalance);
walletMapper.updateBalance(receiverWallet.getWalletId(), receiverNewBalance);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;그 다음 쿼리 최적화 부분이다. 이 부분도 마찬가지로 락을 건 두 개의 wallet에 대해 총 두 번의 업데이트 쿼리가 나간다.&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;x86asm&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;@Override
public void saveWalletTransferTransaction(Wallet senderWallet, BigDecimal senderNewBalance, Wallet receiverWallet,
    BigDecimal receiverNewBalance, Member member, Member receiver, String transactionGroupId, BigDecimal amount) {
    Transaction senderTransaction = Transaction.builder()
       .walletId(senderWallet.getWalletId())
       .memberId(member.getMemberId())
       .transactionGroupId(transactionGroupId)
       .amount(amount)
       .beforeBalance(senderWallet.getBalance())
       .afterBalance(senderNewBalance)
       .transactionType(WALLET_TRANSFER)
       .counterPartyMemberId(receiver.getMemberId())
       .counterPartyWalletId(receiverWallet.getWalletId())
       .counterPartyName(receiver.getName())
       .status(SUCCESS)
       .build();

    Transaction receiverTransaction = Transaction.builder()
       .walletId(receiverWallet.getWalletId())
       .memberId(receiver.getMemberId())
       .transactionGroupId(transactionGroupId)
       .amount(amount)
       .beforeBalance(receiverWallet.getBalance())
       .afterBalance(receiverNewBalance)
       .transactionType(DEPOSIT)
       .counterPartyMemberId(senderWallet.getMemberId())
       .counterPartyWalletId(senderWallet.getWalletId())
       .counterPartyName(member.getName())
       .status(SUCCESS)
       .build();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;거래 내역을 각각 만든 후 쿼리를 두 번 날렸었으나, 이 부분도 마찬가지로 한 번의 쿼리로 insert하게 수정했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 송금 플로우에서 마지막 차대변 회계 테이블 저장 로직 부분이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 차 대변 각각의 회계 장부에 대해 벌크 인서트를 잘 구현해뒀었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;LedgerCode bankPayable = ledgerCodeMapper.findByName(&quot;BANK_PAYABLE&quot;)
    .orElseThrow(() -&amp;gt; new CustomException(LEDGER_CODE_NOT_FOUND, LogLevel.ERROR, null, Common.builder()
       .ledgerCode(&quot;BANK_PAYABLE&quot;)
       .srcIp(servletRequest.getRemoteAddr())
       .callApiPath(servletRequest.getRequestURI())
       .apiMethod(servletRequest.getMethod())
       .deviceInfo(servletRequest.getHeader(&quot;user-agent&quot;))
       .build()));

LedgerCode bankAsset = ledgerCodeMapper.findByName(&quot;BANK_ASSET&quot;)
    .orElseThrow(() -&amp;gt; new CustomException(LEDGER_CODE_NOT_FOUND, LogLevel.ERROR, null, Common.builder()
       .ledgerCode(&quot;BANK_ASSET&quot;)
       .srcIp(servletRequest.getRemoteAddr())
       .callApiPath(servletRequest.getRequestURI())
       .apiMethod(servletRequest.getMethod())
       .deviceInfo(servletRequest.getHeader(&quot;user-agent&quot;))
       .build()));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;회계코드를 조회해 올 때 쿼리가 두 번 나간다. 이 또한 한 번의 쿼리로 List로 받아오기로 하자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;결과&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;총 12회의 쿼리 -&amp;gt; 8회의 쿼리로 줄일 수 있었다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;인덱싱&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;List&amp;lt;Wallet&amp;gt; wallets = walletMapper.findByMemberIdsWithLock(ids);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;멤버 아이디를 기준으로 지갑을 조회해서 락을 거는 쿼리가 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 멤버 아이디에 인덱스를 걸어야할까?? 인덱스를 건다면 어떤 인덱스를 걸어야할지도 생각을 해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;삽입 케이스가 더 많은지 조회 케이스가 더 많은지 생각해보자.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전자지갑 생성은 모든 멤버 별로 딱 한 번밖에 이뤄지지 않는다. 즉 삽입은 현저히 낮다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;조회 같은 경우는 송금이 이뤄질 때마다, 또한 메인 페이지에 진입 할 때마다 조회가 이뤄진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 인덱스는 무조건 걸어야한다는 결론이 나온다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 이제 인덱스의 종류에 대해 생각해보자. 현재 내가 고려할 수 있는 인덱스는 두 개다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일반 인덱스 VS 유니크 인덱스&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 인덱스의 장단점은 어떻게 될까&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;유니크 인덱스&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;장점:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;where member_id = ? 같은 검색에서 최대 한 건이 확정이기 때문에 즉시 종료가 가능해, 조회 성능에서 약간 더 유리하다.&lt;/li&gt;
&lt;li&gt;update의 경우 (잔액 업데이트) 락을 걸어야 하는데, 유니크 키이므로 GAP Lock이 아닌 record Lock이 걸린다.&lt;br /&gt;따라서 대기, 교착 가능성이 낮아진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;쓰기 성능이 일반 인덱스에 비해 떨어진다. -&amp;gt; 중복 체크를 위해 리프 페이지를 모두 읽어야하기 때문이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;일반 인덱스&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;장점:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;삽입에 대해 중복조회를 하지 않아도 되기에 유니크 인덱스보다 성능이 뛰어나다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;락이 걸릴 때 Gap Lock이 걸릴 수 있어 혼잡 상황에서 P95가 더 튈 수 있다는 단점이 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 내 상황은 insert 쿼리가 현저히 낮고, 조회 및 Lock을 거는 상황이 엄청나게 많다.&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;즉, Unique 인덱스를 걸었을 때 훨씬 더 많은 이득을 얻을 수 있다.&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;Wallet 테이블의 member_id에 대해 유니크 인덱스를 걸어주도록 하자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;인덱스 교체 후 성능 비교&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 외래키에 자동으로 인덱스가 걸리는지 모르고 있었다. 지금 인덱스 성능 측정을하려고 살펴보니&lt;br /&gt;mysql에서 외래키에 자동으로 인덱스를 걸어준다고 한다. 따라서 기존 외래키 인덱스와 유니크 인덱스의 성능 차이를 비교하기로 했다.&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;pre id=&quot;code_1755879745096&quot; class=&quot;sql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;EXPLAIN FORMAT=JSON
SELECT wallet_id, member_id, balance, password
FROM wallet
WHERE member_id IN (123, 456)
FOR UPDATE;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 쿼리를 통해 JSON 형태로 쿼리 실행 계획을 살펴보자.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;{
  &quot;query_block&quot;: {
    &quot;select_id&quot;: 1,
    &quot;cost_info&quot;: {
      &quot;query_cost&quot;: &quot;3.84&quot;
    },
    &quot;table&quot;: {
      &quot;table_name&quot;: &quot;wallet&quot;,
      &quot;access_type&quot;: &quot;range&quot;,
      &quot;possible_keys&quot;: [
        &quot;member_id&quot;
      ],
      &quot;key&quot;: &quot;member_id&quot;,
      &quot;used_key_parts&quot;: [
        &quot;member_id&quot;
      ],
      &quot;key_length&quot;: &quot;8&quot;,
      &quot;rows_examined_per_scan&quot;: 2,
      &quot;rows_produced_per_join&quot;: 2,
      &quot;filtered&quot;: &quot;100.00&quot;,
      &quot;index_condition&quot;: &quot;(`test1`.`wallet`.`member_id` in (123,456))&quot;,
      &quot;cost_info&quot;: {
        &quot;read_cost&quot;: &quot;3.64&quot;,
        &quot;eval_cost&quot;: &quot;0.20&quot;,
        &quot;prefix_cost&quot;: &quot;3.84&quot;,
        &quot;data_read_per_join&quot;: &quot;96&quot;
      },
      &quot;used_columns&quot;: [
        &quot;wallet_id&quot;,
        &quot;member_id&quot;,
        &quot;balance&quot;,
        &quot;password&quot;
      ]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인덱스를 이용해 범위 스캔을 진행하며, 인덱스를 잘 사용하는 모습이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 실행 결과를 explain 해보자,&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;-&amp;gt; Index range scan on wallet using member_id over (member_id = 123) OR 
(member_id = 456), with index condition: (wallet.member_id in (123,456))  
(cost=3.84 rows=2) (actual time=0.0331..0.0364 rows=1 loops=1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인덱스 범위 스캔이 일어났고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실제 실행 시간은 31ms ~ 36ms가 나온다&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;50회 반복 수행에서 평균치는 43ms&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;ALTER TABLE wallet
    ADD UNIQUE KEY ux_wallet_member_id (member_id);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 유니크 키를 걸고 성능을 다시 측정해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;{
  &quot;query_block&quot;: {
    &quot;select_id&quot;: 1,
    &quot;cost_info&quot;: {
      &quot;query_cost&quot;: &quot;1.50&quot;
    },
    &quot;table&quot;: {
      &quot;table_name&quot;: &quot;wallet&quot;,
      &quot;access_type&quot;: &quot;range&quot;,
      &quot;possible_keys&quot;: [
        &quot;ux_wallet_member_id&quot;
      ],
      &quot;key&quot;: &quot;ux_wallet_member_id&quot;,
      &quot;used_key_parts&quot;: [
        &quot;member_id&quot;
      ],
      &quot;key_length&quot;: &quot;8&quot;,
      &quot;rows_examined_per_scan&quot;: 2,
      &quot;rows_produced_per_join&quot;: 2,
      &quot;filtered&quot;: &quot;100.00&quot;,
      &quot;index_condition&quot;: &quot;(`test1`.`wallet`.`member_id` in (123,456))&quot;,
      &quot;cost_info&quot;: {
        &quot;read_cost&quot;: &quot;1.30&quot;,
        &quot;eval_cost&quot;: &quot;0.20&quot;,
        &quot;prefix_cost&quot;: &quot;1.50&quot;,
        &quot;data_read_per_join&quot;: &quot;96&quot;
      },
      &quot;used_columns&quot;: [
        &quot;wallet_id&quot;,
        &quot;member_id&quot;,
        &quot;balance&quot;,
        &quot;password&quot;
      ]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;3.84가&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;나왔지만,&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;유니크 인덱스&lt;/b&gt;를 사용할 때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1.5가&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;나왔다 무려 추정치의 절반 이상이 감소했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 실행 시간도 비교해보자.&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;-&amp;gt; Index range scan on wallet using ux_wallet_member_id over (member_id = 123) 
OR (member_id = 456), with index condition: (wallet.member_id in (123,456))  
(cost=4.41 rows=2) (actual time=0.0257..0.0267 rows=1 loops=1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;유니크 인덱스를 사용했을 때 50회 반복 수행의&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;평균치는 32ms&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;평균지연이&amp;nbsp; 43ms -&amp;gt; 32ms로 약 25.6%가 개선됐다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;세 번째 부하테스트 결과&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쿼리 최적화와 인덱싱 이후의 부하테스트 결과다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2662&quot; data-origin-height=&quot;1662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Iagh/btsPP4ypl3R/fXZtCl2UoqKtfHFmnRKA51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Iagh/btsPP4ypl3R/fXZtCl2UoqKtfHFmnRKA51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Iagh/btsPP4ypl3R/fXZtCl2UoqKtfHFmnRKA51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Iagh%2FbtsPP4ypl3R%2FfXZtCl2UoqKtfHFmnRKA51%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;2662&quot; height=&quot;1662&quot; data-origin-width=&quot;2662&quot; data-origin-height=&quot;1662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;음,, 1000TPS까지 올렸을 때 Elapse가 확 튀는 부분들이 많이 보인다. 최대 1초까지도 올라가는 거 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문제를 살펴보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKoukg/btsPQzZfbc2/Kgtl3zBkK63Y05JR2cMHw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKoukg/btsPQzZfbc2/Kgtl3zBkK63Y05JR2cMHw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKoukg/btsPQzZfbc2/Kgtl3zBkK63Y05JR2cMHw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKoukg%2FbtsPQzZfbc2%2FKgtl3zBkK63Y05JR2cMHw1%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;1496&quot; height=&quot;208&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에도 쿼리 문제는 아니고 커넥션 풀 대기 문제였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아무래도 캐시를 적용해 데이터베이스 접근을 더 줄여야 할 거 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전체 리포트를 살펴보자&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3374&quot; data-origin-height=&quot;1722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNIm79/btsPRyMhI9D/IEEyDve4XROFSu7IQAL5Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNIm79/btsPRyMhI9D/IEEyDve4XROFSu7IQAL5Ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNIm79/btsPRyMhI9D/IEEyDve4XROFSu7IQAL5Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNIm79%2FbtsPRyMhI9D%2FIEEyDve4XROFSu7IQAL5Ok%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;3374&quot; height=&quot;1722&quot; data-origin-width=&quot;3374&quot; data-origin-height=&quot;1722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;에러율은&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;0.01%로&amp;nbsp; 통과&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;95%의 요청이 103ms이내에 통과&lt;/b&gt;했으므로 초기의 목표는 이룬 셈이다. 하지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;maximum RPS가 1419ms&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;까지 오르니 캐시도 적용해서 좀 더 최적화를 시켜보도록 하자.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;중간 결과&lt;/b&gt;&lt;/h4&gt;
&lt;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;498ms&amp;nbsp; -&amp;gt; 22.21ms : 95.54% 개선&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;95P&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;1966ms -&amp;gt; 103ms : 94.76% 개선&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최대 응답 시간&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;3634ms -&amp;gt; 1419ms : 60.95% 개선&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;캐시 적용&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 캐시를 적용 시킬만한 부분이 있는지 살펴보자,&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;Member receiver = memberService.getMemberByPhoneNumber(request.phoneNumber().replaceAll(&quot;-&quot;, &quot;&quot;),
    servletRequest);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;멤버 조회 부분에 캐시를 적용시킬 부분이 보인다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전화번호 &amp;rarr; (memberId, name) 은 변경이 드물며(번호/이름 변경 이벤트), 읽기 수요가 굉장히 많다 &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;캐시 적합&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특징을 살펴보면 -&amp;gt; 휴대폰 번호로 멤버를 조회하고 송금을 진행해야하니&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터 정합성이&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;중요하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다만 많은 변경이 없기에, 원천 저장소를 데이터베이스로 유지하는 &lt;/span&gt;&lt;b&gt;look aside 전략&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;PreFix는 다음과 같이 정했다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;groovy&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;MemberMapper:selectMemberByPhoneNumber:&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;이렇게 PREFIX를 정한다면, 같은 메서드를 사용하는 모든 곳에서 재사용을 할 수 있을 것이다.&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;TTL 설정&lt;/b&gt;: 솔직히 ttl 설정은 정답이 없다. 우리 애플리케이션의 특징과 어느정도의 재사용성이 있을지를 생각해보고 적용한 뒤 캐시 히트율을 최대로 끌어올릴 시간을 지속적으로 추적해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리 애플리케이션에서 생각을 해보자,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 TTL을 12시간으로 잡고 LFU 전략으로 사용하지 않는 키들을 삭제하며, 모니터링하는 방식으로 가자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;캐시 히트율은 85% 정도로 잡고 TTL을 추후 조정하도록 하겠다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또 캐시를 걸만 한 곳이 있나 찾아보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;ebnf&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;var codes = ledgerCodeMapper.findByNames(List.of(&quot;BANK_PAYABLE&quot;, &quot;BANK_ASSET&quot;));

LedgerCode bankPayable = codes.get(&quot;BANK_PAYABLE&quot;);
LedgerCode bankAsset = codes.get(&quot;BANK_ASSET&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;송금, 충전, 해외 송금 등 모든 내부 결제 관련 로직에서 다음과 같이 회계 코드를 검색하는 쿼리가 나간다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;회계 코드는 거의 모든 시간에 사용되고, 바뀔일이 전혀 없다. TTL을 24시간으로 잡고 하거나, 메모리 로드 시 바로 적재하고 시작해도 좋을 것 같다.&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;우선 TTL을 24시간으로 설정하고 적용시키도록 하자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;캐시를 적용하기 적합한 두 곳에 캐시 적용을 마쳤다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 성능 측정을 다시 시도해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;네 번째 부하 테스트 결과&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cy8u3i/btsPSPAhGQu/amBZ3mf88zkzwPI0IqfAU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cy8u3i/btsPSPAhGQu/amBZ3mf88zkzwPI0IqfAU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cy8u3i/btsPSPAhGQu/amBZ3mf88zkzwPI0IqfAU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcy8u3i%2FbtsPSPAhGQu%2FamBZ3mf88zkzwPI0IqfAU0%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;2650&quot; height=&quot;1378&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1000 TPS까지 잘 올라갔고, elapse도 튀는 거 없이 안정적&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3398&quot; data-origin-height=&quot;1610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5zSgm/btsPPMekypb/jbYURkCKQdZrTp4z9z0kK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5zSgm/btsPPMekypb/jbYURkCKQdZrTp4z9z0kK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5zSgm/btsPPMekypb/jbYURkCKQdZrTp4z9z0kK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5zSgm%2FbtsPPMekypb%2FjbYURkCKQdZrTp4z9z0kK0%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;3398&quot; height=&quot;1610&quot; data-origin-width=&quot;3398&quot; data-origin-height=&quot;1610&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에러율&lt;/b&gt;: 에러율은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;59/219000로 -&amp;gt; 0.027%&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;59개의 에러가 발생한 이유도 초당 1000 Request를 네트워크가 감당하지 못 해서 네트워크 단에서 타임아웃이 난 경우다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2168&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mIcNO/btsPSseiQOs/RETKtGlJxdSvDc5zHPn120/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mIcNO/btsPSseiQOs/RETKtGlJxdSvDc5zHPn120/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mIcNO/btsPSseiQOs/RETKtGlJxdSvDc5zHPn120/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmIcNO%2FbtsPSseiQOs%2FRETKtGlJxdSvDc5zHPn120%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;2168&quot; height=&quot;514&quot; data-origin-width=&quot;2168&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모든 에러가 다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;네트워크 에러&lt;/b&gt;로 발생한 것을 확인할 수 있다.&lt;/p&gt;
&lt;p 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;네트워크 에러를 제외한 나머지 에러는 발생하지 않았으므로 에러율도 0%라고 할 수 있다.&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;95% Rps&lt;/b&gt;: 95P 도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;13.98ms&lt;/b&gt;로 기존 목표였던 300ms보다 훨씬 뛰어난 성능으로 성능 최적화를 마칠 수 있었다.&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;Maximum Rps&lt;/b&gt;도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;348ms&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;&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;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;평균 응답시간 : 6.4ms =&amp;gt; 0.0064s, 그러나 평균 응답시간은 tomcat server + mysql server의 값이다. 이를 고려하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkI0WW/btsPSrU0sK4/V916rk0h6YLe4tncmv3N2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkI0WW/btsPSrU0sK4/V916rk0h6YLe4tncmv3N2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkI0WW/btsPSrU0sK4/V916rk0h6YLe4tncmv3N2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkI0WW%2FbtsPSrU0sK4%2FV916rk0h6YLe4tncmv3N2K%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;756&quot; height=&quot;490&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;응답 시간이 6ms가 나온 응답을 하나 찾아서 분석해봤다.&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;sql Time은 3ms&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;평균 응답시간을 3ms&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;목표 TPS&amp;nbsp; =&amp;gt; 1000&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;커넥션 풀 사이즈 =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1000 * 0.003 = 3&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;최소 3개의 동시 워커가 필요&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;아까 위쪽에서 하드웨어를 고려한 최적의 커넥션 풀사이즈는 5개였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리틀의 법칙을 기반으로 계산한 최소 커넥션 풀 사이즈는 3이 나왔으므로 통과&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;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 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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;498 ms&amp;nbsp; -&amp;gt; 22.21 ms -&amp;gt; 6.4 ms : 98.71% 개선&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;95P&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;1966 ms -&amp;gt; 103 ms -&amp;gt; 13.98 ms : 99.29% 개선&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최대 응답 시간&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;3634ms -&amp;gt; 1419ms -&amp;gt; 348ms : 90.42% 개선&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;평균 응답 시간을 498 ms 에서 6.4 ms로 총&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;98.71%의 성능 개선을 이뤄냈다 !&lt;/b&gt;&lt;/p&gt;</description>
      <category>☃️❄️개발일지, 트러블슈팅❄️☃️</category>
      <category>APM</category>
      <category>K6</category>
      <category>TPS</category>
      <category>돌깎기</category>
      <category>부하테스트</category>
      <category>성능 최적화</category>
      <category>성능테스트</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/210</guid>
      <comments>https://2junbeom.tistory.com/210#entry210comment</comments>
      <pubDate>Sat, 23 Aug 2025 01:23:04 +0900</pubDate>
    </item>
    <item>
      <title>[KB IT's Your Life] 파이널 프로젝트 - 시스템 아키텍쳐 설계 (Auto Scalilng, 무중단 배포, private subnet, nat gateway)</title>
      <link>https://2junbeom.tistory.com/208</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1951&quot; data-origin-height=&quot;1364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nWfQZ/btsPsWzB3rl/a9kllMRwK4Q80N9Wjsenj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nWfQZ/btsPsWzB3rl/a9kllMRwK4Q80N9Wjsenj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nWfQZ/btsPsWzB3rl/a9kllMRwK4Q80N9Wjsenj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnWfQZ%2FbtsPsWzB3rl%2Fa9kllMRwK4Q80N9Wjsenj0%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;1951&quot; height=&quot;1364&quot; data-origin-width=&quot;1951&quot; data-origin-height=&quot;1364&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;kb 부트캠프 마지막 프로젝트가 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포를 시작부터 전부 하고 가고 싶어 cicd랑 배포를 전부 마무리하고 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은행권 프로젝트이다보니, 보안을 최대한 신경써서 아키텍쳐를 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍쳐의 간단한 설명을 진행하도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아키텍쳐 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 우리가 돌리는 서버는 총 5개다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메인 서버 2개&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카프카 서버 1개&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그 전송 서버 1개&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;터널링용 서버 1개&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;메인서버, 카프카 서버, 로그 전송 서버, RDS, elastic cahce&lt;/b&gt;는 모두 &lt;b&gt;private 서브넷&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;private 서브넷은 인터넷에 연결되어 있지 않기 때문에, 외부에서 접속할 수도, 내부 트래픽을 외부로 내보낼 수도 없다.&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;하지만 우리가 로컬에서 테스트할 때 RDS나 메인서버에 접속을 할 수도 있으니 &lt;b&gt;Tunneling 전용 서버&lt;/b&gt;를 &lt;b&gt;public 서브넷&lt;/b&gt;에 파서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tunneling용 서버를 통해 프라이빗 서브넷에 배포한 서버들에 접근할 수 있도록 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 메인 서버는 외부 인터넷에 api를 요청해서 환율 정보나 상품 정보를 받아와야 하기 때문에 &lt;b&gt;Nat Gateway&lt;/b&gt;를 통해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프라이빗 서브넷에서 외부 인터넷으로 나가는 경로&lt;/b&gt;만 뚫어줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅 테이블에 내부 메인서버와 Internet Gateway를 연결시켜줬다.&lt;/p&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;KMS 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 및 캐시에 저장되는 모든 정보를 private subnet에 배포하긴 했으나, 추가적인 보안으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMS를 이용해 aws 내부망에 키를 생성하고 해당 키로 모든 데이터를 암호화 해서 저장시키도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 워치를 통해 수집하는 모든 로그 또한 내부망에서 kms를 통해 암호화된 후 전송된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배포 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 S3에 docker-compose.yml 파일과 .env 파일을 저장시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3에 접근할 수 있는 권한은 오로지 메인 서버에서만 가능하게 했고, 외부에서는 S3에 접근이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 배포가 이뤄질 때 launch template에서 S3의 환경변수 파일과 도커 컴포즈 파일을 가져와 그를 통해 war를 만들고 tomcat에 붙여 서버가 구동될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;런치 템플릿&lt;/h4&gt;
&lt;pre id=&quot;code_1753075932795&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

# --- 시스템 업데이트 및 필수 패키지 설치 ---
sudo yum update -y
sudo yum install -y docker aws-cli

# --- Docker 서비스 시작 및 부팅 시 자동 실행 설정 ---
sudo service docker start
sudo systemctl enable docker

# Docker 명령어를 ec2-user가 사용할 수 있도록 설정
sudo usermod -aG docker ec2-user

# --- 작업 디렉토리 준비 ---
DEPLOY_DIR=&quot;/home/ec2-user/deploy&quot;
sudo mkdir -p $DEPLOY_DIR
sudo chown ec2-user:ec2-user $DEPLOY_DIR

# --- S3에서 Docker Compose 파일과 .env 파일 다운로드 ---
BUCKET_NAME=&quot;버킷 이름&quot;
COMPOSE_FILE=&quot;도커 컴포즈 파일&quot;
ENV_FILE=&quot;환경변수 파일 이름&quot;
AWS_REGION=&quot;지역&quot;

# AWS CLI를 사용할 때 EC2 IAM 역할에 S3 접근 권한이 필요합니다.
aws s3 cp s3://$BUCKET_NAME/$COMPOSE_FILE $DEPLOY_DIR/ --region $AWS_REGION
aws s3 cp s3://$BUCKET_NAME/$ENV_FILE $DEPLOY_DIR/ --region $AWS_REGION

# 파일이 성공적으로 다운로드되었는지 확인
if [ ! -f &quot;$DEPLOY_DIR/$COMPOSE_FILE&quot; ] || [ ! -f &quot;$DEPLOY_DIR/$ENV_FILE&quot; ]; then
  echo &quot;Failed to download $COMPOSE_FILE or $ENV_FILE from S3. Exiting.&quot; &amp;gt;&amp;gt; /var/log/user-data.log
  exit 1
fi

# --- Docker Compose 설치 ---
sudo curl -L &quot;https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Docker Compose 버전 확인 (디버깅 용도)
docker-compose --version &amp;gt;&amp;gt; /var/log/user-data.log

# --- Docker Compose 실행 ---
cd $DEPLOY_DIR
sudo docker-compose pull
sudo docker-compose up -d &amp;gt;&amp;gt; $DEPLOY_DIR/deployment.log 2&amp;gt;&amp;amp;1
if [ $? -ne 0 ]; then
  echo &quot;Docker Compose failed to start. Check $DEPLOY_DIR/deployment.log for details.&quot; &amp;gt;&amp;gt; /var/log/user-data.log
  exit 1
fi

# --- Docker 이미지 정리 ---
sudo docker image prune -f

# --- 완료 로그 출력 ---
echo &quot;Deployment completed successfully on $(date)&quot; &amp;gt;&amp;gt; $DEPLOY_DIR/deployment.log&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;이렇게 런치 템플릿을 설정해서 S3에서 도커 컴포즈 파일과 환경변수 파일을 가져와 도커 이미지를 실행하도록 구성했다.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;깃허브 액션&lt;/h4&gt;
&lt;pre id=&quot;code_1753076029586&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Java CI/CD with Gradle

on:
  push:
    branches: [ &quot;main&quot; ]

permissions:
  contents: read

jobs:
  build-docker-image:
    runs-on: ubuntu-latest

    steps:
      # 소스코드 체크아웃
      - uses: actions/checkout@v3

      # JDK 17 설치
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      # Gradle 실행 권한 부여
      - name: Grant Execute Permission For Gradlew
        run: chmod +x gradlew

      # Gradle 캐싱
      - name: Gradle Caching
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      # Gradle 빌드 (WAR 파일 생성)
      - name: Build With Gradle
        run: ./gradlew clean build -x test

      # Docker 이미지 빌드 (WAR &amp;rarr; Tomcat 이미지에 복사)
      - name: docker image build
        run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/dolfin-main .

      # Docker Hub 로그인
      - name: docker login
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      # Docker Hub로 이미지 푸시
      - name: docker Hub push
        run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/dolfin-main

      # AWS 자격 증명 주입
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.ACCESS_KEY }}
          aws-secret-access-key: ${{ secrets.SECRET_KEY }}
          aws-region: ${{ secrets.REGION }}

      # S3로 Docker Compose 업로드
      - name: Upload Docker Compose to S3
        run: |
          aws s3 cp docker-compose.yml s3://${{ secrets.BUCKET_NAME }}/docker-compose.yml --region ${{ secrets.REGION }}

      # 오토스케일링 그룹 롤링 재배포
      - name: Trigger ASG Rolling Update
        run: |
          aws autoscaling start-instance-refresh \
            --auto-scaling-group-name ${{ secrets.ASG_NAME }} \
            --region ${{ secrets.REGION }} \
            --preferences '{&quot;MinHealthyPercentage&quot;: 50}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브 액션을 다음과 같이 구성해서 도커 이미지를 레포지토리에 올리고 오토 스케일링 그룹을 실행하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오토 스케일링의 재배포는 최소 가용율을 50%로 만들어서, 최소 1개의 서버가 가용성을 가지도록 구성했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 서버가 2개기 때문에 롤링 업데이트 방식을 활용한 무중단 배포가 구성됐다고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수평 스케일링 전략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수평 스케일링 전략은 cpu 활용률이 60% 이상이 되었을 때 클라우드 워치에서 감지해서 ec2 서버 개수를 한 개 늘리도록 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 공식문서 기반 cpu 활용률이 60% 이상이 되었을 때 수평 스케일링을 고려할 수 있다고 나와있다.&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://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/as-scaling-simple-step.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/as-scaling-simple-step.html?utm_source=chatgpt.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753076296619&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;Amazon EC2 Auto Scaling의 단계별 조정 및 단순 조정 정책 - Amazon EC2 Auto Scaling&quot; data-og-description=&quot;PercentChangeInCapacity - 그룹의 현재 용량을 지정된 퍼센트만큼 늘리거나 줄입니다. 양의 값은 용량을 늘리고, 음의 값은 용량을 줄입니다. 예: 현재 용량이 10개이고 조절이 10%인 경우, 이 정책이 수&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/as-scaling-simple-step.html?utm_source=chatgpt.com&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/as-scaling-simple-step.html?utm_source=chatgpt.com&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/as-scaling-simple-step.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/as-scaling-simple-step.html?utm_source=chatgpt.com&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;Amazon EC2 Auto Scaling의 단계별 조정 및 단순 조정 정책 - Amazon EC2 Auto Scaling&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;PercentChangeInCapacity - 그룹의 현재 용량을 지정된 퍼센트만큼 늘리거나 줄입니다. 양의 값은 용량을 늘리고, 음의 값은 용량을 줄입니다. 예: 현재 용량이 10개이고 조절이 10%인 경우, 이 정책이 수&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실시간 모니터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 모니터링은 로그 서버에서 클라우드 워치 SDK를 통해 바로 클라우드 워치 logs에 로그를 전송하도록 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그는 ERROR 알림이 1회 이상 울리면 바로 슬랙 알림을 울리도록,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WARNING 알림이 1초내 10회 이상 울리면 바로 슬랙 알림을 울리도록 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 망에서 전송되는 로그도 모두 KMS에서 관리하는 키를 사용해 암호화 하도록 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메인서버 또는 로그 서버 접근 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 터널링 서버를 통해 접근해야 하기 때문에 ssh를 두 번 사용해서 서버에 접근해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 터널링 서버의 퍼블릭 ip 주소를 확인한다.&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;1718&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p90EB/btsPqPPGRNL/aJyfG4bKYKKwS2gXLBZwW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p90EB/btsPqPPGRNL/aJyfG4bKYKKwS2gXLBZwW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p90EB/btsPqPPGRNL/aJyfG4bKYKKwS2gXLBZwW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp90EB%2FbtsPqPPGRNL%2FaJyfG4bKYKKwS2gXLBZwW0%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;1718&quot; height=&quot;638&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;638&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;다음 퍼블릭 주소를 확인하고 터널링 서버에 접속하기 위한 pem키를 이용해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh -i 명령어를 통해 터널링 서버에 접속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 접속하고 싶은 서버의 private 주소를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private V4ip 주소를 찾고 나서 해당 서버에 접속하기 위한 펨키를 이용해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh -i 명령어를 통해 접속하고 싶은 서버에 접속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에 있는 pem 키는 내가 터널링 서버에 복사해뒀으니 그냥 터널링 서버 접속해서 pem 키 사용해서 접속하면 된다.&lt;/p&gt;
&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;ssh -i [펨키 이름] ec2-user@[접속하고 싶은 주소]&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;로컬에서 RDS 접속 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엘라스틱 캐시는 가격이 비싸서 로컬에서는 연결 안 시킬 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDS도 가격 비싸서 로컬에서 RDS 연결하지 말고, 그냥 모니터링 툴로 데이터 잘 들어갔는지 정도 확인하는 용도로 사용하자.&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;1438&quot; data-origin-height=&quot;1188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/luO85/btsPqL7Gcd3/B0mACn3iBrouKDm13nZjA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/luO85/btsPqL7Gcd3/B0mACn3iBrouKDm13nZjA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/luO85/btsPqL7Gcd3/B0mACn3iBrouKDm13nZjA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FluO85%2FbtsPqL7Gcd3%2FB0mACn3iBrouKDm13nZjA0%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;1438&quot; height=&quot;1188&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1188&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;우선 인텔리제이에서 mysql 데이터 소스 생성하자&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;1608&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3hrSs/btsPr88Ru8o/dUja8eAfayDTzKiqQAjS11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3hrSs/btsPr88Ru8o/dUja8eAfayDTzKiqQAjS11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3hrSs/btsPr88Ru8o/dUja8eAfayDTzKiqQAjS11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3hrSs%2FbtsPr88Ru8o%2FdUja8eAfayDTzKiqQAjS11%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;1608&quot; height=&quot;1062&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1062&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;호스트에는 우리 rds 주소를 입력하고&lt;/p&gt;
&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;데이터베이스 이름은 dolfin 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 옆에 SSH/SSL 탭을 클릭해주세요&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;1686&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1eg9y/btsPrpbXgIc/EePTWWXqHnNuGXzDRDURC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1eg9y/btsPrpbXgIc/EePTWWXqHnNuGXzDRDURC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1eg9y/btsPrpbXgIc/EePTWWXqHnNuGXzDRDURC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1eg9y%2FbtsPrpbXgIc%2FEePTWWXqHnNuGXzDRDURC1%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;1686&quot; height=&quot;790&quot; data-origin-width=&quot;1686&quot; data-origin-height=&quot;790&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;use SSH tennel 클릭해주고, ssl configuration 오른쪽 ... 클릭해주세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KIzbe/btsPrbyh6eF/ELyla2voY6VNbLHRWk1oU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KIzbe/btsPrbyh6eF/ELyla2voY6VNbLHRWk1oU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KIzbe/btsPrbyh6eF/ELyla2voY6VNbLHRWk1oU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKIzbe%2FbtsPrbyh6eF%2FELyla2voY6VNbLHRWk1oU1%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;1720&quot; height=&quot;892&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;892&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 눌러서 페이지 하나 만들고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Host에 터널링 퍼블릭 ip 주소 넣어주세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 네임은 아마존 리눅스 기본 값 ec2-user로 입력해주세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프라이빗 키 파일에 다운받은 pem 키 넣어주시고 apply 누르고 나와서&lt;/p&gt;
&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;2052&quot; data-origin-height=&quot;1340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bePfIH/btsPscXv5W3/ZJKIVNKtGFfid5xqwKFUJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bePfIH/btsPscXv5W3/ZJKIVNKtGFfid5xqwKFUJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bePfIH/btsPscXv5W3/ZJKIVNKtGFfid5xqwKFUJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbePfIH%2FbtsPscXv5W3%2FZJKIVNKtGFfid5xqwKFUJ0%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;2052&quot; height=&quot;1340&quot; data-origin-width=&quot;2052&quot; data-origin-height=&quot;1340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 되네요 굿&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 연결하시면 됩니다.&lt;/p&gt;</description>
      <category>대외활동/KB IT's Your Life</category>
      <category>auto scalilng</category>
      <category>AWS</category>
      <category>NAT Gateway</category>
      <category>Private Subnet</category>
      <category>ssh tunneling</category>
      <category>무중단 배포</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/208</guid>
      <comments>https://2junbeom.tistory.com/208#entry208comment</comments>
      <pubDate>Mon, 21 Jul 2025 14:41:22 +0900</pubDate>
    </item>
    <item>
      <title>[KB IT's Your Life] AWS ec2, 도커 이미지를 이용한 간단한 CI-CD 구축 (Spring Legacy)</title>
      <link>https://2junbeom.tistory.com/207</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;ㅎㅇ&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 스프링 레거시 환경에서 war 파일 was에 붙여 배포 어떻게 하냐는 문의가 많아 그냥 간단하게 정리해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목표&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 기본적인 ci-cd를 구축해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vpc public subnet에 ec2를 올리고 ci-cd 작업까지 완료하는 걸 목표로 해보겠습니다. (public subnet은 아무나 접근 가능하기 때문에 보안상 좋지 않으니 추후 private으로 옮기는 것도 고려해보세요)&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;build.gradle 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 build.gradle 파일에 war 파일을 만드는 코드를 추가해줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;plugins {
    id 'java'
    id 'war'
}&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;그냥 플러그인에 war 넣으면 돼요 간단하죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이제 도커 파일을 만들어봅시다&lt;/h3&gt;
&lt;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;FROM tomcat:9.0.107-jdk17-temurin

# WAR 파일 복사
COPY build/libs/dolfin_back_log-1.0-SNAPSHOT.war /usr/local/tomcat/webapps/ROOT.war

EXPOSE 8080&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;저희 팀은 톰캣 9.0.107 버전을 쓰기 때문에 저렇게 정의해주었구요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 프로젝트 실행하면 build 폴더에 war파일이 생성될 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포를 위해선 톰캣에 war파일을 붙여야하니, war파일의 경로를 저기 적어주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옆에 붙어있는 /usr/local 이 부분은 빌드할 때 생성된 war파일을 톰캣에 ROOT.war라는 이름으로 복사하는 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커로 간단하게 톰캣 만들고 war파일 붙이는 걸 해봤구요, 이제 이 도커 파일로 이미지 만들어서 도커 허브 레포지토리에 집어넣어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도커 허브 접속해서 레포지토리 만들기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TBQhw/btsPpsgpGvA/ACbR1rz7OMvWgkjE7FHAI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TBQhw/btsPpsgpGvA/ACbR1rz7OMvWgkjE7FHAI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TBQhw/btsPpsgpGvA/ACbR1rz7OMvWgkjE7FHAI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTBQhw%2FbtsPpsgpGvA%2FACbR1rz7OMvWgkjE7FHAI0%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;1974&quot; height=&quot;1168&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;1168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 도커 허브 회원가입 하고 접속해서 바로 레포지토리 만들어 주고요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private으로 만들면 도커 허브 아이디랑 비밀번호 알고 있는 사람만 이미지 다운받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안을 위해서 private으로 하는 게 맞으나 설정 귀찮으신 분들은 그냥 public으로 하세요, 다만 레포 public이니까 다른 사람들 모르게 도커 허브 유저네임이랑 레포 이름은 숨겨야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기선 public으로 작업할게요&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;aws ec2 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 저희가 배포할 ec2를 만들어 줍시다.&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;2278&quot; data-origin-height=&quot;1432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dch8Hn/btsPp4eu0Ah/gyCeABKa0x4cslgBTGcz31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dch8Hn/btsPp4eu0Ah/gyCeABKa0x4cslgBTGcz31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dch8Hn/btsPp4eu0Ah/gyCeABKa0x4cslgBTGcz31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdch8Hn%2FbtsPp4eu0Ah%2FgyCeABKa0x4cslgBTGcz31%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;2278&quot; height=&quot;1432&quot; data-origin-width=&quot;2278&quot; data-origin-height=&quot;1432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마존 리눅스 사용해줄게요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스 기반으로 aws ec2 최적화 시켜서 만들어진 운영체제입니다 이거 쓰는 걸 추천 해여&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;1904&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kUUKJ/btsPqyl6DZk/eU0Ij5EA6F0qBf60C4RxZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kUUKJ/btsPqyl6DZk/eU0Ij5EA6F0qBf60C4RxZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kUUKJ/btsPqyl6DZk/eU0Ij5EA6F0qBf60C4RxZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkUUKJ%2FbtsPqyl6DZk%2FeU0Ij5EA6F0qBf60C4RxZ1%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;1904&quot; height=&quot;848&quot; data-origin-width=&quot;1904&quot; data-origin-height=&quot;848&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;키 만들어주세요, 서버 접속할 때 이 pem 키 사용해서만 접속 가능하게 제어할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 cicd 코드 작성할 때도 저 pem키 이용해서 접속할 거에요&lt;/p&gt;
&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;2026&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/47M2i/btsPrgkVuzg/C8qMQ7L0U3mJqO8zWn023k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/47M2i/btsPrgkVuzg/C8qMQ7L0U3mJqO8zWn023k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/47M2i/btsPrgkVuzg/C8qMQ7L0U3mJqO8zWn023k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F47M2i%2FbtsPrgkVuzg%2FC8qMQ7L0U3mJqO8zWn023k%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;2026&quot; height=&quot;860&quot; data-origin-width=&quot;2026&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 그룹은 방화벽입니다. 보안 그룹에서 들어오는 요청과 나가는 요청을 제어할 수 있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;979&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/140GP/btsPp2np0fr/o0b19ERA5qkdZjkRGzqiM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/140GP/btsPp2np0fr/o0b19ERA5qkdZjkRGzqiM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/140GP/btsPp2np0fr/o0b19ERA5qkdZjkRGzqiM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F140GP%2FbtsPp2np0fr%2Fo0b19ERA5qkdZjkRGzqiM1%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;3000&quot; height=&quot;979&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;979&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;저는 private 서브넷에 배포했고 ssh만 열어뒀어요 어차피 로그만 발송하는 서버라서 요청 들어올 일도 없고 아웃 바운드만 9094 카프카랑 cloudwatch 발송 가능하게 열어줬습니다.&lt;/p&gt;
&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;1358&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciwLbg/btsPre1GXml/Ym9VaSGKdCz1nT5kiVBz91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciwLbg/btsPre1GXml/Ym9VaSGKdCz1nT5kiVBz91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciwLbg/btsPre1GXml/Ym9VaSGKdCz1nT5kiVBz91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciwLbg%2FbtsPre1GXml%2FYm9VaSGKdCz1nT5kiVBz91%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;1358&quot; height=&quot;478&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;478&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;EBS 볼륨 크기랑 종류 정하는건데 하드 디스크나 SSD 생각하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 로그만 쏠 거라 기본 모델에 15GB만 해줄게요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리티어는 기본 모델에 30GB까지 이용가능합니다.&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;2218&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmSFtN/btsPqARKRxp/7WS6YIwFsNsc09JdVS8jR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmSFtN/btsPqARKRxp/7WS6YIwFsNsc09JdVS8jR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmSFtN/btsPqARKRxp/7WS6YIwFsNsc09JdVS8jR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmSFtN%2FbtsPqARKRxp%2F7WS6YIwFsNsc09JdVS8jR0%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;2218&quot; height=&quot;726&quot; data-origin-width=&quot;2218&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공적으로 생성이 되었군요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 로그 서버에 접속해서 도커 컴포즈 파일과 환경변수 파일을 올려줄 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSH로 EC2 접속하기&lt;/h3&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;1912&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/32OHn/btsPrsrS10S/KNmVL8MGRnybUBVFzNOUMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/32OHn/btsPrsrS10S/KNmVL8MGRnybUBVFzNOUMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/32OHn/btsPrsrS10S/KNmVL8MGRnybUBVFzNOUMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F32OHn%2FbtsPrsrS10S%2FKNmVL8MGRnybUBVFzNOUMK%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;1912&quot; height=&quot;456&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;456&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;여러분은 그냥 퍼블릭 서브넷에 배포한 메인 서버 퍼블릭 주소 복사하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDflzk/btsPpixO5ci/3oxVdDvYPLtOpuov95Gh5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDflzk/btsPpixO5ci/3oxVdDvYPLtOpuov95Gh5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDflzk/btsPpixO5ci/3oxVdDvYPLtOpuov95Gh5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDflzk%2FbtsPpixO5ci%2F3oxVdDvYPLtOpuov95Gh5K%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;358&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;358&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;아까 다운 받았던 pem키 있죠? 그 디렉토리까지 이동하고 나서, 다음 명령어 입력해주세요&lt;/p&gt;
&lt;pre id=&quot;code_1752921614879&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh -i [펨키 이름.pem] ec2-user@[public ip 주소]&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;처음 접속할 때는 뭐 막 뜰텐데 그냥 yes 입력하고 엔터 치면 접속할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위 사진처럼 접속 가능할 거구요 저는 프라이빗 서브넷으로 들어가야하니 한 번 더 ssh -i 해줄게요&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;EC2 서버 내부에서 밑 작업 해두기&lt;/h3&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;892&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn7suP/btsPrbw837R/n7BnrsBiKWVyvHu41UkMW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn7suP/btsPrbw837R/n7BnrsBiKWVyvHu41UkMW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn7suP/btsPrbw837R/n7BnrsBiKWVyvHu41UkMW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn7suP%2FbtsPrbw837R%2Fn7BnrsBiKWVyvHu41UkMW0%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;892&quot; height=&quot;96&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;96&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;우선 아무것도 없는 상태니까 저희가 작업할 폴더부터 mkdir로 만들어줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 dolfin-log로 폴더 만들었구요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vi 명령어로 docker compose 파일 만들어줄게여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1752921858196&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.8'

services:
  app:
    image: [도커 허브 유저네임]/[아까 우리가 팠던 도커 허브 레포지토리 이름]
    ports:
      - &quot;8080:8080&quot;
    env_file:
      - ./dolfin-log.env&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거 넣어주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지는 본인에 맞게 수정해주시구요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밑은 저희가 프로젝트에서 사용할 환경변수 파일입니다. 이름 기억해두세요&lt;/p&gt;
&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;똑같이 vi 명령어 써서 dolfin-log.env 파일 만들어 줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1752921972495&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;KAFKA_BROKER_SERVER=

AWS_ACCESS_KEY=
AWS_SECRET_KEY=&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;환경변수 파일은 그냥 이런식으로 여러분 application.properties에 쓸 ${AWS_ACCESS_KEY} 이름 똑같이 해서 값 넣어주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 환경변수 주입 받을 때 우선순위가 application.properties 파일 보다 .env 파일이 우선순위가 더 높아서 저기에 작성한 환경변수가 우선적으로 들어가서 실행될 거에요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lSubA/btsPpuSioHw/SkkUis6q0E0Gys7Cr8Rz90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lSubA/btsPpuSioHw/SkkUis6q0E0Gys7Cr8Rz90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lSubA/btsPpuSioHw/SkkUis6q0E0Gys7Cr8Rz90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlSubA%2FbtsPpuSioHw%2FSkkUis6q0E0Gys7Cr8Rz90%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;176&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;176&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;대충 뭐 ls 해서 환경변수 파일 잘 만들어졌나 확인 해봤구요 이제 도커 명령어 사용해야 되니까 도커랑 도커 컴포즈 다운받아 줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도커, 도커 컴포즈 다운&lt;/h3&gt;
&lt;pre id=&quot;code_1752922125053&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. Docker 설치
sudo yum update -y
sudo amazon-linux-extras enable docker
sudo yum install -y docker
sudo service docker start
sudo usermod -aG docker ec2-user

# 2. Docker Compose V2 설치 (plugin 방식)
mkdir -p ~/.docker/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.27.1/docker-compose-linux-x86_64 \
  -o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 이 명령어 실행하면 바로 다운됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본격적으로 깃허브 액션을 통한 cicd 코드를 작성해봅시다.&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;CI-CD 코드&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsYDmj/btsPpsOfcEB/zBkhI5HeyrMitruk9YLLk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsYDmj/btsPpsOfcEB/zBkhI5HeyrMitruk9YLLk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsYDmj/btsPpsOfcEB/zBkhI5HeyrMitruk9YLLk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsYDmj%2FbtsPpsOfcEB%2FzBkhI5HeyrMitruk9YLLk1%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;478&quot; height=&quot;222&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;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;name: Java CI/CD with Gradle

on:
  push:
    branches: [ &quot;main&quot; ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant Execute Permission For Gradlew
        run: chmod +x gradlew

      - name: Gradle Caching
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Build With Gradle
        run: ./gradlew build -x test

      - name: docker image build
        run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/dolfin-log .

      - name: docker login
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: docker Hub push
        run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/dolfin-log

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.event_name == 'push'
    steps:
      - name: Deploy to EC2 via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PEM_KEY }}
          proxy_host: ${{ secrets.SSH_TUN_HOST }}  #  퍼블릭 IP를 가진 중간 서버
          proxy_username: ${{ secrets.SSH_USER }}  # 터널링 서버의 사용자
          proxy_key: ${{ secrets.SSH_PEM_KEY }}
          port: 22
          script: |
            set -e

            echo &quot; [1] 아까 만든 dolfin-log 디렉토리로 이동&quot;
            cd ~/dolfin-log

            echo &quot; [2] 도커 이미지 pull&quot;
            docker pull ${{ secrets.DOCKERHUB_USERNAME }}/dolfin-log

            echo &quot; [3] 도커 컴포즈 재시작&quot;
            docker compose down
            docker compose up -d

            echo &quot; 배포 완료&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;p data-ke-size=&quot;size16&quot;&gt;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;일단&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;- name: docker image build
  run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/dolfin-log .

- name: docker login
  uses: docker/login-action@v2
  with:
    username: ${{ secrets.DOCKERHUB_USERNAME }}
    password: ${{ secrets.DOCKERHUB_PASSWORD }}

- name: docker Hub push
  run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/dolfin-log&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;이렇게 도커 이미지 사용하는 부분에 뒷 부분 보시면 dolfin-log 보일 겁니다. 저거 제 레포지토리 이름인데 여러분이 만든 레포 이름으로 바꿔주세요 전부다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;with:
  host: ${{ secrets.SSH_HOST }}
  username: ${{ secrets.SSH_USER }}
  key: ${{ secrets.SSH_PEM_KEY }}
  proxy_host: ${{ secrets.SSH_TUN_HOST }}  #  퍼블릭 IP를 가진 중간 서버
  proxy_username: ${{ secrets.SSH_USER }}  # 터널링 서버의 사용자
  proxy_key: ${{ secrets.SSH_PEM_KEY }}
  port: 22
  script: |&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;이 부분 저는 private subnet 사용해서 프록시 서버 둔 건데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분은 그냥 porxy로 시작하는 거 다 지워주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 설명은 안 할게요 궁금하시면 찾아오십셔&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경변수 설정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ci-cd 코드에&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;${{ secrets.SSH_HOST }}&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;/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;1660&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ArRR9/btsPqJnC54u/KMbx9pfB2lcnXDOT4KPy11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ArRR9/btsPqJnC54u/KMbx9pfB2lcnXDOT4KPy11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ArRR9/btsPqJnC54u/KMbx9pfB2lcnXDOT4KPy11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FArRR9%2FbtsPqJnC54u%2FKMbx9pfB2lcnXDOT4KPy11%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;1660&quot; height=&quot;888&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브 레포 -&amp;gt; setting -&amp;gt; 왼쪽 바에서 저거 찾은 다음에 레포지토리 시크릿 만들면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초록색 버튼 클릭해주세여&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;1112&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kUfx2/btsPp37HxYE/T97smEkss9gUBLsGM0jwU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kUfx2/btsPp37HxYE/T97smEkss9gUBLsGM0jwU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kUfx2/btsPp37HxYE/T97smEkss9gUBLsGM0jwU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkUfx2%2FbtsPp37HxYE%2FT97smEkss9gUBLsGM0jwU1%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;1112&quot; height=&quot;836&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 들어갈 값 전부 입력하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh_host는 퍼블릭 ip주소구요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh_pem_key는 저희 다운받았던 펨키 전문 다 집어넣으시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh_user는 유저네임인데 아마존 리눅스의 기본값은 &lt;b&gt;ec2-user&lt;/b&gt; 입니다. 이름 바꾼 거 아니시면 저 값 집어넣으면 돼요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터널링 서버 주소는 여러분 필요 없고 도커 허브 아이디 비밀번호 저기 입력하면 돼요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대망의 테스트&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;1206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVLSlr/btsPoXOhEiA/F90KUFjRFg9LecJVtUwb0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVLSlr/btsPoXOhEiA/F90KUFjRFg9LecJVtUwb0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVLSlr/btsPoXOhEiA/F90KUFjRFg9LecJVtUwb0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVLSlr%2FbtsPoXOhEiA%2FF90KUFjRFg9LecJVtUwb0k%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;221&quot; height=&quot;389&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;1206&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&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;1692&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tEzZh/btsPpvX3Rvp/wXySi0ZNn6j7L5Fs79b8vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tEzZh/btsPpvX3Rvp/wXySi0ZNn6j7L5Fs79b8vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tEzZh/btsPpvX3Rvp/wXySi0ZNn6j7L5Fs79b8vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtEzZh%2FbtsPpvX3Rvp%2FwXySi0ZNn6j7L5Fs79b8vK%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;1692&quot; height=&quot;618&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;618&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;이런 젠장 에러가 났군요, 에러를 살펴보니 gradlew 파일의 형식이 다른 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;war 배포의 차이 때문일까요?&lt;/p&gt;
&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;아무튼 gradlew 파일 형식이 다르면 다시 만들면 됩니다.&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;1328&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl3vPq/btsPqnE52wU/7F4yn37WHgWbKCC4bArqKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl3vPq/btsPqnE52wU/7F4yn37WHgWbKCC4bArqKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl3vPq/btsPqnE52wU/7F4yn37WHgWbKCC4bArqKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl3vPq%2FbtsPqnE52wU%2F7F4yn37WHgWbKCC4bArqKk%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;1328&quot; height=&quot;326&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;326&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;기존에 존재하던 gradlew 파일 rm으로 다 지워줬구요,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 gradle wrapper 실행해서 빌드 관련 파일 전부 다시 실행시켜 줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 생성된 gradlew, gradle 뭐 4개 파일 다시 깃 레포에 푸시해줬구요 결과를 살펴봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dc4PzS/btsPretTtec/KRovguQwzBTU3fMpIinGCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dc4PzS/btsPretTtec/KRovguQwzBTU3fMpIinGCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dc4PzS/btsPretTtec/KRovguQwzBTU3fMpIinGCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdc4PzS%2FbtsPretTtec%2FKRovguQwzBTU3fMpIinGCK%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;1624&quot; height=&quot;860&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공 ~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 모르니 다시 ec2 접속해서 docker ps로 잘 돌아가나 확인해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2150&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U1kzu/btsPpWVw3Sc/ss5j5i5azpp7JSYcINmPB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U1kzu/btsPpWVw3Sc/ss5j5i5azpp7JSYcINmPB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U1kzu/btsPpWVw3Sc/ss5j5i5azpp7JSYcINmPB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU1kzu%2FbtsPpWVw3Sc%2Fss5j5i5azpp7JSYcINmPB1%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;2150&quot; height=&quot;240&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2150&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;docker logs dolfin-log-app-1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5VcL7/btsPrtRRE3F/QaQTxzKdIDJLFvjX2QMFh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5VcL7/btsPrtRRE3F/QaQTxzKdIDJLFvjX2QMFh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5VcL7/btsPrtRRE3F/QaQTxzKdIDJLFvjX2QMFh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5VcL7%2FbtsPrtRRE3F%2FQaQTxzKdIDJLFvjX2QMFh0%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;1774&quot; height=&quot;204&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굿 로그 살펴보니 파티션도 할당 받고 카프카 브로커랑 연결 잘 됐네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;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;이건 보안 신경 아예 안 쓰고 기초적인 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;https랑 Auto Scailing 적용이랑 nat gateway 설정이랑 환경변수랑 도커 컴포즈 파일 s3에서 관리하고, rds 프라이빗 서브넷으로 분리하는 거 나중에 올릴 거니까 관심있으면 보세요&lt;/p&gt;</description>
      <category>대외활동/KB IT's Your Life</category>
      <category>AWS</category>
      <category>CD</category>
      <category>CI</category>
      <category>EC2</category>
      <category>github action</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/207</guid>
      <comments>https://2junbeom.tistory.com/207#entry207comment</comments>
      <pubDate>Sat, 19 Jul 2025 20:03:42 +0900</pubDate>
    </item>
    <item>
      <title>[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 3 (redis Shed lock, @Scheduled, 분산락)</title>
      <link>https://2junbeom.tistory.com/206</link>
      <description>&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://2junbeom.tistory.com/205&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://2junbeom.tistory.com/205&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748418107127&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;[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 2 (AWS SES로의 마이그레이션, 적절한 스&quot; data-og-description=&quot;https://2junbeom.tistory.com/204이전 포스트 [개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 1 (멀티 스레딩 동시성 문제)spring batch 도입현재 진행 중인 프로젝트에서 이메일을 대량으로 발송&quot; data-og-host=&quot;2junbeom.tistory.com&quot; data-og-source-url=&quot;https://2junbeom.tistory.com/205&quot; data-og-url=&quot;https://2junbeom.tistory.com/205&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iXb6I/hyYYvOad2y/vYzKKw8J4xfu5beg6mFCoK/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/SXquA/hyY0mhZC3Q/tUhHk4erIibAI9Rl77lz7K/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/hLXaM/hyYYA9KFGn/BpJSsIUdPuMSTUBUcOA4xk/img.png?width=944&amp;amp;height=264&amp;amp;face=0_0_944_264&quot;&gt;&lt;a href=&quot;https://2junbeom.tistory.com/205&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://2junbeom.tistory.com/205&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iXb6I/hyYYvOad2y/vYzKKw8J4xfu5beg6mFCoK/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/SXquA/hyY0mhZC3Q/tUhHk4erIibAI9Rl77lz7K/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/hLXaM/hyYYA9KFGn/BpJSsIUdPuMSTUBUcOA4xk/img.png?width=944&amp;amp;height=264&amp;amp;face=0_0_944_264');&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;[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 2 (AWS SES로의 마이그레이션, 적절한 스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://2junbeom.tistory.com/204이전 포스트 [개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 1 (멀티 스레딩 동시성 문제)spring batch 도입현재 진행 중인 프로젝트에서 이메일을 대량으로 발송&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;2junbeom.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분산락 도입 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 서비스는 여러 대의 서버가 작동하는 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새벽 3시에 자동으로 이메일 전송을 하게 만드려면 @&lt;span style=&quot;background-color: #fafafa; color: #4078f2; text-align: start;&quot;&gt;Scheduled&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;background-color: #fafafa; color: #4078f2; text-align: start;&quot;&gt;Scheduled&lt;/span&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;Shed Lock이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가볍게 우리가 현재 사용중인 redis를 활용해 분산락을 만드려고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산락을 찾아보던 중 shed lock이란 것을 발결하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1748418396000&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;shed lock은 분산 환경에서 스케줄링된 작업이 여러 서버에서 동시에 실행되지 않도록 막기위해
사용되는 락 라이브러리이다.

작업 전 redis에 락을 걸고 (insert into lock_table)
해당 락을 얻은 서버만 작업을 실행하도록 도와준다.

작업 이후 락을 해제한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 레디스에 키 밸류 쌍으로 다음과 같이 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SET&amp;nbsp;emailJobLock&amp;nbsp;&quot;server-1&quot;&amp;nbsp;NX&amp;nbsp;PX&amp;nbsp;600000&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;emailJobLock&quot; : 락의 이름 (고유 key)&lt;/li&gt;
&lt;li&gt;&quot;server-1&quot; : 락을 소유한 서버 식별자 (hostName 등)&lt;/li&gt;
&lt;li&gt;NX : 키가 없을 때만 설정 (기존에 락이 있으면 무시)&lt;/li&gt;
&lt;li&gt;PX&amp;nbsp;600000&amp;nbsp;:&amp;nbsp;600,000ms&amp;nbsp;(10분)&amp;nbsp;후&amp;nbsp;락&amp;nbsp;자동&amp;nbsp;만료&amp;nbsp;(TTL,&amp;nbsp;안전장치)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;여기서 NX를 통해 Key를 먼저 얻지 못한 서버에서는 작동을 못하게 만드는 원리이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Shed Lock 흐름 정리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&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;SET&amp;nbsp;my-job-lock&amp;nbsp;server-1&amp;nbsp;NX&amp;nbsp;PX&amp;nbsp;10000&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성공시 해당 server-1에서 코드 실행&lt;/li&gt;
&lt;li&gt;실패시 서버 작업 패스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;작업이 끝나면 락 삭제&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;// redis shedlock
implementation 'net.javacrumbs.shedlock:shedlock-spring:5.16.0'
implementation 'net.javacrumbs.shedlock:shedlock-provider-redis-spring:5.16.0'
&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 코드를 살펴보자&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = &quot;PT10M&quot;) // 락 유지 시간 (10분)
public class RedisShedLockConfig {
    @Bean
    public LockProvider lockProvider(RedisConnectionFactory redisConnectionFactory) {
        return new RedisLockProvider(redisConnectionFactory);
    }
}&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;@EnableScheduling으로 스케쥴러가 동작하게 해주었고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EnableSchedulerLock(defaultLockAtMostFor&amp;nbsp;=&amp;nbsp;&quot;PT10M&quot;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 통해 shed lock을 활성화 해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;defaultLockATMostFor은 락의 최대 유지 시간을 설정하는 메서드다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10분으로 설정해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LockATMostFor 값은 레디스의 PX 값에 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Scheduled(cron = &quot;0 0 3 * * *&quot;) // 매일 새벽 3시
@SchedulerLock(name = &quot;emailNotificationJobLock&quot;, lockAtMostFor = &quot;PT10M&quot;) // 락 10분간 유지
public void executeIfScheduled() {
    String today = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
    String key = &quot;email_batch_&quot; + today;

    if (checkReserved(key)) {
        return; // 예약된 작업이 없으면 실행 안 함
    }

    try {
        JobParameters params = new JobParametersBuilder()
                .addLong(&quot;time&quot;, System.currentTimeMillis())
                .toJobParameters();

        jobLauncher.run(emailNotificationJob, params);

        log.info(&quot;신규 지원 이메일 배치 작업 실행 완료&quot;);
    } catch (Exception e) {
        log.error(&quot;신규 지원 이메일 배치 작업 실행 실패&quot;, e);
        throw new ApplyEmailBatchJobFailException();
    } finally {
        redisUtil.delete(key); // 중복 실행 방지
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@SchedulerLock(name = &quot;emailNotificationJobLock&quot;, lockAtMostFor = &quot;PT10M&quot;) // 락 10분간 유지&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;name은 레디스에 들어갈 key값을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lockAtMostFor은 아까 말했던 TTL설정 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자가 하나 더 숨어있긴 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LockAtLeastFor&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;LockAtLeastFor 인자는 설정해주지 않았다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;운영진의 이메일 대량 발송 요청&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 사이트의 로직은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영진이 이메일 대량 발송 요청 보냄 -&amp;gt; 레디스에 해당하는 날짜의 키 밸류를 설정&lt;/p&gt;
&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;private Boolean checkReserved(String key) {
    Boolean scheduled = redisUtil.hasKey(key);
    if (!Boolean.TRUE.equals(scheduled)) {
        return true; // 예약된 작업이 없으면 실행 안 함
    }
    return false;
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케쥴러는 매일 실행되는 코드기 때문에 위의 검증 작업이 꼭 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 문제가 하나 발생하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케쥴러는 새벽 3시에 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 00:00 ~ 02:59에 들어온 요청은 오늘 날짜로 레디스에 저장하면 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;03:01 ~ 이후에 들어온 요청은 다음 날 새벽 03:00에 실행되는 것이니 레디스에 날짜를 하루 추가해서 저장해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;if (now.isBefore(LocalTime.of(3, 0))) {
    targetDate = LocalDate.now(); // 오늘 03:00 예약
} else {
    targetDate = LocalDate.now().plusDays(1); // 내일 03:00 예약
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 shed lock을 활용한 간단한 동시성 제어 코드를 작성해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;미구현된 부분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 메일 발송에 실패한 메일들에 대한 처리가 어떻게 이뤄질지 정해지지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 생각하는 건 DLQ를 만들고 이메일 전송에 실패한 메일들만 전부 DLQ로 보내는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;context -&amp;gt; {
        item.changeStatus(EmailStatus.FAILED);
        failedItems.add(item);
        log.error(&quot;DLQ 처리 - {}: {}&quot;, item.getEmail(),
                Objects.requireNonNull(context.getLastThrowable()).getMessage());
        return null;
    });
} catch (Exception e) {
    item.changeStatus(EmailStatus.FAILED);
    failedItems.add(item);
    log.error(&quot;Unexpected failure: {}&quot;, item.getEmail(), e);
}&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;writer의 위 부분은 지수 백오프 3회 재시도 이후, 실패한 행에 대한 처리가 이뤄지는 부분인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 DLQ 전송 후 삭제 로직을 추가하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JobLauncher.run 이후에 모든 행을 전부 삭제하는 로직만 추가하면 안정적으로 모든 email을 처리할 수 있을 것이라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>☃️❄️개발일지, 트러블슈팅❄️☃️</category>
      <category>Redis</category>
      <category>Scheduled</category>
      <category>shed lock</category>
      <category>분산락</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/206</guid>
      <comments>https://2junbeom.tistory.com/206#entry206comment</comments>
      <pubDate>Wed, 28 May 2025 17:29:34 +0900</pubDate>
    </item>
    <item>
      <title>[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 2 (AWS SES로의 마이그레이션, 적절한 스레드 개수와 청크 사이즈 계산, 쿼리 최적화, 스레드 동적 생성)</title>
      <link>https://2junbeom.tistory.com/205</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://2junbeom.tistory.com/204&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://2junbeom.tistory.com/204&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스트&lt;/p&gt;
&lt;figure id=&quot;og_1748396046950&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;[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 1 (멀티 스레딩 동시성 문제)&quot; data-og-description=&quot;spring batch 도입현재 진행 중인 프로젝트에서 이메일을 대량으로 발송하는 기능을 개발해야했다. 대량 메일 발송 기능을 위해 spring batch 기능을 도입했다. spring batch 도입 이유는 다음과 같다.메모&quot; data-og-host=&quot;2junbeom.tistory.com&quot; data-og-source-url=&quot;https://2junbeom.tistory.com/204&quot; data-og-url=&quot;https://2junbeom.tistory.com/204&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bsOy4A/hyYW6gMeSS/OBW1FHXBBp4uIhQ0LHKeh1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/dudDeN/hyYYEj1fjD/0Yc98dh0uSDrAvzzD1huc1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/Niw6K/hyY075UQGY/bKUMwLLKSoBOQHKk6sKPok/img.png?width=402&amp;amp;height=439&amp;amp;face=0_0_402_439&quot;&gt;&lt;a href=&quot;https://2junbeom.tistory.com/204&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://2junbeom.tistory.com/204&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bsOy4A/hyYW6gMeSS/OBW1FHXBBp4uIhQ0LHKeh1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/dudDeN/hyYYEj1fjD/0Yc98dh0uSDrAvzzD1huc1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/Niw6K/hyY075UQGY/bKUMwLLKSoBOQHKk6sKPok/img.png?width=402&amp;amp;height=439&amp;amp;face=0_0_402_439');&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;[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 1 (멀티 스레딩 동시성 문제)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;spring batch 도입현재 진행 중인 프로젝트에서 이메일을 대량으로 발송하는 기능을 개발해야했다. 대량 메일 발송 기능을 위해 spring batch 기능을 도입했다. spring batch 도입 이유는 다음과 같다.메모&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;2junbeom.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SES 선택 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대량 메일 발송 서비스에서 가장 유명한 서비스는 두 개가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. AWS SES&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. SendGrid&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 장단점을 비교해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SES
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;비용 저렴 : 1000건 당 $0.1&lt;/li&gt;
&lt;li&gt;기존 배포 및 서비스를 모두 AWS에서 사용하고 있었기에 통합이 쉽고&lt;br /&gt;로그 관리에 용이함&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;저수준의 UI&lt;/li&gt;
&lt;li&gt;이메일 템플릿은 직접 구현해야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SendGrid
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;UI가 뛰어나다&lt;/li&gt;
&lt;li&gt;이메일 템플릿을 제공한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;비용이 비싸다: 1000건당 $1 ~ $3&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우리 서비스는 AWS 인프라를 기반으로 구축되어 있고 비용을 절감해야하기 때문에 AWS SES를 선택했다.&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;SES 도메인 및 이메일 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ses에서 기존에 rotue 53에 등록해둔 도메인과&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;초기 이메일 발송 제한은 하루 200건 밖에 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 발송 제한을 해제하기 위해선 AWS에 발신 한도 증설 요청을 보내야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1748396640710&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Subject: Additional Information Regarding Our Email Practices

Hello,

Thank you for your response.

Our service sends email notifications exclusively to users who have explicitly opted in to receive updates. Specifically, we send announcements twice a year to inform users about the opening of a new application/support cycle.

In addition, our system handles routine transactional emails such as authentication code delivery and welcome messages during the signup process.

Here are additional details about our email practices:

Frequency:

Transactional Emails: Approximately 40&amp;ndash;50 emails per day are sent for user authentication codes and signup confirmations.

Promotional Emails: Sent only twice a year (every 6 months) for recruitment announcements.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 뭐 이런 느낌으로 빈도, 발송 내용, 구독자에게만 보내는 건지 구독 해지는 어떻게 하는지 등등을 적어서 보내면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3일 정도 기다린 후에, 하루 5만 건, 초당 14개의 메일 전송이 가능하다는 메일을 받았다!&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;942&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beoeoZ/btsOf3NWUbx/WHvevHnywRGwqynA7aoz41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beoeoZ/btsOf3NWUbx/WHvevHnywRGwqynA7aoz41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beoeoZ/btsOf3NWUbx/WHvevHnywRGwqynA7aoz41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeoeoZ%2FbtsOf3NWUbx%2FWHvevHnywRGwqynA7aoz41%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;942&quot; height=&quot;248&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 코드를 작성해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 사용하던 AWS BACKEND IAM USER에 SESFULLACCESS 권한을 부여해줬고,&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
public class SESMailConfig {

    @Value(&quot;${cloud.aws.credentials.access-key}&quot;)
    private String accessKey;
    @Value(&quot;${cloud.aws.credentials.secret-key}&quot;)
    private String secretKey;

    @Bean
    public AmazonSimpleEmailService amazonSimpleEmailService() {
        final BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        final AWSStaticCredentialsProvider awsStaticCredentialsProvider = new AWSStaticCredentialsProvider(
                basicAWSCredentials);

        return AmazonSimpleEmailServiceClientBuilder.standard()
                .withCredentials(awsStaticCredentialsProvider)
                .withRegion(&quot;ap-northeast-2&quot;)
                .build();
    }
}&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;/p&gt;
&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;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;@Slf4j
@Component
@RequiredArgsConstructor
public class SESMailUtil {

    private final AmazonSimpleEmailService amazonSimpleEmailService;

    @Value(&quot;${spring.mail.username}&quot;)
    private String mail;

    public SendRawEmailRequest getSendRawEmailRequest(String title, String content, String receiver, String html)
            throws MessagingException, IOException {

        Session session = Session.getDefaultInstance(getProperties());
        MimeMessage message = new MimeMessage(session);

        // Define mail title
        message.setSubject(title);

        // Define mail Sender
        message.setFrom(new InternetAddress(mail));

        message.setHeader(&quot;X-SES-CONFIGURATION-SET&quot;, &quot;my-first-configuration-set&quot;);

        // Define mail Receiver
        message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(receiver));

        // Create a multipart/alternative child container.
        MimeMultipart msg_body = new MimeMultipart(&quot;alternative&quot;);

        // Create a wrapper for the HTML and text parts.
        MimeBodyPart wrap = new MimeBodyPart();

        // Define the text part.
        MimeBodyPart textPart = new MimeBodyPart();
        textPart.setContent(content, &quot;text/plain; charset=UTF-8&quot;);

        // Define the HTML part.
        MimeBodyPart htmlPart = new MimeBodyPart();
        htmlPart.setContent(html, &quot;text/html; charset=UTF-8&quot;);

        // Add the text and HTML parts to the child container.
        msg_body.addBodyPart(textPart);
        msg_body.addBodyPart(htmlPart);

        // Add the child container to the wrapper object.
        wrap.setContent(msg_body);

        // Create a multipart/mixed parent container.
        MimeMultipart msg = new MimeMultipart(&quot;mixed&quot;);

        // Add the parent container to the message.
        message.setContent(msg);

        // Add the multipart/alternative part to the message.
        msg.addBodyPart(wrap);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        message.writeTo(outputStream);
        RawMessage rawMessage = new RawMessage(ByteBuffer.wrap(outputStream.toByteArray()));
        return new SendRawEmailRequest(rawMessage);

    }

    private Properties getProperties() {
        Properties props = System.getProperties();
        props.put(&quot;mail.transport.protocol&quot;, &quot;smtp&quot;);
        props.put(&quot;mail.smtp.port&quot;, 587);
        props.put(&quot;mail.smtp.starttls.enable&quot;, &quot;true&quot;);
        props.put(&quot;mail.smtp.auth&quot;, &quot;true&quot;);
        return props;
    }

}&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;이제 아래의 방식으로 SES Service SDK를 통해 메일을 보낼 수 있다.&lt;/p&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 AmazonSimpleEmailService emailService;


try {
            SendRawEmailRequest request = sesMailUtil.getSendRawEmailRequest(
                    &quot;TAVE 4기 모집 안내&quot;,
                    &quot;TAVE 4기 모집이 시작되었습니다. 자세한 내용은 이메일 본문을 확인해주세요.&quot;,
                    recipient,
                    getApplyNotificationTemplate()
            );
         	emailService.sendRawEmail(request);
}&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SES 초당 최대 전송 가능 수는 14개다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레딩으로 병렬 처리를 하면 초당 14개를 넘기는 경우가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 메일 발송에 실패하게 되는데, 우리는 단 한 건의 메일도 실패하지 않는 것을 목표로 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 적절한 스레드 갯수를 구해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Little%27s_law&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Little%27s_law&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1748398030054&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ThreadPoolSize = (Max TPS &amp;times; Processing Time per Request) / (1 second)&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;여기서 Max TPS는 초당 14건 즉 14이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일 한 건당 전송 속도는 이제 측정해보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메일 한 건당 전송 속도&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yFMCN/btsOgr17Ceh/NTm61g3xCVzAI6g2e0bJn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yFMCN/btsOgr17Ceh/NTm61g3xCVzAI6g2e0bJn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yFMCN/btsOgr17Ceh/NTm61g3xCVzAI6g2e0bJn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyFMCN%2FbtsOgr17Ceh%2FNTm61g3xCVzAI6g2e0bJn0%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;944&quot; height=&quot;264&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;264&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;scouter로 바로 측정했고 10건의 평균 Elapse를 구했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균 elapse는 182ms&lt;/p&gt;
&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;TPS가 14&lt;/b&gt;인 상황에서 &lt;b&gt;elapse가 182ms&lt;/b&gt;니까&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.548개&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;스레드 수는 3개&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;스레드가 3개일 때 초당 16건&lt;/b&gt;까지 메일을 보낼 수도 있으니, 지수 백오프를 도입해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;초당 14회가 넘는 요청에 대해선 지수적으로 대기 후 재시도를 하도록 로직을 구성&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우린 이를 통해 &lt;b&gt;최대 효율료, 실패 없이 많은 이메일&lt;/b&gt;을 보낼 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scouter 킨 김에 &lt;b&gt;적절한 chunk 사이즈&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;적절한 chunk Size 구하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 사용하는 서버는 aws t3.micro다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 사이즈는 1GB이고, 힙 메모리는 512mb로 설정돼 있다.&lt;/p&gt;
&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;GC 여유 공간을 위해 100mb를 남겨두고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring + hibrenate 등등을 위해 약 100mb를 더 빼자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여유 공간은 약 300mb, 새벽 3시이지만 다른 요청이 들어올 수도 있으니 여유 50mb 정도 남겨두고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙 메모리를 250mb까지 사용하는 걸 목표로 잡고 적절한 청크 사이즈를 정해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oIGJX/btsOfuFvcvl/UqLZpm2iVcUOlvU6WqP8S1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oIGJX/btsOfuFvcvl/UqLZpm2iVcUOlvU6WqP8S1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oIGJX/btsOfuFvcvl/UqLZpm2iVcUOlvU6WqP8S1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoIGJX%2FbtsOfuFvcvl%2FUqLZpm2iVcUOlvU6WqP8S1%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;257&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 라이브러리가 많아서 그런지 아무 요청이 없는 상태에서 heap used가 150mb 정도로 측정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙 메모리 사용 목표치를 200mb로 설정하고 다시 수행,&lt;/p&gt;
&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;(청크 사이즈는 클 수록 좋다. - &amp;gt; 스레드 전환 비용, 디비 커넥션 횟수, 커밋 등 I/O작업 횟수 감소 등등 I/O 작업을 줄여야 하니 청크를 크게 잡는 게 이득이라 판단했다.)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 시작 청크 사이즈를 500으로 잡아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;@Bean
public Step emailNotificationStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
    return new StepBuilder(&quot;emailNotificationStep&quot;, jobRepository)
            .&amp;lt;EmailNotification, EmailNotification&amp;gt;chunk(500, transactionManager)
            .reader(emailReader)
            .writer(emailWriter)
            .build();
}

@Bean
public Partitioner idRangePartitioner() {
    return new IdRangePartitioner(emailNotificationRepository);
}

@Bean
public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(3);
    executor.setMaxPoolSize(3);
    executor.setQueueCapacity(100);
    executor.setThreadNamePrefix(&quot;batch-thread-&quot;);
    executor.initialize();
    return executor;
}&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;자 청크 사이즈는 500이고, 스레드 개수는 아까 위에서 계산한대로 3개로 설정해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z3NVj/btsOg2VjlcN/RGp8oKYSOPQv03jhspjE71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z3NVj/btsOg2VjlcN/RGp8oKYSOPQv03jhspjE71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z3NVj/btsOg2VjlcN/RGp8oKYSOPQv03jhspjE71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ3NVj%2FbtsOg2VjlcN%2FRGp8oKYSOPQv03jhspjE71%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;621&quot; height=&quot;330&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 253mb 약 100mb 정도를 차지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드가 3개이므로 한 번에 1500 청크가 메모리에 올라가는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1500 청크에 100mb이므로, 200mb까지 사용가능하다 하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3000 청크까지 가능하다. 3000 청크면 스레드 별로 1000 chunk를 할당해주면 되니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크의 개수는 1000개로 할당해주자.&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;Jpa Item Reader와 파티셔닝 선택 이유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우선 사용 가능한 Reader의 종류는 다음과 같다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JpaPagingItemReader - JPA기반 페이징 조회,&amp;nbsp; 스레드 세이프하다.&lt;/li&gt;
&lt;li&gt;JdbcPagingItemReader - JDBC기반 페이징 조회, 스레드 세이프&lt;/li&gt;
&lt;li&gt;ListItemReader - 메모리상의 List를 순서대로 읽음, 스레드 세이프 하지 않으며 필요한 데이터를 모두 가져와 메모리에 올려두고 사용한다. 데이터가 많다면 Out Of Meory의 원인이 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리 서비스는 jpa를 사용하고 멀티 스레딩으로 작업할 것이니 jpaPagingItemReader를 사용해주자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 이제 병렬 처리를 어떻게 할 것인가를 선택해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;멀티 스레드 스텝 - 스텝 내의 각 청크를 여러 스레드로 병렬 실행하는 방법 - reader, writer가 스레드 세이프 하지 않다면&lt;br /&gt;동시성 문제가 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;파티셔닝 - 전체 사이즈를 파티션으로 나눠 각 파티션 별로 스레드가 할당되어 독립적으로 작업을 수행하는 방식&lt;br /&gt;파티션 별로 작업을 수행하기 때문에 reader, writer가 스레드 세이프 한지 안 한지 고려할 필요 없다.&lt;/li&gt;
&lt;li&gt;병렬 스텝 - 여러 개의 스텝을 병렬로 수행하는 방식 - 스레드 세이프하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 ItemWriter를 직접 구현할 것이기 때문에 동시성문제 고려를 가장 적게 하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티셔닝과 병렬 스텝 둘 중 고민을 많이 했는데, 둘 사이의 차이가 거의 없어&amp;nbsp;&lt;br /&gt;STATUS가 PENDING 상태인 이메일을 ID기준으로 파티셔닝 하는 로직이 적합할 것 같아&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;1272&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HfO4g/btsOkPi5hpa/KnNzLkPWrdH4PZELd1VnXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HfO4g/btsOkPi5hpa/KnNzLkPWrdH4PZELd1VnXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HfO4g/btsOkPi5hpa/KnNzLkPWrdH4PZELd1VnXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHfO4g%2FbtsOkPi5hpa%2FKnNzLkPWrdH4PZELd1VnXK%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;461&quot; data-origin-width=&quot;1272&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;좀 더 자세히 설명하면 파티셔닝 방식은 전체 데이터 범위를 설정한 그리드 사이즈로 나누고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신의 파티션을 청크 사이즈 별로 독립적으로 처리하는 방식이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 중 쿼리 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 기존에 JpaWriter를 사용하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 JpaWriter는 내부적으로 entitymanager.maerge() 메서드를 사용하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;merge 메서드는 select와 insert 두 번의 쿼리가 모든 영속성 컨텍스트 별로 나간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난 이점이 굉장히 마음에 안 들었고 나중에 QueryDsl로 Reader를 구현할 일이 생길 수도 있으니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 직접 writer를 구현하기로 했다. 어차피 파티셔닝 써서 동시성 문제 많이 고려 안 해두 됨 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 writer를 구현하던 중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2000개의 이메일 테스트 중 쿼리가 2000번이 나가는 아주 심각한 문제점 발견&lt;/p&gt;
&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;writer에서 saveAll 호출이 원인이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 saveAll이 벌크 연산인줄 알았으나 내부를 살펴보니 벌크 연산이 아니었다 ㅠㅠㅠ&lt;/p&gt;
&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Modifying(clearAutomatically = true)
@Query(&quot;UPDATE EmailNotification e SET e.status = :status, e.retryCount = e.retryCount + 1, e.updatedAt = CURRENT_TIMESTAMP WHERE e.id IN :ids&quot;)
void bulkUpdateStatus(@Param(&quot;status&quot;) EmailStatus status, @Param(&quot;ids&quot;) List&amp;lt;Long&amp;gt; ids);&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;이후 writer를 다음과 같이 변경&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;return items -&amp;gt; {
    List&amp;lt;Long&amp;gt; successIds = new ArrayList&amp;lt;&amp;gt;();
    List&amp;lt;EmailNotification&amp;gt; failedItems = new ArrayList&amp;lt;&amp;gt;();

    for (EmailNotification item : items) {
        try {
            retryTemplate.execute(context -&amp;gt; {
                sesMailService.sendApplyNotification(item.getEmail());
                log.info(&quot;메일 전송 성공: {}&quot;, item.getEmail());
                successIds.add(item.getId());
                return null;
            }, context -&amp;gt; {
                item.changeStatus(EmailStatus.FAILED);
                failedItems.add(item);
                log.error(&quot;DLQ 처리 - {}: {}&quot;, item.getEmail(),
                        Objects.requireNonNull(context.getLastThrowable()).getMessage());
                return null;
            });
        } catch (Exception e) {
            item.changeStatus(EmailStatus.FAILED);
            failedItems.add(item);
            log.error(&quot;Unexpected failure: {}&quot;, item.getEmail(), e);
        }
    }

    // 1. 성공 건: 벌크 업데이트
    if (!successIds.isEmpty()) {
        emailNotificationRepository.bulkUpdateStatus(EmailStatus.SUCCESS, successIds);
    }

    // 2. 실패 건: 개별 필드 변경사항 포함하여 saveAll
    if (!failedItems.isEmpty()) {
        emailNotificationRepository.saveAll(failedItems);
        emailNotificationRepository.flush();
    }
};&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;실패 건에 대해서는 saveAll() 형태를 유지한다. (발생 확률이 거의 없기 때문)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 2000번 -&amp;gt; 6번으로 무사히 구현완료 !&lt;/p&gt;
&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;다음 포스트에서는 분산락을 활용하여 Shecduler 어노테이션을 도입한 내용에 대해 작성하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://2junbeom.tistory.com/206?category=1256638&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://2junbeom.tistory.com/206?category=1256638&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748837452882&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;[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 3 (redis Shed lock, @Scheduled, 분산락)&quot; data-og-description=&quot;이전 포스트https://2junbeom.tistory.com/205 [개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 2 (AWS SES로의 마이그레이션, 적절한 스https://2junbeom.tistory.com/204이전 포스트 [개발일지] spring batch를&quot; data-og-host=&quot;2junbeom.tistory.com&quot; data-og-source-url=&quot;https://2junbeom.tistory.com/206?category=1256638&quot; data-og-url=&quot;https://2junbeom.tistory.com/206&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cfbnwp/hyY07MKNTR/ohjTOozx1SmKrFPZtU00y1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/cmQZUx/hyY1cAxF3l/NZv1SYD30TuLJtnkZQknG1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/hNhUi/hyY1dlU028/VjqTqjQMJTb7Set34JLukK/img.png?width=402&amp;amp;height=439&amp;amp;face=0_0_402_439&quot;&gt;&lt;a href=&quot;https://2junbeom.tistory.com/206?category=1256638&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://2junbeom.tistory.com/206?category=1256638&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cfbnwp/hyY07MKNTR/ohjTOozx1SmKrFPZtU00y1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/cmQZUx/hyY1cAxF3l/NZv1SYD30TuLJtnkZQknG1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/hNhUi/hyY1dlU028/VjqTqjQMJTb7Set34JLukK/img.png?width=402&amp;amp;height=439&amp;amp;face=0_0_402_439');&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;[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 3 (redis Shed lock, @Scheduled, 분산락)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 포스트https://2junbeom.tistory.com/205 [개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 2 (AWS SES로의 마이그레이션, 적절한 스https://2junbeom.tistory.com/204이전 포스트 [개발일지] spring batch를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;2junbeom.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;</description>
      <category>☃️❄️개발일지, 트러블슈팅❄️☃️</category>
      <category>AWS</category>
      <category>SES</category>
      <category>Spring Batch</category>
      <category>멀티 스레딩</category>
      <category>청크 사이즈</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/205</guid>
      <comments>https://2junbeom.tistory.com/205#entry205comment</comments>
      <pubDate>Wed, 28 May 2025 15:22:27 +0900</pubDate>
    </item>
    <item>
      <title>[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 1 (멀티 스레딩 동시성 문제)</title>
      <link>https://2junbeom.tistory.com/204</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;spring batch 도입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 진행 중인 프로젝트에서 이메일을 대량으로 발송하는 기능을 개발해야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대량 메일 발송 기능을 위해 spring batch 기능을 도입했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring batch 도입 이유는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 최적화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 번에 모든 객체를 메모리에 올려서 처리하는 게 아니라 chunk 단위로 메모리에 올려 처리하기 때문에,&lt;br /&gt;메모리 최적화에 용이하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;쉬운 재시도
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이메일 발송의 경우 초당 전송량이 제한돼 있기 때문에, 이메일 전송에 실패할 가능성이 높다.&lt;br /&gt;모든 회원에게 이메일을 실패없이 보내는 것을 목표로 하기 때문에, 재시도는 필수이고&lt;br /&gt;이러한 재시도는 spring batch에서 쉽게 구성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;병렬 처리 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티 스레딩, 파티셔닝 등의 기능을 지원하여 이를 기반으로 원할한 병렬 처리가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유들을 기반으로 spring batch를 도입하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티 스레딩 동시성 이슈 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 스레드 수를 정하기 전, 테스트 목적으로 이메일 2000개를 전송하고 있었다.&lt;/p&gt;
&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;그러나 2000개 중 500개 정도의 메일뿐이 전송되지 않은 것을 확인할 수 있었다.&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    return new JpaTransactionManager(entityManagerFactory);
}&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;배치 job에 transactionManager로 entityManager를 주입해줬는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티 매니저는 싱글 스레드 환경에 맞게 설계됐다는 것을 깜빡한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 매니저는 더티 체킹, 자동 플러시 등의 작업을 위해, 각 request 별로 별도의 객체가 생성되며&lt;br /&gt;해당 객체 내부에서 프록시 객체들의 상태를 관리한다.&lt;br /&gt;여기서 프록시 객체의 상태가 변했다면 자동으로 더티 체킹을 통해 동기화를 진행하고&lt;br /&gt;이 작업을 위해서 싱글 스레드로 운영하는 것이다. (멀티 스레딩이라면 내부 객체에 여러 스레드가 접근해 프록시 객체의 상태가 계속 변하기 때문 한 마디로 의도한 대로 흘러가지 않는다.)&lt;/li&gt;
&lt;li&gt;이 엔티티 매니저 하나에 여러 개의 스레드를 할당 했기 때문에 동시성 문제가 발생한 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생한 부분을 살펴보자&lt;/p&gt;
&lt;pre id=&quot;code_1748394192294&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class EmailReaderConfig {

    @Bean
    public JpaPagingItemReader&amp;lt;EmailNotification&amp;gt; emailReader(EntityManagerFactory entityManagerFactory) {
        JpaPagingItemReader&amp;lt;EmailNotification&amp;gt; reader = new JpaPagingItemReader&amp;lt;&amp;gt;();
        reader.setEntityManagerFactory(entityManagerFactory);
        reader.setQueryString(&quot;SELECT e FROM EmailNotification e WHERE e.status = 'PENDING'&quot;);
        reader.setPageSize(50);
        reader.setSaveState(false);
        reader.setName(&quot;emailReader&quot;);
        return reader;
    }
}&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;이 코드에서 JpaPagingItemReader를 통해 페이징 수를 자동으로 증가시키며 다음 페이지를 읽어오고 배치 처리를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JpaPagingItemReader는 다음과 같은 상태를 유지한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;entityManager&lt;/li&gt;
&lt;li&gt;CurrentPage&lt;/li&gt;
&lt;li&gt;Query&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 상태를 모든 스레드에서 공유하기 때문에 동시성 문제가 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;currentPage를 공유하기 때문에 같은 페이지를 읽어올 수 있고,&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;이 문제를 해결하기 위해 Partitioner + 각 파티션별 Reader 구조를 사용했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이전 구조는 한 개의 Reader를 사용했기 때문에 문제가 생겼고,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파티션 스텝은 각 스텝 별 각기 다른 Reader를 만들고, 각각 다른 엔티티 매니저를 할당하며&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 스텝별로 범위를 분할해서 읽어오기 때문에 동시성 문제를 해결할 수 있는 것이다.&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
public class EmailReaderConfig {

    @Bean
    @StepScope // step scope로 런타임 시 동적으로 변수 값 할당
    public JpaPagingItemReader&amp;lt;EmailNotification&amp;gt; emailReader(
            @Value(&quot;#{stepExecutionContext['startId']}&quot;) Long startId,
            @Value(&quot;#{stepExecutionContext['endId']}&quot;) Long endId,
            EntityManagerFactory entityManagerFactory
    ) {
        JpaPagingItemReader&amp;lt;EmailNotification&amp;gt; reader = new JpaPagingItemReader&amp;lt;&amp;gt;();
        reader.setEntityManagerFactory(entityManagerFactory);
        reader.setQueryString(
                &quot;SELECT e FROM EmailNotification e WHERE e.status = 'PENDING' AND e.id BETWEEN :startId AND :endId&quot;);
        reader.setParameterValues(Map.of(&quot;startId&quot;, startId, &quot;endId&quot;, endId));
        reader.setPageSize(50);
        reader.setSaveState(false);
        reader.setName(&quot;emailReader&quot;);
        return reader;
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StepScope 어노테이션을 통해 Reader 생성시 마다 동적으로 가져올 페이지를 할당한다.&lt;/p&gt;
&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class IdRangePartitioner implements Partitioner {

    private final EmailNotificationRepository repository;

    public IdRangePartitioner(EmailNotificationRepository repository) {
        this.repository = repository;
    }

    @Override
    public Map&amp;lt;String, ExecutionContext&amp;gt; partition(int gridSize) {
        Long minId = repository.findMinId();
        Long maxId = repository.findMaxId();

        Map&amp;lt;String, ExecutionContext&amp;gt; result = new HashMap&amp;lt;&amp;gt;();

        if (minId == null || maxId == null) {
            return result;
        }

        long targetSize = (maxId - minId) / gridSize + 1;
        long start = minId;
        long end = start + targetSize - 1;

        for (int i = 0; i &amp;lt; gridSize; i++) {
            ExecutionContext context = new ExecutionContext();
            context.putLong(&quot;startId&quot;, start);
            context.putLong(&quot;endId&quot;, Math.min(end, maxId));
            result.put(&quot;partition&quot; + i, context);
            start += targetSize;
            end += targetSize;
        }

        return result;
    }
}&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;전체 이메일의 크기를 가져오고 그것을 파티션 사이즈 별로 분할해서 실행 context에 저장시켜둔다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 실행 context에서 StepScope를 통해 동적으로 할당 받아와 Reader에서 읽어온다.&lt;/p&gt;
&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;naver smtp는 1시간 동안 최대 ???회, 1회에 최대 100명에게 메일을 보낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://help.naver.com/service/30029/contents/21159?lang=ko&amp;amp;osType=COMMONOS&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://help.naver.com/service/30029/contents/21159?lang=ko&amp;amp;osType=COMMONOS&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748395413741&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;help.naver.com&quot; data-og-source-url=&quot;https://help.naver.com/service/30029/contents/21159?lang=ko&amp;amp;osType=COMMONOS&quot; data-og-url=&quot;https://help.naver.com/service/30029/contents/21159?lang=ko&amp;amp;osType=COMMONOS&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cLzKj0/hyYYExu8VG/DR90kftAJ34ytFulDokbTk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://help.naver.com/service/30029/contents/21159?lang=ko&amp;amp;osType=COMMONOS&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://help.naver.com/service/30029/contents/21159?lang=ko&amp;amp;osType=COMMONOS&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cLzKj0/hyYYExu8VG/DR90kftAJ34ytFulDokbTk/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;메일 발송 제한 및 한 번에 보낼 수 있는 수신인 수 : 메일 고객센터&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;help.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 수치는 적혀져 있지 않지만, 내가 테스트를 진행할 땐 100명에서 더 이상 보내지지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(사실 이 문제는 너무 당연한 거라 이미 SES로 마이그레이션을 생각하고 있었다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트에서 SES로의 마이그레이션을 다루겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://2junbeom.tistory.com/204&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://2junbeom.tistory.com/205&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748417894119&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;[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 1 (멀티 스레딩 동시성 문제)&quot; data-og-description=&quot;spring batch 도입현재 진행 중인 프로젝트에서 이메일을 대량으로 발송하는 기능을 개발해야했다. 대량 메일 발송 기능을 위해 spring batch 기능을 도입했다. spring batch 도입 이유는 다음과 같다.메모&quot; data-og-host=&quot;2junbeom.tistory.com&quot; data-og-source-url=&quot;https://2junbeom.tistory.com/204&quot; data-og-url=&quot;https://2junbeom.tistory.com/204&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bsOy4A/hyYW6gMeSS/OBW1FHXBBp4uIhQ0LHKeh1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/dudDeN/hyYYEj1fjD/0Yc98dh0uSDrAvzzD1huc1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/Niw6K/hyY075UQGY/bKUMwLLKSoBOQHKk6sKPok/img.png?width=402&amp;amp;height=439&amp;amp;face=0_0_402_439&quot;&gt;&lt;a href=&quot;https://2junbeom.tistory.com/204&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://2junbeom.tistory.com/204&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bsOy4A/hyYW6gMeSS/OBW1FHXBBp4uIhQ0LHKeh1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/dudDeN/hyYYEj1fjD/0Yc98dh0uSDrAvzzD1huc1/img.png?width=278&amp;amp;height=181&amp;amp;face=0_0_278_181,https://scrap.kakaocdn.net/dn/Niw6K/hyY075UQGY/bKUMwLLKSoBOQHKk6sKPok/img.png?width=402&amp;amp;height=439&amp;amp;face=0_0_402_439');&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;[개발일지] spring batch를 통한 대량 이메일 발송 기능 개발 - 1 (멀티 스레딩 동시성 문제)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;spring batch 도입현재 진행 중인 프로젝트에서 이메일을 대량으로 발송하는 기능을 개발해야했다. 대량 메일 발송 기능을 위해 spring batch 기능을 도입했다. spring batch 도입 이유는 다음과 같다.메모&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;2junbeom.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;</description>
      <category>☃️❄️개발일지, 트러블슈팅❄️☃️</category>
      <category>Spring Batch</category>
      <category>대량 이메일 발송</category>
      <category>멀티 스레딩</category>
      <author>대기업 가고 싶은 공돌이</author>
      <guid isPermaLink="true">https://2junbeom.tistory.com/204</guid>
      <comments>https://2junbeom.tistory.com/204#entry204comment</comments>
      <pubDate>Wed, 28 May 2025 10:31:22 +0900</pubDate>
    </item>
  </channel>
</rss>