<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기록</title>
    <link>https://coor.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 29 Jun 2026 10:49:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>coor</managingEditor>
    <image>
      <title>기록</title>
      <url>https://tistory1.daumcdn.net/tistory/4082942/attach/3757b97fceaa46c3857019d7b7b67e32</url>
      <link>https://coor.tistory.com</link>
    </image>
    <item>
      <title>제주도 한달살이 후기</title>
      <link>https://coor.tistory.com/83</link>
      <description>&lt;div id=&quot;toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5월 딱 한달. 제주도에서 한달동안 살아보았다. 짧다면 짧고 길다면 긴 시간 동안 그 안에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;느꼈던 순간들과 생각들을 하나씩 정리해보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1. 왜 제주도 한달살이를 하게 됐는지&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;❓&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;한 달 살이의 시작은 여자친구의 교생 실습이었다. &lt;/span&gt;&lt;span&gt;선생님이 되기 위해 한 달 동안 교생 실습을 해야 했는데, 서울에서도 할 수 있었지만 여자친구의 로망이었던 &amp;ldquo;&lt;b&gt;조금 더 특별하게, 제주도에서 해보고 싶다&lt;/b&gt;&amp;rdquo;는 마음에서 제주 한 달 살이가 시작됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;나 역시 마침 반복되는 일상에서 잠시 벗어나고 싶었던 시기였고, 제주도에서의 생활이 어떤 느낌일지 궁금하기도 했다. 평소와는 다른 환경에서 지내보는 경험 자체가 꽤 의미 있을 것 같다는 생각이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;제주 한 달 살이를 5월에 계획하면서, 그 전에 프리랜서 계약 종료 시점을 맞춰두었다. &lt;b&gt;생활비&lt;/b&gt;를 어떻게 해야 할지 고민도 있었지만, 실업급여 신청이 가능한 상황이라 현실적인 부분도 충분히 해결할 수 있었다. 덕분에 제주에서 한 달 정도 지내보는 선택을 부담 없이 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;이런 여러 가지 이유가 맞물리면서, 제주 한 달 살이를 하게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2. 준비 과정&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;숙소&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;숙소는 주로 &lt;b&gt;에어비앤비를&lt;/b&gt; 통해 알아봤고, 중간중간 네이버에서 제주도 한달살이 후기를 찾아보며 다른 사람들은 어떤 기준으로 숙소를 선택했는지도 참고했다. &lt;/span&gt;&lt;/span&gt;우리가 숙소를 고를 때 중요하게 봤던 기준은 크게 세 가지였다. 금액, 자연 환경, 그리고 편안한 인테리어.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;처음에는 200~300만원대 숙소도 많이 봤는데, 확실히 컨디션은 좋았지만 한 달 기준으로는 부담스럽게 느껴졌다. 그래서 현실적으로 타협 가능한 선을 찾다 보니 최종적으로는 150만원대 숙소 위주로 알아보게 됐다.&lt;/p&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvycrU/dJMcabRY8sQ/qipBCQKSv6LRKlkCLGwExK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvycrU/dJMcabRY8sQ/qipBCQKSv6LRKlkCLGwExK/img.jpg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; width=&quot;425&quot; height=&quot;567&quot; data-filename=&quot;KakaoTalk_Photo_2026-06-09-15-42-08 002.jpeg&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;4032&quot; data-origin-width=&quot;3024&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvycrU/dJMcabRY8sQ/qipBCQKSv6LRKlkCLGwExK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvycrU%2FdJMcabRY8sQ%2FqipBCQKSv6LRKlkCLGwExK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yBjco/dJMcafGVCvK/TaEAAj1yBsFlbcOzUsTIrK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yBjco/dJMcafGVCvK/TaEAAj1yBsFlbcOzUsTIrK/img.jpg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; width=&quot;254&quot; height=&quot;339&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-06-09-15-42-12 003.jpeg&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yBjco/dJMcafGVCvK/TaEAAj1yBsFlbcOzUsTIrK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyBjco%2FdJMcafGVCvK%2FTaEAAj1yBsFlbcOzUsTIrK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQu9wh/dJMcabRY8sN/WJDq43ontvFRcF00hgQr9K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQu9wh/dJMcabRY8sN/WJDq43ontvFRcF00hgQr9K/img.jpg&quot; style=&quot;width: 32.5581%;&quot; width=&quot;300&quot; height=&quot;400&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-06-09-15-42-03 001.jpeg&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQu9wh/dJMcabRY8sN/WJDq43ontvFRcF00hgQr9K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQu9wh%2FdJMcabRY8sN%2FWJDq43ontvFRcF00hgQr9K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그렇게 선택한 곳은 제주 한림에 위치한 &amp;lsquo;&lt;a href=&quot;https://naver.me/546xjPsA&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;제주돌집 소락&lt;/a&gt;&amp;rsquo;이라는 숙소였다. 제주 특유의 돌집 감성과 조용한 분위기가 마음에 들었고, 자연과 가까운 환경에서 한 달을 보내기에 적합하다고 느껴졌다. 실제로 지내보니 관광지의 번잡함보다는 조용히&amp;nbsp;&amp;lsquo;살아보는&amp;nbsp;느낌&amp;rsquo;에&amp;nbsp;더&amp;nbsp;가까운&amp;nbsp;선택이었다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이런 집에 살아보니 장단점이 확실했다. 자연 속에 있는 듯한 기분이 들어서 좋긴 했지만, 벌레가 너무 많았다. 이틀에 한 번꼴로 집 안에 벌레가 있었고, 한 번은 지네를 발로 밟아서 물린 적도 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;처음에는 그냥 따끔하고 끝날 줄 알았는데, 시간이 갈수록 고통이 점점 심해지면서 마비가 오는 느낌까지 들었다. 처음엔 &amp;ldquo;오오..?&amp;rdquo; 했다가, 이내 &amp;ldquo;아악... 하앍...&amp;rdquo; 생각보다 훨씬 아파서 꽤 놀랐다. 마치 벌에 쏘이면 이런 느낌일까? 싶은 생각도 들었다. 아무튼 벌레가 너무 많아서, 다음에는 완전한 자연 속보다는 적당히 자연이 있는 곳으로 가야겠다고 느꼈다ㅋㅋ&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;교통&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제주도 한달살이를 준비하면서 교통은 &lt;b&gt;렌트 vs 대중교통 vs 자차&lt;/b&gt; 세 가지를 두고 고민했다. 결론부터 말하면 우리는 &lt;b&gt;렌트를 선택&lt;/b&gt;했다. 대중교통은 이동 시간이 길어질 것 같았고, 자차를 배로 가져가는 방법도 고려했지만 시간과 번거로움을 생각했을 때 현실적인 선택은 렌트였다.&lt;/p&gt;
&lt;p data-end=&quot;372&quot; data-start=&quot;359&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;다만 문제는 가격이었다. &lt;span&gt;&lt;span&gt;SK렌터카&lt;/span&gt;&lt;/span&gt;, &lt;span&gt;&lt;span&gt;롯데렌터카&lt;/span&gt;&lt;/span&gt;, &lt;span&gt;&lt;span&gt;쏘카&lt;/span&gt;&lt;/span&gt; 같은 대형 렌터카 업체들은 &lt;b&gt;한 달 기준으로 250만원 이상&lt;/b&gt;이어서 부담이 컸다. 그래서 최대한 저렴한 방법을 찾다가 평소 이용했던 &lt;a href=&quot;https://www.jejuangeltour.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span&gt;제주엔젤카&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;를 선택하게 됐다. 이곳은 한 달 단위 예약은 불가능해서 최대 2주씩 나눠 예약해야 했다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;372&quot; data-start=&quot;359&quot; data-ke-size=&quot;size16&quot;&gt;저렴한 가격으로 예약하기 위해서 나는 한 달 전부터 계속 가격을 비교하면서 가장 저렴한 타이밍을 찾았다. 한달동안 찾는 저렴한 알고리즘을 정리하면, 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;372&quot; data-start=&quot;359&quot;&gt;&lt;b&gt;예약일이 임박할수록 가격이 내려감 !&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;372&quot; data-start=&quot;359&quot;&gt;&lt;b&gt;대신 차량이 없을 수도 있어서 눈치싸움 필수 !&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;372&quot; data-start=&quot;359&quot;&gt;&lt;b&gt;주말(금~일)은 가격이 확 올라감 !&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;372&quot; data-start=&quot;359&quot;&gt;&lt;b&gt;14일보다 7~10일 단위 예약이 더 저렴한 경우 많음 !&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;372&quot; data-start=&quot;359&quot;&gt;&lt;b&gt;30분~1시간 단위로 가격이 달라져서 계속 검색 필요 !&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;위 내용 기준으로 예약해서 &lt;b&gt;렌트비 110만원 (하루 약 3.5만원 수준) + 기름값: 30만원으로 총 140만 비용&lt;/b&gt;이 들었다. 기름값이 특히 많이 나왔는데, 당시 미국-이란 전쟁으로 유가가 올라 휘발유 가격이 2000원대였던 영향도 있었다ㅠㅠ (제주 기름 너무 비쌈...)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;994&quot; data-end=&quot;1035&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;3. 하루 루틴&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;평일에는 최대한 일정한 루틴을 유지하려고 했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;375&quot; data-start=&quot;214&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;246&quot; data-start=&quot;214&quot; data-section-id=&quot;1jfbtw9&quot;&gt;&lt;b&gt;07:30&lt;/b&gt; 기상 후 여자친구 학교 데려다주기&lt;/li&gt;
&lt;li data-end=&quot;285&quot; data-start=&quot;247&quot; data-section-id=&quot;1li2lv8&quot;&gt;&lt;b&gt;08:30&lt;/b&gt; 한림/협재 카페에서 책 읽기 또는 코딩 강의 보기&lt;/li&gt;
&lt;li data-end=&quot;316&quot; data-start=&quot;286&quot; data-section-id=&quot;j5my9y&quot;&gt;&lt;b&gt;13:00&lt;/b&gt; 숙소 복귀 후 점심 식사&lt;/li&gt;
&lt;li data-section-id=&quot;j5my9y&quot; data-start=&quot;286&quot; data-end=&quot;316&quot;&gt;&lt;b&gt;14:00&lt;/b&gt;&lt;span&gt; &lt;/span&gt;집안일 + 휴식&lt;/li&gt;
&lt;li data-end=&quot;336&quot; data-start=&quot;317&quot; data-section-id=&quot;4qeog4&quot;&gt;&lt;b&gt;16:10&lt;/b&gt; 학교 픽업&lt;/li&gt;
&lt;li data-section-id=&quot;4qeog4&quot; data-start=&quot;317&quot; data-end=&quot;336&quot;&gt;&lt;b&gt;18:00&lt;/b&gt;&lt;span&gt;&amp;nbsp;저녁 식사&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;358&quot; data-start=&quot;337&quot; data-section-id=&quot;unh2wj&quot;&gt;&lt;b&gt;19:00 ~ 22:00&lt;/b&gt;&amp;nbsp;배드민턴 운동&lt;/li&gt;
&lt;li data-end=&quot;375&quot; data-start=&quot;359&quot; data-section-id=&quot;1kn8lzz&quot;&gt;&lt;b&gt;23:30 &lt;/b&gt;잠자기&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btnZmP/dJMcadPMFIR/4T4DnQJE7LUtJIEPtDXpVK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btnZmP/dJMcadPMFIR/4T4DnQJE7LUtJIEPtDXpVK/img.jpg&quot; style=&quot;width: 25.855%; margin-right: 10px;&quot; width=&quot;321&quot; height=&quot;428&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-06-09-19-02-18 002.jpeg&quot; data-widthpercent=&quot;26.47&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btnZmP/dJMcadPMFIR/4T4DnQJE7LUtJIEPtDXpVK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtnZmP%2FdJMcadPMFIR%2F4T4DnQJE7LUtJIEPtDXpVK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SIPNb/dJMcadPMFIP/BHC9OAPKf9Ktfe3uWRKEn0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SIPNb/dJMcadPMFIP/BHC9OAPKf9Ktfe3uWRKEn0/img.jpg&quot; style=&quot;width: 45.9644%; margin-right: 10px;&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-06-09-19-02-17 001.jpeg&quot; data-widthpercent=&quot;47.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SIPNb/dJMcadPMFIP/BHC9OAPKf9Ktfe3uWRKEn0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSIPNb%2FdJMcadPMFIP%2FBHC9OAPKf9Ktfe3uWRKEn0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cV7DHO/dJMcadPMFIQ/t3xHSKQY77H0Em7AC0G3S0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cV7DHO/dJMcadPMFIQ/t3xHSKQY77H0Em7AC0G3S0/img.jpg&quot; style=&quot;width: 25.855%;&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-06-09-19-02-19 003.jpeg&quot; data-widthpercent=&quot;26.47&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cV7DHO/dJMcadPMFIQ/t3xHSKQY77H0Em7AC0G3S0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcV7DHO%2FdJMcadPMFIQ%2Ft3xHSKQY77H0Em7AC0G3S0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;div data-is-intersecting=&quot;true&quot; data-turn-id-container=&quot;request-WEB:dc4f23d4-15d1-44e4-a63c-f35e6af0e0bf-5&quot;&gt;
&lt;div&gt;
&lt;div data-conversation-screenshot-content=&quot;&quot;&gt;
&lt;div&gt;
&lt;div data-turn-start-message=&quot;true&quot; data-message-model-slug=&quot;gpt-5-5&quot; data-message-id=&quot;e551ee3b-4f0c-41bf-96fc-a2420ee08234&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-end=&quot;133&quot; data-start=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 시간대별로 나눠보면 단순한 하루처럼 보이지만, 이 루틴을 최대한 유지하려고 했던 이유는 자기개발과 휴식의 균형을 맞추기 위해서였다. 너무 놀기만 하면 서울 돌아가서 후유증이 클 것 같아서ㅋㅋ 적당한 자기개발이 필요하다고 느꼈다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;253&quot; data-start=&quot;135&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;자기개발은 코딩보다는 책을 많이 읽는 쪽으로 가져갔다. 읽고 싶었던 책도 많았고, 재미있는 책들도 많아서 자연스럽게 독서량이 늘었다. 게다가 바다가 바로 보이는 뷰라 그런지 집중도 잘 되고, 책도 더 잘 읽혔다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br3dtB/dJMcaaZSDgj/RmFyqk82pOg20sNLk63J2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br3dtB/dJMcaaZSDgj/RmFyqk82pOg20sNLk63J2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br3dtB/dJMcaaZSDgj/RmFyqk82pOg20sNLk63J2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr3dtB%2FdJMcaaZSDgj%2FRmFyqk82pOg20sNLk63J2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1592&quot; height=&quot;716&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;4. 비용 정리&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;한 달 동안 사용한 비용을 정리해보면 다음과 같다.&lt;/p&gt;
&lt;h4 data-end=&quot;231&quot; data-start=&quot;221&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;숙소&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;268&quot; data-start=&quot;232&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;246&quot; data-start=&quot;232&quot; data-section-id=&quot;vtckgb&quot;&gt;숙소비: 150만원&lt;/li&gt;
&lt;li data-end=&quot;268&quot; data-start=&quot;247&quot; data-section-id=&quot;xz3x0j&quot;&gt;공과금(가스 + 전기): 6만원&lt;/li&gt;
&lt;li data-end=&quot;268&quot; data-start=&quot;247&quot; data-section-id=&quot;xz3x0j&quot;&gt;&lt;b&gt;총 : 156만원&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;308&quot; data-start=&quot;293&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;교통 (렌트)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;380&quot; data-start=&quot;309&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;339&quot; data-start=&quot;309&quot; data-section-id=&quot;1qwaze6&quot;&gt;렌트비: 110만원&lt;/li&gt;
&lt;li data-end=&quot;355&quot; data-start=&quot;340&quot; data-section-id=&quot;1ehjg0m&quot;&gt;기름값: 약 30만원&lt;/li&gt;
&lt;li data-end=&quot;380&quot; data-start=&quot;356&quot; data-section-id=&quot;bl1za7&quot;&gt;기타 비용: 4만원 (배터리 방전  )&lt;/li&gt;
&lt;li data-end=&quot;380&quot; data-start=&quot;356&quot; data-section-id=&quot;bl1za7&quot;&gt;&lt;b&gt;총 : 144만원&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;398&quot; data-start=&quot;382&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;415&quot; data-start=&quot;405&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;운동&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;484&quot; data-start=&quot;416&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;437&quot; data-start=&quot;416&quot; data-section-id=&quot;3sbn43&quot;&gt;배드민턴 클럽 가입비: 10만원&lt;/li&gt;
&lt;li data-end=&quot;450&quot; data-start=&quot;438&quot; data-section-id=&quot;1nuu0dz&quot;&gt;셔틀콕: 8만원&lt;/li&gt;
&lt;li data-end=&quot;468&quot; data-start=&quot;451&quot; data-section-id=&quot;1jkl4wl&quot;&gt;라켓 거트비: 1만5천원&lt;/li&gt;
&lt;li data-end=&quot;484&quot; data-start=&quot;469&quot; data-section-id=&quot;1584o6t&quot;&gt;체육관 이용: 3만원&lt;/li&gt;
&lt;li data-end=&quot;484&quot; data-start=&quot;469&quot; data-section-id=&quot;1584o6t&quot;&gt;&lt;b&gt;총 : 22만원&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;520&quot; data-start=&quot;510&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;항공&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;577&quot; data-start=&quot;521&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;548&quot; data-start=&quot;521&quot; data-section-id=&quot;sb6co&quot;&gt;서울 &amp;harr; 제주 왕복: 22만원 + 10만원&lt;/li&gt;
&lt;li data-end=&quot;577&quot; data-start=&quot;549&quot; data-section-id=&quot;ztkeym&quot;&gt;추가 왕복 (결혼식): 20만원 + 10만원&lt;/li&gt;
&lt;li data-end=&quot;577&quot; data-start=&quot;549&quot; data-section-id=&quot;ztkeym&quot;&gt;&lt;b&gt;총 : 62만원&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;613&quot; data-start=&quot;601&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;생활비&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;658&quot; data-start=&quot;614&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;626&quot; data-start=&quot;614&quot; data-section-id=&quot;lhd50y&quot;&gt;식비: 50만원&lt;/li&gt;
&lt;li data-end=&quot;658&quot; data-start=&quot;627&quot; data-section-id=&quot;1t6s3i6&quot;&gt;기타 생활비 (데이트, 손님 초대 등): 50만원&lt;/li&gt;
&lt;li data-end=&quot;658&quot; data-start=&quot;627&quot; data-section-id=&quot;1t6s3i6&quot;&gt;&lt;b&gt;총 : 100만원&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;676&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;694&quot; data-start=&quot;683&quot; data-section-id=&quot;l2v9cj&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;총 비용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;714&quot; data-start=&quot;696&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;총합: 약 484만원&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;812&quot; data-start=&quot;736&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;812&quot; data-start=&quot;736&quot; data-ke-size=&quot;size16&quot;&gt;지출할 때는 크게 체감하지 못했는데, 이렇게 정리해보니 생각보다 비용이 많이 들었다. 특히 &lt;b&gt;숙소와 렌트 비용이 가장 크게 느껴졌다&lt;/b&gt;. 지금 와서 돌이켜보면 렌트는 당근이나 네이버 같은 곳에서 개인 간 거래로 알아봤다면 조금 더 저렴하게 할 수 있지 않았을까..? 하는 생각도 들었다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;5. 행복했던 순간&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;바다&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/071CJ/dJMcahxXEWm/NvOWvrpjVpPzANQgxav870/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/071CJ/dJMcahxXEWm/NvOWvrpjVpPzANQgxav870/img.jpg&quot; style=&quot;width: 63.2558%; margin-right: 10px;&quot; width=&quot;500&quot; height=&quot;375&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; data-filename=&quot;금능해수욕장1.jpeg&quot; data-widthpercent=&quot;64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/071CJ/dJMcahxXEWm/NvOWvrpjVpPzANQgxav870/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F071CJ%2FdJMcahxXEWm%2FNvOWvrpjVpPzANQgxav870%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LXdSQ/dJMcagy1p1K/kzPvSGA3SBZnAnYltysmq1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LXdSQ/dJMcagy1p1K/kzPvSGA3SBZnAnYltysmq1/img.jpg&quot; style=&quot;width: 35.5814%;&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;금능해수욕장2.jpeg&quot; data-widthpercent=&quot;36&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LXdSQ/dJMcagy1p1K/kzPvSGA3SBZnAnYltysmq1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLXdSQ%2FdJMcagy1p1K%2FkzPvSGA3SBZnAnYltysmq1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NeOrz/dJMcadWA5fE/1lRjrJeWS0tLE7XAfK2qK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NeOrz/dJMcadWA5fE/1lRjrJeWS0tLE7XAfK2qK0/img.png&quot; style=&quot;width: 63.2558%; margin-right: 10px; margin-top: 10px;&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NeOrz/dJMcadWA5fE/1lRjrJeWS0tLE7XAfK2qK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNeOrz%2FdJMcadWA5fE%2F1lRjrJeWS0tLE7XAfK2qK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvjky1/dJMcadPMMNx/qHaqyvCDx4h8hC1N5uKZiK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvjky1/dJMcadPMMNx/qHaqyvCDx4h8hC1N5uKZiK/img.jpg&quot; style=&quot;width: 35.5814%; margin-top: 10px;&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;바다뷰1.jpeg&quot; data-widthpercent=&quot;36&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvjky1/dJMcadPMMNx/qHaqyvCDx4h8hC1N5uKZiK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvjky1%2FdJMcadPMMNx%2FqHaqyvCDx4h8hC1N5uKZiK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;제주도에서 지내면서 가장 좋았던 건 어디를 가도 바다가 가까이 있다는 점이었다. 해변이 많다 보니 다양한 바다 풍경을 볼 수 있었는데, 그중에서도 에메랄드빛이 인상적이었던 &lt;a href=&quot;https://naver.me/5oE5rcxm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span&gt;세기알 해변&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;과 &lt;a href=&quot;https://naver.me/Fr7ptPUY&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span&gt;금능해수욕장&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;은 특히 기억에 남는다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;바다에 들어가 한 시간 정도 수영을 하고 나와서, 바다를 바라보며 멍하니 시간을 보내다가 사우나에서 씻고 나오면 자연스럽게 배가 고파진다. 그 상태에서 먹는 한 끼는 유독 더 맛있게 느껴졌고, 그런 순간들이 꽤 오래 기억에 남는다. 서울에서는 쉽게 할 수 없는 경험이라서, 더 행복하게 느껴졌던 게 아닐까 하는 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;485&quot; data-start=&quot;463&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;485&quot; data-start=&quot;463&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;485&quot; data-start=&quot;463&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;배드민턴&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkvqXq/dJMcacpT11J/JdMW9qzsf2MzIhEhbEmKkK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkvqXq/dJMcacpT11J/JdMW9qzsf2MzIhEhbEmKkK/img.jpg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;한림체육관.jpeg&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkvqXq/dJMcacpT11J/JdMW9qzsf2MzIhEhbEmKkK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkvqXq%2FdJMcacpT11J%2FJdMW9qzsf2MzIhEhbEmKkK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWA5qg/dJMb997KaBg/04G7yGXVpgNXixZ9mqdCe1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWA5qg/dJMb997KaBg/04G7yGXVpgNXixZ9mqdCe1/img.jpg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; width=&quot;465&quot; height=&quot;620&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;외도배드민턴장 1.jpeg&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWA5qg/dJMb997KaBg/04G7yGXVpgNXixZ9mqdCe1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWA5qg%2FdJMb997KaBg%2F04G7yGXVpgNXixZ9mqdCe1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXj6Ik/dJMb997KaBf/rPc6w4vftX3060DbR0UcP0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXj6Ik/dJMb997KaBf/rPc6w4vftX3060DbR0UcP0/img.jpg&quot; style=&quot;width: 32.5581%;&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;외도배드민턴장 2.jpeg&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXj6Ik/dJMb997KaBf/rPc6w4vftX3060DbR0UcP0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXj6Ik%2FdJMb997KaBf%2FrPc6w4vftX3060DbR0UcP0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfzoLo/dJMcahLt35d/MOs4nendcbrTgASEbkZw1K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfzoLo/dJMcahLt35d/MOs4nendcbrTgASEbkZw1K/img.jpg&quot; style=&quot;width: 35.5814%; margin-right: 10px;&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;제이콕 2호점 사진.jpeg&quot; data-widthpercent=&quot;36&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfzoLo/dJMcahLt35d/MOs4nendcbrTgASEbkZw1K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfzoLo%2FdJMcahLt35d%2FMOs4nendcbrTgASEbkZw1K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctkqY8/dJMcacpT11I/l8boOUnu8IxnW9ZOVsaM11/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctkqY8/dJMcacpT11I/l8boOUnu8IxnW9ZOVsaM11/img.jpg&quot; style=&quot;width: 63.2558%;&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; data-filename=&quot;제이콕 사진 2.jpeg&quot; data-widthpercent=&quot;64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctkqY8/dJMcacpT11I/l8boOUnu8IxnW9ZOVsaM11/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctkqY8%2FdJMcacpT11I%2Fl8boOUnu8IxnW9ZOVsaM11%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p data-end=&quot;550&quot; data-start=&quot;487&quot; data-ke-size=&quot;size16&quot;&gt;운동을 좋아해서 제주에서도 자연스럽게 체육관을 찾게 됐고, 근처에 있는 곳들을 하나씩 다녀보는 재미도 쏠쏠했다. 처음 보는 사람들과 게임도 치고, 가볍게 얘기 나누며 농담을 주고받는 시간들이 꽤 즐거웠다.&lt;/p&gt;
&lt;p data-end=&quot;323&quot; data-start=&quot;134&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;제주에서의 배드민턴 문화가 신기하면서도 재미있었다. 일화로, 제주에서는 화이팅할 때 라켓을 부딪히기보다는 손으로 하이파이브를 더 많이 하고, 13점 코드 체인지하고 나면 꼭 &amp;ldquo;고생하쇼~&amp;rdquo; 한마디를 한다. 그리고 서울에서는 하이파이브보다는 라켓 부딪히기를 많이 해서, 제주에서도 무심코 라켓을 부딪혔더니 &amp;ldquo;육지에서 오셨어요?&amp;rdquo;라고 몇 번 물어보셨다ㅋㅋㅋㅋ 그 이후로는 하이파이브로 바꿨다ㅎㅎ&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;390&quot; data-start=&quot;325&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;체육관은 외도 배드민턴장, 제이콕 배드민턴 1호점, 제이콕 배드민턴 2호점, 구구배드민턴장, 한림체육관 등을 다녔다. 운동을 마치고 근처 카페에서 아이스 카페라떼를 하나 사서 차를 타고 드라이브를 하면서 뻥 뚫린 도로와 바다 냄새를 맡고 있으면 그 순간이 참 행복했다. 행복별 거 있나? 이런 게 행복이지~&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 뚱띠와 함께&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fk4IA/dJMb997MPn0/fDbJKBPJW53VCLf1vEkhlK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fk4IA/dJMb997MPn0/fDbJKBPJW53VCLf1vEkhlK/img.jpg&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-filename=&quot;채원이 1.jpeg&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;4032&quot; data-origin-width=&quot;3024&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fk4IA/dJMb997MPn0/fDbJKBPJW53VCLf1vEkhlK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFk4IA%2FdJMb997MPn0%2FfDbJKBPJW53VCLf1vEkhlK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k56YW/dJMcadoKKpM/Ozze8VYJgQEiI5KuSROsFK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k56YW/dJMcadoKKpM/Ozze8VYJgQEiI5KuSROsFK/img.jpg&quot; style=&quot;width: 49.4186%;&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;채원이 2.jpeg&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k56YW/dJMcadoKKpM/Ozze8VYJgQEiI5KuSROsFK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk56YW%2FdJMcadoKKpM%2FOzze8VYJgQEiI5KuSROsFK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7cYKd/dJMcaar74WO/BpPoGLm2m05rnMZ08leoYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7cYKd/dJMcaar74WO/BpPoGLm2m05rnMZ08leoYK/img.png&quot; style=&quot;width: 32.5581%; margin-right: 10px; margin-top: 10px;&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;3000&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7cYKd/dJMcaar74WO/BpPoGLm2m05rnMZ08leoYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7cYKd%2FdJMcaar74WO%2FBpPoGLm2m05rnMZ08leoYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;3000&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ASZEQ/dJMcaaeARJI/AxJkLhw0fQe5krKxkKS2U0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ASZEQ/dJMcaaeARJI/AxJkLhw0fQe5krKxkKS2U0/img.jpg&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot; data-is-animation=&quot;false&quot; data-filename=&quot;채원이 5.jpeg&quot; style=&quot;width: 32.5581%; margin-right: 10px; margin-top: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ASZEQ/dJMcaaeARJI/AxJkLhw0fQe5krKxkKS2U0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FASZEQ%2FdJMcaaeARJI%2FAxJkLhw0fQe5krKxkKS2U0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4284&quot; height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yG4KG/dJMcaaTa3Xa/CbD16U1kiN8jE2kbVBGP40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yG4KG/dJMcaaTa3Xa/CbD16U1kiN8jE2kbVBGP40/img.png&quot; style=&quot;width: 32.5581%; margin-top: 10px;&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;3000&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yG4KG/dJMcaaTa3Xa/CbD16U1kiN8jE2kbVBGP40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyG4KG%2FdJMcaaTa3Xa%2FCbD16U1kiN8jE2kbVBGP40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;3000&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 말하는 뚱띠는 여자친구의 별명이다. 우리는 서로를 뚱띠, 뚱때라고 부르는데 그렇다고 뚱뚱한 건 아니다ㅋㅋㅋ(통통이라고 치자.) 그냥 둘 다 잘 먹어서 붙여진 귀여운 별명이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;한 달 동안 낯선 환경에서 지내다 보니 가끔은 다투기도 하고, 서로 서운한 순간들도 있었지만 그래도 결국은 &amp;ldquo;같이여서 더 행복했다&amp;rdquo;는 생각이 많이 들었다. &lt;/span&gt;&lt;span&gt;제주당 같은 카페에 가서 커피랑 빵 먹으면서 예쁜 자연 뷰를 보고, 사진도 찍고&amp;hellip; 또 외식비를 아끼려고 이마트나 하나로마트에서 장을 봐서 같이 요리해 먹었던 시간들. 서울에서 친구를 초대해서 주말에 이런저런 얘기 나누며 웃던 순간들까지, 하나하나가 다 기억에 남는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;산방사에 가서 용머리해안도 보고, 바이킹도 같이 타고, 한림해안로랑 해맞이해안도로를 따라 드라이브도 했다. 나중에 제주에 살게 된다면 어디가 좋을지 이야기하면서 동네를 구경하던 시간들도 참 행복했다. &lt;/span&gt;&lt;span&gt;혼자였다면 절대 느끼지 못했을 행복들이었고, 그런 순간들을 함께 만들어줘서 고마웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;낯선 환경에서, 낯선 선생님들 사이에서, 말 안 듣는 중2병 학생들까지 상대하느라 많이 힘들었을 텐데도 매일 밤 수업 준비하느라 애썼던 모습이 아직도 기억난다. 진짜 고생 많았고, 잘했다고 꼭 말해주고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;그리고 한편으로는 미안한 마음도 있다. 교생 때문에 많이 힘들었을 텐데, 나는 중간중간 괜히 예민하게 굴었던 것 같다. 내 감정을 좀 더 잘 신경 썼어야 했는데 그러지 못해서 미안해.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;6. 좋았던 점 &amp;amp; 아쉬웠던 점&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;좋았던 점&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;제주도 한 달 살이를 하면서 서울에서는 느끼기 어려운 감정과 추억들을 많이 남길 수 있었다. 그중에서도 가장 크게 와닿았던 건 &amp;ldquo;살기 좋은 곳이다&amp;rdquo;라는 느낌이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;b&gt;집값 부담도 상대적으로 덜하고, 동네 분위기도 정겹고, 사람들도 여유로웠다. 북적이지 않으면서도 적당한 인파가 있는 점도 좋았고, 어디든 차만 타면 드라이브하기 좋은 환경이라는 것도 큰 장점&lt;/b&gt;이었다. (정말 이게 중요하다는 것을 크게 느낀다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;전체적으로 가족들과 함께 알콩달콩 살기에 참 좋은 곳이라는 생각이 많이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;아쉬운 점&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;주말에 병원을 가려고 하면 제주시까지 나가야 했고, 한림&amp;middot;애월&amp;middot;대정 쪽은 주말에 여는 병원이 거의 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;또 하나는 물가였다. 제주도는 당연히 서울보다 저렴할 거라고 생각했는데, 막상 살아보니 꼭 그렇지만은 않았다. 생각보다 비슷하거나 오히려 비싼 부분도 있어서 조금 놀랐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;분리수거 방식도 꽤 불편했다. 서울에서는 원할 때 분리수거를 할 수 있었는데, 제주에서는 요일별로 배출 가능한 품목이 나뉘어 있고 시간도 정해져 있었다. 특히 오후부터만 가능해서, 아침에 버리고 싶어도 기다려야 하는 점이 불편하게 느껴졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;그리고 지역에 따라 분위기 차이도 있었다. 제주시 근처는 젊은 사람들이 비교적 많은 편이지만, 아래쪽으로 내려갈수록 젊은 층이 적었고 특히 20대는 거의 보기 힘들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;7. 결론&lt;span&gt;‼️&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여자친구한테 &amp;ldquo;제주도 한 달 살이 어땠어?&amp;rdquo;라고 물어보니, 여건만 된다면 제주도에서 살고 싶다고 했다. 선생님이라는 직업 특성상 어디서든 근무가 가능하니 여자친구 입장에서는 큰 문제가 없는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;문제는 나였다. 개발자라는 직업은 결국 IT 회사에 들어가야 하는데, 대부분의 회사가 서울에 몰려 있고 제주에는 상대적으로 많이 부족하다. 찾아보니 아예 없는 건 아니었지만, 내 분야뿐만 아니라 다른 분야까지 포함해도 생각보다 선택지가 많지 않았다. 잠깐 찾아본 것만으로도 3군데 정도였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서 나도 자연스럽게 고민이 생겼다. 개발자로서 제주에서 돈을 벌며 살아갈 수 있을까? 물론 방법이 아예 없는 건 아니다. 회사에 속하지 않고, 스스로 수익 구조를 만들어 살아가는 길도 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아무튼 한 달 살이를 하고 나서 제주도에 대한 생각이 꽤 많이 바뀌었다. 단순한 여행지가 아니라, &amp;lsquo;살 수 있는 곳&amp;rsquo;으로 보이기 시작했다. 남은 고민은 서울에 돌아가서 조금 더 진지하게 생각해봐야 할 것 같다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>∙기타</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/83</guid>
      <comments>https://coor.tistory.com/83#entry83comment</comments>
      <pubDate>Fri, 12 Jun 2026 22:26:35 +0900</pubDate>
    </item>
    <item>
      <title>수작업 100&amp;rarr;0% 세금계산서 자동화</title>
      <link>https://coor.tistory.com/80</link>
      <description>&lt;div id=&quot;toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이번에 세금계산서 자동화를 기능을 맡으면서, 어떤 데이터 기반으로 자동화를 하고 어떻게 설계를 하고 어떻게 자동화를 했는지 그리고 중간에 발생했던 문제를 어떻게 해결했는지 등 경험을 하고자 하여 글을 작성합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1. 왜 세금계산서 자동화하게 됐는지?&amp;nbsp;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;1a5cd6de61b4&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;세금계산서는 정기 과금, B2B 상품 판매, 외주 거래, 월세, 광고/마케팅 비용 등 다양한 비즈니스 상황에서 필수적으로 발행됩니다. 초기에는 발행 건수가 많지 않기 때문에 수기로 처리해도 큰 문제가 없습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;1a5cd6de61b4&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;&lt;br /&gt;&lt;b&gt;하지만 발행 건수가 수십, 수백, 수천 건으로 늘어나면 상황이 완전히 달라집니다.&lt;/b&gt;&lt;br /&gt;사람이 직접 처리하다 보면 금액 입력 실수, 중복 발행, 발행 대상 누락 등의 문제가 발생하기 쉽고, &amp;ldquo;누가 누구에게 발행했는지&amp;rdquo;를 일일이 추적하는 것도 점점 어려워집니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rBCVP/dJMcadHJCZY/E7rCGWlp7gbTAgGZ0qTOmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rBCVP/dJMcadHJCZY/E7rCGWlp7gbTAgGZ0qTOmk/img.png&quot; data-alt=&quot;현재 수작업 프로세스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rBCVP/dJMcadHJCZY/E7rCGWlp7gbTAgGZ0qTOmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrBCVP%2FdJMcadHJCZY%2FE7rCGWlp7gbTAgGZ0qTOmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;251&quot; height=&quot;401&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;현재 수작업 프로세스&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;1a5cd6de61b4&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;&lt;br /&gt;&lt;br /&gt;이러한 문제들은 단순한 불편함을 넘어, 회계 신뢰도와 운영 효율성에 직접적인 영향을 줍니다. 결국 일정 규모 이상에서는 수작업 방식으로는 한계가 명확해지고, 이를 해결하기 위해 자동화를 도입하게 됐습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;1a5cd6de61b4&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;&lt;br /&gt;이번에 진행한 세금계산서 자동화는 부동산 관리 플랫폼에서 임차인이 납부하는 임대료, 관리비, 기타 비용에 대해 세금계산서를 자동으로 발행하도록 구현하여, 반복적인 수작업을 제거하고 정확성과 효율성을 동시에 확보하는 것을 목표로 잡았습니다. 구체적인 목표는 다음과 같았습니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot; data-local-id=&quot;050eb8b4b4af&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;&lt;b&gt;1. 세금계산서 발행 소요 시간 100% 단축&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. 중복 발행 건수 0건 달성&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 발행 이후 데이터 변경 이력 관리율 100%&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-prosemirror-initial-todom-render=&quot;true&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;table&quot; data-prosemirror-node-block=&quot;true&quot;&gt;
&lt;div data-testid=&quot;table-alignment-container&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-number-column=&quot;false&quot; data-layout=&quot;default&quot; data-testid=&quot;table-container&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;89c0bebe1e64&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;89c0bebe1e64&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2. 세금계산서 자동화 요구사항&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;89c0bebe1e64&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;요구사항은 다음과 같습니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;050eb8b4b4af&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;1. 중복 발행 방지&lt;br /&gt;2. 발행 시 이메일을 추가하여 여러명한테 발송 가능&lt;br /&gt;3. 임대료와 관리비와 기타비용을 임차인과 다른 사람한테 발행 가능&lt;br /&gt;4. 청구서 과세/면세 여부에 따라 합산 발행 처리 (비용 절감)&lt;br /&gt;5. 발행 이후 데이터 조회/변경/취소 관리 (어드민)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;table&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-initial-todom-render=&quot;true&quot;&gt;
&lt;div data-testid=&quot;table-alignment-container&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;table-container&quot; data-layout=&quot;default&quot; data-number-column=&quot;false&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: left;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;89c0bebe1e64&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;89c0bebe1e64&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;89c0bebe1e64&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-1&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;3. 자동화를 하기 위한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;고민의 흔적들  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ERD 설계&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1841&quot; data-origin-height=&quot;1021&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y1JRu/dJMcaduMbz6/K4WVYtnBfmoXXNOigaD01k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y1JRu/dJMcaduMbz6/K4WVYtnBfmoXXNOigaD01k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y1JRu/dJMcaduMbz6/K4WVYtnBfmoXXNOigaD01k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY1JRu%2FdJMcaduMbz6%2FK4WVYtnBfmoXXNOigaD01k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1841&quot; height=&quot;1021&quot; data-origin-width=&quot;1841&quot; data-origin-height=&quot;1021&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일단 구조는 세금계산서 발행 정보를 담고 있는 테이블(&lt;b&gt;room_billing_tax&lt;/b&gt;)과 세금에 들어갈 청구서 정보들을 담고 있는 테이블(&lt;b&gt;room_billing_tax_mapping&lt;/b&gt;)을 나누어 1:N 구조로 잡았다. 그리고 임차인과 다른 사람한테 세금계산서 발행할 수 있도록 별도의 테이블(&lt;b&gt;contract_tax_buyer&lt;/b&gt;)을 만들었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;상세 필드들은 각각 나름의 이유가 있지만, 하나하나 모두 설명하기에는 다소 과한 부분이 있어서.. 전체적인 맥락에서 &amp;ldquo;이런 구조로 설계되었구나&amp;rdquo; 정도로 가볍게 봐주세요  &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;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;우리 서버 &amp;rarr; 국세청 API 만으로 자동화 가능한가?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;국세청 전자세금계산서 시스템은 존재하지만, 일반 개발자가 바로 붙을 수 있는 오픈 API 구조가 아니다...  보통 많이 사용하는 게 REST API 호출하는 게 아닌, XML 포맷 + 전송 규격을 맞춰야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;또한 API Key 넣어서 하는 인증서가 아닌 사업자용 공동인증서(구 공인인증서) 필요하다. 즉, &quot;요청 &amp;rarr; 전자서명 &amp;rarr; 암호화 &amp;rarr; 전송&quot; 이 과정을 직접 구현해야 한다. 물론 구현할 수 있지만 배보다 배꼽이 더 큰 상황이 펼쳐지게 된다&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;그래서 중개(미들웨어)를 사용해야 한다. 대표적으로 더존 / KG이니시스 / 비즈플레이 / KCP / 팝빌 / 바로빌 / 볼타 등 있다.&lt;br /&gt;회사마다 성향이 다른데&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- 더존 / KG이니시스 / 비즈플레이 / KCP = 업무 시스템(ERP/결제 중심) 이고&lt;br /&gt;- 팝빌 / 바로빌 / 불타 = 개발자용 API 플랫폼 이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;자동화를 해야 하기 때문에 팝빌 / 바로빌 / 불타 중에 골라야한다. 각 회사마다 장단점이 나뉘는데, 상세히 말하면 이야기 길어지기 때문에 크게 고려한 부분만 얘기하면&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;- 안전한가?&lt;br /&gt;- 운영 금액이 적당한가?&lt;br /&gt;- 문서가 잘되어있나?&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;b&gt;3가지 기준으로 적합했던 곳은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;a href=&quot;https://www.popbill.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;팝빌&lt;/a&gt;&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이었다!!&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;SDK 기반으로 개발을 편리하게 할 수 있고 무엇보다 &lt;b&gt;&lt;u&gt;&lt;a href=&quot;https://developers.popbill.com/guide/taxinvoice/getting-started/environment-set-up&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문서&lt;/a&gt;&lt;/u&gt;&lt;/b&gt;가 잘 되어 있어서 좋았다. 기술 문의 및 테스트 포인트 신청하는 경우 빠른 응답이 좋았다. 팝빌이 중개로 들어오면 우리 서버 + 팝빌 + 국세청 흐름은 아래와 같다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775897500795&quot; class=&quot;&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;┌──────────┐      API 호출       ┌──────────┐     자동 전송      ┌──────────┐
│          │  ───────────────&amp;rarr;  │          │  ─────────────&amp;rarr;  │          │
│  우리 서버  │                   │   팝빌    │                  │   국세청   │
│          │  &amp;larr;───────────────  │          │  &amp;larr;─────────────  │          │
└──────────┘   Webhook 콜백     └──────────┘   전송 결과 응답    └──────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;div data-mode=&quot;wide&quot; data-width=&quot;760&quot; data-prosemirror-content-type=&quot;mark&quot; data-prosemirror-mark-name=&quot;breakout&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;대량 발행 시 어떻게 처리할 것인가?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-local-id=&quot;faa49b03c29a&quot; data-prosemirror-content-type=&quot;node&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-node-block=&quot;true&quot;&gt;운영 데이터를 기준으로 확인해보니, 말일 기준 하루 최대 약 5,000건 정도의 청구서가 생성되었다. 이때 한 번에 대량 발행을 진행하면 트래픽이 급증할 수 있다고 판단하여, 처리 방식을 분산하는 방향으로 설계했다.&lt;br /&gt;&lt;br /&gt;초기에는 새벽 시간대에 일정 건수씩 나누어 배치 처리하는 방식도 고려했지만, 담당자 측에서 청구일 당일에 세금계산서를 바로 확인하고 싶다는 요구가 있었다.&lt;br /&gt;&lt;br /&gt;그래서 최종적으로는&amp;nbsp;&lt;b&gt;1시간&amp;nbsp;주기의&amp;nbsp;스케줄러를&amp;nbsp;통해&amp;nbsp;발행&amp;nbsp;대상을&amp;nbsp;분산&amp;nbsp;처리하는&amp;nbsp;방식&lt;/b&gt;을 선택했고, 이를 통해 트래픽 급증을 방지하면서도 실시간에 가까운 발행 요구사항을 만족시킬 수 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;발행 중복 검증 어떻게 할 것인가?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계약 ID&lt;/li&gt;
&lt;li&gt;호실 ID&lt;/li&gt;
&lt;li&gt;청구월&lt;/li&gt;
&lt;li&gt;청구일자&lt;/li&gt;
&lt;li&gt;공급받는자 등록번호&lt;/li&gt;
&lt;li&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;에 대해서는, 스케줄러는 1시간 주기로 동작하기 때문에 큰 문제가 없지만, 어드민에서 수동 발행 기능이 있어 동시 요청이 발생할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이를 해결하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;DB 테이블&amp;nbsp;스케줄러용 레코드를 별도로 두고, SELECT FOR UPDATE를 통해 행 수준의 배타 락&lt;/b&gt;을 적용하였다. 락을 획득한 프로세스만 발행 로직을 수행하고, 나머지는 락 해제 시까지 대기하도록 하여 동시 요청으로 인한 중복 발행을 방지했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;외부 API 실패 대응 어떻게 처리할 것인가?&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;b&gt;]&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1373&quot; data-end=&quot;1418&quot;&gt;외부 API 실패에 대해서는 상태 관리와 재시도 전략을 중심으로 대응했다. 우선 상태를 아래처럼 &lt;b&gt;PENDING, PROCESSING, SUCCESS, FAIL&lt;/b&gt; 관리하여 실패 건을 추적할 수 있도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1775897500798&quot; class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum TaxInvoiceStatus {
  // 팝빌 상태
  ISSUE_PENDING(&quot;발행대기&quot;, &quot;발행 대기&quot;),
  ISSUE_PROCESSING(&quot;발행진행중&quot;, &quot;발행 진행중&quot;),  
  ISSUE_SUCCESS(&quot;발행성공&quot;, &quot;발행 성공&quot;),
  ISSUE_FAIL(&quot;발행실패&quot;, &quot;발행 실패&quot;),

  // 국세청 상태
  SEND_BEFORE(&quot;전송전&quot;, &quot;팝빌에서 국세청 전송을 준비중인 상태&quot;),
  SEND_WAIT(&quot;전송대기&quot;, &quot;팝빌에서 국세청 전송을 대기중인 상태&quot;),
  SENDING(&quot;전송중&quot;, &quot;팝빌에서 국세청 전송을 진행중인 상태&quot;),
  SEND_SUCCESS(&quot;전송성공&quot;, &quot;전자세금계산서 국세청 신고가 정상적으로 완료된 상태&quot;),
  SEND_FAIL(&quot;전송실패&quot;, &quot;국세청이 특정사유로 전자세금계산서 신고를 반려한 상태&quot;),

  ISSUE_CANCEL(&quot;발행취소&quot;, &quot;운영자가 취소한 상태&quot;),

  CORRECTED(&quot;정정됨&quot;, &quot;재계약으로 수정세금계산서로 정정된 상태&quot;);
  
  ..
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1420&quot; data-end=&quot;1520&quot;&gt;네트워크 오류와 같은 일시적인 문제는&lt;span&gt; Redis Queue 넣어두고&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;재시도 로직을 통해 처리했다. 반복적인 실패가 발생할 경우에는 재시도 횟수를 제한하고, 최종 실패 건은 별도로 관리하여 운영자가 확인할 수 있도록 했다. 그리고 우리쪽에 데이터 문제나 코드 문제가 발생하는 경우 Teams로 알림을 보내서 개발자가 대응할 수 있도록 하였다.&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;778&quot; data-origin-height=&quot;1515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eq8aqU/dJMcaiXb9r5/2r0lE7lmEKAIKbd7Bljvzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eq8aqU/dJMcaiXb9r5/2r0lE7lmEKAIKbd7Bljvzk/img.png&quot; data-alt=&quot;실제 팀즈 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eq8aqU/dJMcaiXb9r5/2r0lE7lmEKAIKbd7Bljvzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feq8aqU%2FdJMcaiXb9r5%2F2r0lE7lmEKAIKbd7Bljvzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;217&quot; height=&quot;422&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;1515&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 팀즈 화면&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1420&quot; data-end=&quot;1520&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1420&quot; data-end=&quot;1520&quot;&gt;&lt;br /&gt;예외 상황으로, 외부에서 정상 처리되었는데&lt;span&gt;&amp;nbsp;&lt;/span&gt;timeout이 발생하여 응답을 받지 못하는 케이스가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1775897500800&quot; class=&quot;applescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;요청 보냄 &amp;rarr; 실제로는 성공했음
근데 응답 못 받음 (timeout)
&amp;ldquo;실패했네?&amp;rdquo; &amp;rarr; 다시 요청 보냄&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1420&quot; data-end=&quot;1520&quot;&gt;&amp;nbsp;timeout이 발생하더라도 실제로는 외부에서 정상 처리되었을 가능성이 있기 때문에, 단순히 실패로 처리하지 않고 팝빌 상태 조회 API를 통해 실제 발행 여부를 확인하여 데이터 정합성을 맞췄다.&lt;/p&gt;
&lt;pre id=&quot;code_1775897500800&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;발행 요청
   &amp;darr;
세금계산서 발행 외부 API 호출
   &amp;darr;
[성공] &amp;rarr; SUCCESS 
[실패] &amp;rarr; 재시도
[timeout]
   &amp;darr;
상태 조회 외부 API 호출
   &amp;darr;
있음 &amp;rarr; SUCCESS
없음 &amp;rarr; 재시도&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;공급받는자, 과세/면세 여부에 따라 합산 발행 어떻게 할것인가?&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;b&gt;]&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;1418&quot; data-start=&quot;1373&quot; data-ke-size=&quot;size16&quot;&gt;이번 건은&amp;nbsp;&lt;b&gt;비용 절감과 유연성을 동시에 만족해야 해서 가장 복잡한 부분이었다&lt;span&gt;  &lt;/span&gt;&lt;/b&gt;청구 항목은 임대료, 관리비, 기타비용으로 구성되어 있으며, 각 항목은 과세/면세가 혼합될 수 있다. 또한 공급받는자가 다를 경우 별도로 구분하여 발행해야 한다. 즉, 공급받는자 + 과세/면세 여부를 기준으로 세금계산서를 합산 발행해야 하는 구조였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;✔ 처리 기준&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;세금계산서는 다음 기준으로 그룹화하였다.&lt;br /&gt;- 공급받는자 식별값: 등록번호(사업자번호/주민등록번호)&lt;br /&gt;- 과세 / 면세 여부&lt;br /&gt;&lt;br /&gt;  즉, &quot;&lt;b&gt;같은 등록번호 + 같은 과세/면세&lt;/b&gt;&amp;rdquo;끼리 묶어서 1건으로 발행하는 식이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;✔ 예시 1 (공급받는자 동일)&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;홍길동&amp;nbsp;1명인&amp;nbsp;경우:&lt;br /&gt;- 임대료: 과세&lt;br /&gt;- 관리비: 과세 + 면세 혼합&lt;br /&gt;- 기타비용: 면세&lt;br /&gt;&lt;br /&gt; &amp;nbsp;결과&lt;br /&gt;- 과세: 임대료 + 관리비&lt;br /&gt;- 면세: 관리비 + 기타비용&lt;br /&gt;&lt;br /&gt;&amp;rarr;&amp;nbsp;총&amp;nbsp;2건&amp;nbsp;발행&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;✔ 예시 2 (공급받는자 다른 경우)&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;678&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;홍길동 / 김철수로 나뉘는 경우:&lt;/p&gt;
&lt;p data-end=&quot;678&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;홍길동&lt;br /&gt;- 과세: 임대료&lt;br /&gt;- 면세: 기타비용&lt;/p&gt;
&lt;p data-end=&quot;719&quot; data-start=&quot;713&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;김철수&lt;br /&gt;- 과세: 관리비&lt;br /&gt;- 면세: 관리비&lt;/p&gt;
&lt;p data-end=&quot;780&quot; data-start=&quot;745&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;br /&gt; &lt;span&gt; &lt;/span&gt;&lt;/span&gt;공급받는자별 + 과세/면세 기준으로&lt;br /&gt;&lt;b&gt;총 4건 발행&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;자 이렇게 문제 없이 공급받는자 변경해도 유연하게 처리하면서도, 과세/면세 합산 발행하여 불필요한 세금계산서 발행을 최소화하여 비용 절감을 할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;트랜잭션 범위를 어떻게 할 것인가?&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;b&gt;]&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1373&quot; data-end=&quot;1418&quot;&gt;초기에는 여러 청구서를 하나의 트랜잭션으로 처리하려고 했지만, 일부 청구서에서 세금계산서 발행이 실패할 경우 전체가 롤백되어 나머지 청구서까지 처리되지 않는 문제가 있다고 판단했다. 이러한 문제를 방지하기 위해 &lt;b&gt;트랜잭션 범위를 계약 단위로 분리&lt;/b&gt;하였다. 이를 통해 특정 계약에서 실패가 발생하더라도 다른 계약에는 영향을 주지 않도록 하여, 대량 발행 상황에서도 안정적으로 처리할 수 있도록 설계했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;faa49b03c29a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;4.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;전체 아키텍처&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;세금계산서&amp;nbsp;자동화&amp;nbsp;시스템은&amp;nbsp;크게&amp;nbsp;5개&amp;nbsp;레이어로&amp;nbsp;나뉩니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBXsFn/dJMcag6citx/kVdQp9yXrltX8phen6mQJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBXsFn/dJMcag6citx/kVdQp9yXrltX8phen6mQJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBXsFn/dJMcag6citx/kVdQp9yXrltX8phen6mQJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBXsFn%2FdJMcag6citx%2FkVdQp9yXrltX8phen6mQJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;762&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;762&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;각 레이어의 역할&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.9535%;&quot;&gt;&lt;b&gt;외부 스케줄러&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.0465%;&quot;&gt;- 매시간(1시간 주기) 내부 API를 호출하여 세금계산서 발행을 트리거하는 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.9535%;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fbfbfb; color: #403f53; text-align: start;&quot;&gt;내부 스케줄러&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.0465%;&quot;&gt;- 외부 스케줄러의 요청을 받아 정상 청구 및 재청구 발행 프로세스를 실행하는 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.9535%;&quot;&gt;&lt;b&gt;정상 청구 / 수정 발행&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.0465%;&quot;&gt;- 청구서를 세금계산서 발행 단위로 그룹화하고, 전체 발행 흐름을 orchestration 하는 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.9535%;&quot;&gt;&lt;b&gt;세금계산서 관리 서비스&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.0465%;&quot;&gt;&lt;span&gt;- 청구서 데이터를 기반으로 과세/면세 매핑 처리&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 세금계산서 발행, 상태 관리, 수정분 처리, 발행 취소 등&lt;br /&gt;핵심 비즈니스 로직을 담당하는 역할&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.9535%;&quot;&gt;&lt;b&gt;팝빌 연동&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.0465%;&quot;&gt;- 팝빌 SDK를 래핑한 단일 인터페이스 계층&lt;br /&gt;- 모든 외부 API 호출을 이 계층을 통해 수행&lt;br /&gt;- 국세청 전송(발행) 관련 액션 처리&lt;br /&gt;- Webhook을 통해 발행 결과 및 상태를 수신하는 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;5.&lt;span&gt;&lt;span&gt;&amp;nbsp;세금계산서 자동 발행 프로세스&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동 발행에는 두 가지 케이스가 있습니다. 매일 정기적으로 실행되는&amp;nbsp;&lt;b&gt;정상 발행&lt;/b&gt;과 청구 금액이 변경되거나 삭제되는 경우 기존 세금계산서를 정정해야 하는&lt;span&gt;&amp;nbsp;&lt;b&gt;수정&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&amp;nbsp;발행이 &lt;/b&gt;있습니다.&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 정상 발행&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Eqd8/dJMcahqvxVN/jeJZfbzj7NjX8V73m3AWfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Eqd8/dJMcahqvxVN/jeJZfbzj7NjX8V73m3AWfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Eqd8/dJMcahqvxVN/jeJZfbzj7NjX8V73m3AWfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Eqd8%2FdJMcahqvxVN%2FjeJZfbzj7NjX8V73m3AWfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1193&quot; height=&quot;754&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;수정 발행&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정세금계산서는 다음과 같은 흐름으로 발행됩니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6tOC7/dJMcacJv0YN/9ty6K87bvQD17jVPKjBxPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6tOC7/dJMcacJv0YN/9ty6K87bvQD17jVPKjBxPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6tOC7/dJMcacJv0YN/9ty6K87bvQD17jVPKjBxPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6tOC7%2FdJMcacJv0YN%2F9ty6K87bvQD17jVPKjBxPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1193&quot; height=&quot;753&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;6.&lt;span&gt;&lt;span&gt; 중간 발생했던 문제&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 수정&amp;nbsp;발행&amp;nbsp;시&amp;nbsp;외부&amp;nbsp;API&amp;nbsp;트랜잭션&amp;nbsp;불일치&amp;nbsp;문제&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&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;API를&amp;nbsp;최소&amp;nbsp;2번&amp;nbsp;호출해야&amp;nbsp;하는&amp;nbsp;케이스였다.&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;과정에서&amp;nbsp;가장&amp;nbsp;큰&amp;nbsp;문제는&amp;nbsp;트랜잭션의&amp;nbsp;일관성이었다.&amp;nbsp;&lt;b&gt;하나의&amp;nbsp;트랜잭션&amp;nbsp;안에서&amp;nbsp;처리한다고&amp;nbsp;해도,&amp;nbsp;외부&amp;nbsp;API는&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;발생&lt;/b&gt;할&amp;nbsp;수&amp;nbsp;있었다.&lt;br /&gt;&lt;br /&gt;초기에는&amp;nbsp;세금계산서를&amp;nbsp;즉시&amp;nbsp;전송&amp;nbsp;방식으로&amp;nbsp;처리했는데,&amp;nbsp;이&amp;nbsp;방식은&amp;nbsp;국세청으로&amp;nbsp;바로&amp;nbsp;전송되기&amp;nbsp;때문에&amp;nbsp;&lt;u&gt;&lt;a href=&quot;https://developers.popbill.com/reference/taxinvoice/java/api/issue#CancelIssue&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;발행&amp;nbsp;취소&lt;/b&gt;&lt;/a&gt;&lt;/u&gt;가&amp;nbsp;불가능했다.&amp;nbsp;따라서&amp;nbsp;중간에&amp;nbsp;문제가&amp;nbsp;발생하더라도&amp;nbsp;이미&amp;nbsp;발행된&amp;nbsp;세금계산서를&amp;nbsp;되돌릴&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;구조적인&amp;nbsp;한계가&amp;nbsp;있었다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crZ8un/dJMcabDOWnl/bkiKVZZIudIXm0LrT9s8ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crZ8un/dJMcabDOWnl/bkiKVZZIudIXm0LrT9s8ak/img.png&quot; data-alt=&quot;즉시 전송은 발행 취소 X&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crZ8un/dJMcabDOWnl/bkiKVZZIudIXm0LrT9s8ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrZ8un%2FdJMcabDOWnl%2FbkiKVZZIudIXm0LrT9s8ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;243&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;즉시 전송은 발행 취소 X&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이를 해결하기 위해 &lt;b&gt;전송 방식을 익일 자동 전송으로 변경&lt;/b&gt;하였다. 이 방식은 &lt;b&gt;국세청으로 전송되기 전까지 발행 취소가 가능&lt;/b&gt;하다는 점을 활용한 것이다. 또한 팝빌에서 제공하는 &lt;b&gt;&lt;u&gt;&lt;a href=&quot;https://developers.popbill.com/reference/taxinvoice/java/api/issue#SendToNTS&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;국세청 즉시 전송 API&lt;/a&gt;&lt;/u&gt;&lt;/b&gt;를 별도로 사용하여, 모든 발행 및 비즈니스 로직이 정상적으로 완료된 이후 마지막 단계에서만 전송이 이루어지도록 설계하였다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFuLoz/dJMcagkNWQx/biQfflDYLBopVlbkU8R9h0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFuLoz/dJMcagkNWQx/biQfflDYLBopVlbkU8R9h0/img.png&quot; data-alt=&quot;국세청 전송 방식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFuLoz/dJMcagkNWQx/biQfflDYLBopVlbkU8R9h0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFuLoz%2FdJMcagkNWQx%2FbiQfflDYLBopVlbkU8R9h0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;708&quot; height=&quot;173&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;국세청 전송 방식&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&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;API가&amp;nbsp;추가되었다.&amp;nbsp;아래&amp;nbsp;다이어그램에서도&amp;nbsp;이러한&amp;nbsp;변경&amp;nbsp;사항을&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;결과적으로,&amp;nbsp;중간에&amp;nbsp;문제가&amp;nbsp;발생할&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;외부&amp;nbsp;API&amp;nbsp;호출로&amp;nbsp;인한&amp;nbsp;트랜잭션&amp;nbsp;불일치&amp;nbsp;문제를&amp;nbsp;해결할&amp;nbsp;수&amp;nbsp;있었다.&lt;/p&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6mnN5/dJMcadn3Y4d/G9KGh8ZgxGk2ePqurZ0y8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6mnN5/dJMcadn3Y4d/G9KGh8ZgxGk2ePqurZ0y8k/img.png&quot; style=&quot;width: 47.1937%; margin-right: 10px;&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;824&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;47.75&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6mnN5/dJMcadn3Y4d/G9KGh8ZgxGk2ePqurZ0y8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6mnN5%2FdJMcadn3Y4d%2FG9KGh8ZgxGk2ePqurZ0y8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1193&quot; height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BVwKa/dJMcaaEVQ5P/Nv2Ay46LHXAkazFC5gwsfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BVwKa/dJMcaaEVQ5P/Nv2Ay46LHXAkazFC5gwsfk/img.png&quot; style=&quot;width: 51.6435%;&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;753&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;52.25&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BVwKa/dJMcaaEVQ5P/Nv2Ay46LHXAkazFC5gwsfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBVwKa%2FdJMcaaEVQ5P%2FNv2Ay46LHXAkazFC5gwsfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1193&quot; height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>∙Java &amp;amp; Spring</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/80</guid>
      <comments>https://coor.tistory.com/80#entry80comment</comments>
      <pubDate>Sun, 12 Apr 2026 00:05:29 +0900</pubDate>
    </item>
    <item>
      <title>2025년 회고</title>
      <link>https://coor.tistory.com/79</link>
      <description>&lt;div id=&quot;toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;올해는 드디어 나이 앞자리가 2에서 3으로 바뀌는 해다. 이제 30살이다.&lt;br /&gt;귀여웠던 29살을 지나 30살이 되면서 느꼈던 것들을 정리해보며, 2025년 회고를 써보려고 한다.&lt;/p&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpIbpP/dJMcadN8XSM/bxgX9NAqrHKMUAZSugP5Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpIbpP/dJMcadN8XSM/bxgX9NAqrHKMUAZSugP5Lk/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; width=&quot;372&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpIbpP/dJMcadN8XSM/bxgX9NAqrHKMUAZSugP5Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpIbpP%2FdJMcadN8XSM%2FbxgX9NAqrHKMUAZSugP5Lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCObhw/dJMcaiWfiHO/kg8kpA6xaHpcmiIKkHWfa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCObhw/dJMcaiWfiHO/kg8kpA6xaHpcmiIKkHWfa1/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCObhw/dJMcaiWfiHO/kg8kpA6xaHpcmiIKkHWfa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCObhw%2FdJMcaiWfiHO%2Fkg8kpA6xaHpcmiIKkHWfa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dz27dm/dJMcabpjlcI/ZzZ8GDOKN7mqZrcZFCEnS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dz27dm/dJMcabpjlcI/ZzZ8GDOKN7mqZrcZFCEnS0/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dz27dm/dJMcabpjlcI/ZzZ8GDOKN7mqZrcZFCEnS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdz27dm%2FdJMcabpjlcI%2FZzZ8GDOKN7mqZrcZFCEnS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나의&amp;nbsp;MBTI는&amp;nbsp;ISTJ이다.&amp;nbsp;내향적이면서&amp;nbsp;계획적인&amp;nbsp;성격이&amp;nbsp;장점이기도&amp;nbsp;하지만,&amp;nbsp;동시에&amp;nbsp;단점이&amp;nbsp;되기도&amp;nbsp;한다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;내향적인&amp;nbsp;성격이라&amp;nbsp;그런지,&amp;nbsp;말을&amp;nbsp;꺼내기&amp;nbsp;전에&amp;nbsp;자꾸&amp;nbsp;상대방의&amp;nbsp;기분이나&amp;nbsp;생각부터&amp;nbsp;신경&amp;nbsp;쓰다&amp;nbsp;보니&amp;nbsp;정작&amp;nbsp;내가&amp;nbsp;하고&amp;nbsp;싶은&amp;nbsp;말을&amp;nbsp;못할&amp;nbsp;때가&amp;nbsp;많았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들면&lt;br /&gt;친하지&amp;nbsp;않은&amp;nbsp;동료들과&amp;nbsp;함께&amp;nbsp;있을&amp;nbsp;때,&lt;br /&gt;나이&amp;nbsp;차이가&amp;nbsp;많이&amp;nbsp;나는&amp;nbsp;상사에게&amp;nbsp;피드백을&amp;nbsp;요청해야&amp;nbsp;할&amp;nbsp;때,&lt;br /&gt;옆자리에&amp;nbsp;앉아&amp;nbsp;있어도&amp;nbsp;하루&amp;nbsp;종일&amp;nbsp;한마디도&amp;nbsp;못&amp;nbsp;하고&amp;nbsp;내&amp;nbsp;모니터만&amp;nbsp;바라보고&amp;nbsp;있을&amp;nbsp;때가&amp;nbsp;있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;돌아보면,&amp;nbsp;남에게&amp;nbsp;큰&amp;nbsp;관심이&amp;nbsp;없고&amp;nbsp;말을&amp;nbsp;걸&amp;nbsp;때&amp;nbsp;고민이&amp;nbsp;너무&amp;nbsp;많은&amp;nbsp;성격이&amp;nbsp;오히려&amp;nbsp;나에게&amp;nbsp;걸림돌이&amp;nbsp;되었던&amp;nbsp;것&amp;nbsp;같다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;사실&amp;nbsp;그냥&amp;nbsp;크게&amp;nbsp;생각하지&amp;nbsp;말고&amp;nbsp;먼저&amp;nbsp;말&amp;nbsp;거는&amp;nbsp;게&amp;nbsp;중요한데&amp;nbsp;말이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;혹시나&amp;nbsp;상대가&amp;nbsp;기분&amp;nbsp;나빠&amp;nbsp;보이면,&amp;nbsp;그때&amp;nbsp;가서&amp;nbsp;솔직하게&amp;nbsp;미안하다고&amp;nbsp;말하면&amp;nbsp;된다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;괜히 머릿속으로만 고민하다가 결국 말 못 하면, 나중에 &quot;그때 말할걸...&quot; 하는 아쉬움만 남는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서&amp;nbsp;올해는&amp;nbsp;이렇게&amp;nbsp;생각하려고&amp;nbsp;한다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;괜히&amp;nbsp;혼자&amp;nbsp;끙끙대지&amp;nbsp;말고,&amp;nbsp;일단&amp;nbsp;먼저&amp;nbsp;말&amp;nbsp;걸어보자.&amp;nbsp;&amp;nbsp;&lt;br /&gt;그게&amp;nbsp;관심의&amp;nbsp;시작이고,&amp;nbsp;커뮤니케이션도&amp;nbsp;그렇게&amp;nbsp;시작되는&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;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;29살, 프리랜서로 갈아탔다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는&amp;nbsp;신입&amp;nbsp;때부터&amp;nbsp;프리랜서가&amp;nbsp;늘&amp;nbsp;궁금했다.&lt;br /&gt;하지만&amp;nbsp;어떻게&amp;nbsp;시작하는지,&amp;nbsp;뭘&amp;nbsp;어떻게&amp;nbsp;해야&amp;nbsp;하는지는&amp;nbsp;전혀&amp;nbsp;몰랐다.&lt;br /&gt;그래도&amp;nbsp;하나&amp;nbsp;알고&amp;nbsp;있던&amp;nbsp;건&amp;nbsp;있었다.&lt;br /&gt;프리랜서는 보통 3~5년차 정도는 되어야 시작할 수 있다는 것을.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 프리랜서를 해보고 싶었던 이유는 이거였다.&lt;br /&gt;주도적으로&amp;nbsp;프로젝트를&amp;nbsp;해보고&amp;nbsp;싶어서,&amp;nbsp;사람&amp;nbsp;중심보다는&amp;nbsp;일&amp;nbsp;중심으로&amp;nbsp;살고&amp;nbsp;싶어서,&lt;br /&gt;그리고... 그냥 한 번쯤은 꼭 경험해보고 싶어서.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;너무&amp;nbsp;나중에&amp;nbsp;도전하면&amp;nbsp;결혼,&amp;nbsp;애기&amp;nbsp;같은&amp;nbsp;현실적인&amp;nbsp;이유들&amp;nbsp;때문에&lt;br /&gt;그땐 못 하겠지 라는 생각이 들어서,&lt;br /&gt;초급과&amp;nbsp;중급&amp;nbsp;사이쯤&amp;nbsp;되는&amp;nbsp;지금&amp;nbsp;도전해보기로&amp;nbsp;했다.&lt;br /&gt;그리고 1~2년 해보고 아니면 다시 정직원으로 돌아가자 라는 플랜 B도 만들어놨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;그래서&amp;nbsp;2025년&amp;nbsp;8월,&amp;nbsp;회사를&amp;nbsp;그만두고&amp;nbsp;프리랜서를&amp;nbsp;시작했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;처음에는 프리랜서 형태 중에서 상주형이 아니라 &lt;b&gt;외주형&lt;/b&gt;으로 도전해봤다.&lt;br /&gt;크몽, 숨고, 당근마켓 같은 플랫폼에 나를 PR해서 일감을&amp;nbsp;따보는&amp;nbsp;게&amp;nbsp;목표였다.&lt;br /&gt;다른 사람들은 어떻게 하는지 알고 싶어서 책도&amp;nbsp;보고,&amp;nbsp;커피챗도&amp;nbsp;했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 초반에는 일감을 따내는 게 너무 어려웠다. (마케팅이 쉬운 게 정말 아니다~~)&lt;br /&gt;홈페이지 제작이나 로고 디자인처럼 인기 있는 분야에 비해 내&amp;nbsp;백엔드&amp;nbsp;개발&amp;nbsp;경험은&amp;nbsp;정말&amp;nbsp;시장이&amp;nbsp;좁았다.&lt;br /&gt;&quot;문제 해결 중심 + B2B API 개발 경험&quot; 이걸 누가 좋아하겠는가~~~!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;생활비는&amp;nbsp;벌어야&amp;nbsp;했기에&lt;br /&gt;쿠팡&amp;nbsp;물류센터&amp;nbsp;알바&amp;nbsp;vs&amp;nbsp;배달&amp;nbsp;라이더&amp;nbsp;사이에서&amp;nbsp;고민하다가,&lt;br /&gt;내 시간에 맞춰서 일할 수 있는 &lt;b&gt;배달 라이더&lt;/b&gt;를 선택했다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xt4bH/dJMcaaKIbRZ/Hlm9khc2cBjGP69RdXXDv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xt4bH/dJMcaaKIbRZ/Hlm9khc2cBjGP69RdXXDv1/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; width=&quot;363&quot; height=&quot;484&quot; data-widthpercent=&quot;33.33&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xt4bH/dJMcaaKIbRZ/Hlm9khc2cBjGP69RdXXDv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXt4bH%2FdJMcaaKIbRZ%2FHlm9khc2cBjGP69RdXXDv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VjZ5n/dJMcafSNnB1/m0hCJQTnW1BbgTspQ3bHb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VjZ5n/dJMcafSNnB1/m0hCJQTnW1BbgTspQ3bHb0/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VjZ5n/dJMcafSNnB1/m0hCJQTnW1BbgTspQ3bHb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVjZ5n%2FdJMcafSNnB1%2Fm0hCJQTnW1BbgTspQ3bHb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYzRRh/dJMcabv5zcL/Kl5OBO8yh2kcnx27w5z5L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYzRRh/dJMcabv5zcL/Kl5OBO8yh2kcnx27w5z5L1/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYzRRh/dJMcabv5zcL/Kl5OBO8yh2kcnx27w5z5L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYzRRh%2FdJMcabv5zcL%2FKl5OBO8yh2kcnx27w5z5L1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전기 자전거를 중고로 사서 점심 피크타임에 3시간씩 일했고, 한&amp;nbsp;달에&amp;nbsp;40~60만원&amp;nbsp;정도&amp;nbsp;벌었다.&lt;br /&gt;어떤 날은 콜이 안 와서 하루에 1~2건만 뛰고 끝나는 날도 있었는데... 그럴 때면 진짜 분했다 ㅠㅠ&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래도&amp;nbsp;한&amp;nbsp;가지&amp;nbsp;느낀&amp;nbsp;게&amp;nbsp;있다.&lt;br /&gt;외주형&amp;nbsp;프리랜서는&amp;nbsp;단기간에&amp;nbsp;잘&amp;nbsp;되는&amp;nbsp;게&amp;nbsp;아니다.&lt;br /&gt;많은 일감 중에서 &lt;b&gt;내가 잘하고, 동시에 수요도 많은 분야를 찾고 &lt;/b&gt;그쪽에서&amp;nbsp;전문성을&amp;nbsp;쌓고&amp;nbsp;마케팅까지&amp;nbsp;잘&amp;nbsp;해야&lt;br /&gt;일감을 따낼 수 있다는 것을.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서 &quot;외주형은 이 정도면 맛은 봤다&quot; 싶어서 이번에는 &lt;b&gt;상주형 프리랜서&lt;/b&gt;에도 도전해보기로 했다.&lt;br /&gt;잡코리아랑 원티드에 상주형 프리랜서로 지원했고, 약 30곳 정도 지원해서 그중&amp;nbsp;&lt;b&gt;2번&amp;nbsp;면접&amp;nbsp;기회&lt;/b&gt;를&amp;nbsp;얻었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;첫&amp;nbsp;번째&amp;nbsp;면접은&amp;nbsp;LG&amp;nbsp;계열사의&amp;nbsp;SI&amp;nbsp;쪽이었다.&lt;br /&gt;면접관 3명, 지원자 2명으로 진행됐는데... 솔직히 말해서 &lt;b&gt;면접은 개판이었다.&lt;/b&gt;&lt;br /&gt;야근 가능하냐? 적극적으로 일 찾아서 할 수 있냐? PL은 별 관심 없어 보이고...&lt;br /&gt;여기 들어가면 &quot;자바 개발자 두 명이요〜&quot; 하고&lt;br /&gt;어딘가에 배치될 것 같은 느낌?ㅋㅋ 들었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;두&amp;nbsp;번째&amp;nbsp;면접은&amp;nbsp;SK&amp;nbsp;계열사였다.&lt;br /&gt;PL와 시니어 백엔드 개발자 이렇게 면접관 두 분과 2:1로 진행됐다.&lt;br /&gt;이번엔 정상적인 면접이었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이력서 기반으로 어떤 일을 했는지, 어려움이&amp;nbsp;있었으면&amp;nbsp;어떻게&amp;nbsp;해결했는지,&lt;br /&gt;그리고 JPA, Stream, 트랜잭션 같은 기술적인 질문도 적당히 있었고,&lt;br /&gt;마지막에는 &quot;이런 일감인데 할 수 있겠냐&quot;고 물어보셨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;압박 면접도 거의 없어서 편하게&amp;nbsp;대답할&amp;nbsp;수&amp;nbsp;있었다.&lt;br /&gt;그리고 가장 중요하게 본 건 이 사람이 이 프로젝트를 잘 해낼 수 있는가 였다.&lt;br /&gt;다행히 내가 충분히 할 수 있는 일이었고, 그래서&amp;nbsp;자신&amp;nbsp;있게&amp;nbsp;대답할&amp;nbsp;수&amp;nbsp;있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그렇게 해서 2025년 9월, SK&amp;nbsp;계열사&amp;nbsp;프리랜서로&amp;nbsp;합격했다.&lt;br /&gt;현재는&amp;nbsp;재계약까지&amp;nbsp;해서&amp;nbsp;계속&amp;nbsp;근무&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;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;회사 문화, 생각보다 진짜 중요하다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년에는 회사를 총 3곳 다녔다.&lt;br /&gt;세 군데를 다니면서 느낀 게 있는데, 회사마다 문화가 정말 다르다는 거였다.&lt;br /&gt;그래서&amp;nbsp;내가&amp;nbsp;느낀&amp;nbsp;걸&amp;nbsp;기준으로&amp;nbsp;회사&amp;nbsp;문화를&amp;nbsp;크게&amp;nbsp;나눠보면&amp;nbsp;이런&amp;nbsp;것들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;문화&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;mdash;&amp;nbsp;수평적인&amp;nbsp;문화인가?&amp;nbsp;수직적인&amp;nbsp;문화인가?&lt;br /&gt;&lt;b&gt;분위기&lt;/b&gt;&amp;nbsp;&amp;mdash;&amp;nbsp;조용한가?&amp;nbsp;잔잔한가?&amp;nbsp;아니면&amp;nbsp;시끌벅적한가?&lt;br /&gt;&lt;b&gt;점심&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;mdash;&amp;nbsp;점심은&amp;nbsp;각자&amp;nbsp;먹는&amp;nbsp;분위기인가?&amp;nbsp;다&amp;nbsp;같이&amp;nbsp;먹는&amp;nbsp;문화인가?&lt;br /&gt;&lt;b&gt;그룹화&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;mdash;&amp;nbsp;이미&amp;nbsp;친한&amp;nbsp;사람들끼리만&amp;nbsp;어울리는가?&amp;nbsp;아니면&amp;nbsp;누구든&amp;nbsp;섞이기&amp;nbsp;쉬운가?&lt;br /&gt;&lt;b&gt;사교적&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;mdash;&amp;nbsp;새로운&amp;nbsp;사람에게&amp;nbsp;먼저&amp;nbsp;다가오는&amp;nbsp;분위긴가?&amp;nbsp;아니면&amp;nbsp;내가&amp;nbsp;먼저&amp;nbsp;다가가야만&amp;nbsp;하는&amp;nbsp;분위긴가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 세 회사에 다니기 전까지는 내가 어떤 회사 문화와 잘 맞는 사람인지 전혀 몰랐다.&lt;br /&gt;전에는 정말&amp;nbsp;&lt;b&gt;도메인과&amp;nbsp;기술&amp;nbsp;문화&lt;/b&gt;만&amp;nbsp;중요하게&amp;nbsp;생각했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;근데,&amp;nbsp;이제는&amp;nbsp;알겠다.&lt;br /&gt;회사 문화... 생각보다 진짜 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;왜냐하면,&lt;br /&gt;아무리&amp;nbsp;내가&amp;nbsp;좋아하는&amp;nbsp;도메인이고,&lt;br /&gt;아무리&amp;nbsp;마음에&amp;nbsp;드는&amp;nbsp;기술&amp;nbsp;스택이어도,&lt;br /&gt;회사&amp;nbsp;문화가&amp;nbsp;나랑&amp;nbsp;안&amp;nbsp;맞으면&amp;nbsp;오래&amp;nbsp;다니기가&amp;nbsp;정말&amp;nbsp;힘들기&amp;nbsp;때문이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&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;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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SNS 마케팅, 나도 한번 해봤다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000216669863&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;나는 솔로프리너다&lt;/a&gt;&lt;/b&gt;&amp;nbsp;책을 읽고, 올해부터 SNS 마케팅을 시작했다.&lt;br /&gt;쓰레드,&amp;nbsp;링크드인,&amp;nbsp;트위터,&amp;nbsp;인스타그램을&amp;nbsp;새로&amp;nbsp;만들었고,&lt;br /&gt;기존에&amp;nbsp;하던&amp;nbsp;티스토리는&amp;nbsp;3달에&amp;nbsp;한&amp;nbsp;번&amp;nbsp;정도&amp;nbsp;글을&amp;nbsp;올리고&amp;nbsp;있다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTYxCA/dJMcacuZN5C/opPSn1g1pazxatZ4Wsk79k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTYxCA/dJMcacuZN5C/opPSn1g1pazxatZ4Wsk79k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTYxCA/dJMcacuZN5C/opPSn1g1pazxatZ4Wsk79k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTYxCA%2FdJMcacuZN5C%2FopPSn1g1pazxatZ4Wsk79k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;217&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;217&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;SNS 마케팅을 한다고 해서 큰 업적을 세운 건 아니다...ㅋㅋ&lt;br /&gt;그래도 작은 성과를 꼽아보면 이 정도다.&lt;br /&gt;-&amp;nbsp;쓰레드&amp;nbsp;팔로워&amp;nbsp;50명&amp;nbsp;돌파&lt;br /&gt;-&amp;nbsp;티스토리&amp;nbsp;DAU&amp;nbsp;기준&amp;nbsp;하루&amp;nbsp;80명&amp;nbsp;돌파&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;2026년에는 꾸준히 올리면서 쓰레드&amp;nbsp;팔로워&amp;nbsp;300명을&amp;nbsp;목표로&amp;nbsp;하고&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2026년 목표 및 마무리&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최대한 추상적인 목표보다는 구체적인&amp;nbsp;목표로&amp;nbsp;정리해봤다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 쓰레드 팔로워 300명 돌파&lt;br /&gt;2. 재산 1억 달성&lt;br /&gt;3. 한 달에 책 1권 읽는 습관 들이기&lt;br /&gt;4. 수익 or 누군가에게 도움이 되는 웹/앱 만들어서 운영하기&lt;br /&gt;5. 배드민턴 C조 달성 (현재 D조, 2년째 정체 중...ㅠ)&lt;br /&gt;6. 가계부 작성하기&lt;br /&gt;7. 10kg 감량 (현재 80kg -&amp;gt; 70kg)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아직&amp;nbsp;주니어니까,&amp;nbsp;앞으로&amp;nbsp;계속&amp;nbsp;도전해볼&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;시간이&amp;nbsp;많다.&lt;br /&gt;실패한다고 해서 너무 좌절하지 말자. 실패하면서 배우는 게 진짜 많다.&lt;br /&gt;&lt;br /&gt;그리고 시도할 땐 그냥 막 하지 말고, 나름의&amp;nbsp;전략을&amp;nbsp;세워서&amp;nbsp;해보자.&lt;br /&gt;그 전략들을 하나씩 실행해보고, 또 실패도 해봐야 결국&amp;nbsp;성공이&amp;nbsp;나온다.&lt;br /&gt;그리고 그 성공이, 언젠가는 나를 바꿔주기도 하니까~~~&lt;/p&gt;
&lt;/div&gt;</description>
      <category>∙회고</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/79</guid>
      <comments>https://coor.tistory.com/79#entry79comment</comments>
      <pubDate>Thu, 1 Jan 2026 13:25:58 +0900</pubDate>
    </item>
    <item>
      <title>주니어 백엔드 개발자가 반드시 알아야 할 실무 지식</title>
      <link>https://coor.tistory.com/77</link>
      <description>&lt;div id=&quot;toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 2025년 4월 28일에 출간된 따끈따끈한 신간이다. &lt;b&gt;배드민턴에서 땀을 나눈 파트너에게 추천을 받기도 했고, 팀장님의 블로그에서 이 책에 대한 언급&lt;/b&gt;을 보며 자연스럽게 관심을 갖게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;345&quot; data-start=&quot;214&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;내용은 주니어 개발자가 반드시 알아야 할 핵심 주제들을 여러 카테고리로 나누어 정리한 구성이다. 만약 내가 신입 개발자였더라면, 아는 것보다 모르는 게 훨씬 많았을 것이고, 책을 이해하는 데도 훨씬 더 많은 시간이 걸렸을 것 같다.&lt;/p&gt;
&lt;p data-end=&quot;464&quot; data-start=&quot;347&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;만 3년 차가 된 지금 읽어보니, 알고 있던 내용은 복습처럼 다시 머릿속에 정리되었고, 몰랐던 부분은 새롭게 배울 수 있었다. 실제로 읽다가 &amp;ldquo;이건 처음 듣는 개념인데?&amp;rdquo; 싶었던 부분도 몇 군데 있었다.&lt;/p&gt;
&lt;p data-end=&quot;464&quot; data-start=&quot;347&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아래에는 내가 읽으며 도움이 되었던 내용, 이전에는 몰랐던 내용, 나중에 다시 꺼내 읽고 싶었던 부분들을 정리해두었다.&lt;/p&gt;
&lt;p data-end=&quot;585&quot; data-start=&quot;466&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;585&quot; data-start=&quot;466&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;564&quot; data-start=&quot;547&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DB 커넥션 풀 크기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;565&quot; data-ke-size=&quot;size16&quot;&gt;서버는 DB와의 통신을 지속적으로 수행하기 때문에, DB 커넥션 풀 설정이 성능에 큰 영향을 미친다. 스프링 부트에서는 기본적으로 HikariCP를 사용하며, 최소 및 최대 커넥션 수는 기본값으로 10개로 설정되어 있다. 트래픽은 서비스 유형에 따라 시간대별로 달라진다.&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;565&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들어 금융 서비스는 낮 시간대, 게임 서비스는 저녁 시간대에 트래픽이 집중되는 경우가 많다. 이에 따라 커넥션 풀도 유동적으로 조절할 필요가 있다. 트래픽이 급증하는 패턴이라면 최소 크기를 최대 크기 수준으로 맞춰두는 것이 성능 저하를 방지하는 데 도움이 된다.&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;565&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;물론 커넥션 풀을 무조건 크게 잡는 것은 바람직하지 않다. DB의 상태를 고려해야 하며, CPU 사용률이 이미 높은 상태에서 커넥션을 더 열면 오히려 쿼리 처리 시간이 증가할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;565&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;565&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;995&quot; data-start=&quot;977&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DB 커넥션 대기 시간&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1242&quot; data-start=&quot;996&quot; data-ke-size=&quot;size16&quot;&gt;HikariCP의 기본 커넥션 대기 시간은 30초로 설정되어 있다. 하지만 일반적으로는 서비스 특성에 따라 0.5초에서 3초 이내로 설정하는 것이 권장된다. 대기 시간을 짧게 설정하면 커넥션 풀이 모두 사용 중일 때 사용자에게 &quot;일시적 오류&quot;를 반환하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;1242&quot; data-start=&quot;996&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이는 부정적으로 보일 수 있으나, 오랜 시간 응답이 없는 것보다는 낫다. 짧은 대기 시간은 서버의 과도한 부하를 막고, 전체 시스템을 보다 안정적으로 유지하는 데 기여할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1242&quot; data-start=&quot;996&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;1242&quot; data-start=&quot;996&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1264&quot; data-start=&quot;1249&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스트림을 활용하라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1500&quot; data-start=&quot;1265&quot; data-ke-size=&quot;size16&quot;&gt;파일 다운로드 기능을 구현할 때, 파일 전체를 한꺼번에 메모리에 로딩해서 응답하는 방식은 지양해야 한다. 파일 크기나 동시 사용자 수에 따라 메모리 사용량이 급증할 수 있으며, 이는 Java 환경에서는 OutOfMemoryError를 유발할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1500&quot; data-start=&quot;1265&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;스트림을 활용하면 파일을 부분적으로 읽고 처리할 수 있어 메모리 부담을 줄일 수 있다. 따라서 대용량 파일을 다루는 상황에서는 스트림 기반 처리 방식이 보다 안정적이다.&lt;/p&gt;
&lt;p data-end=&quot;1500&quot; data-start=&quot;1265&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1500&quot; data-start=&quot;1265&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;1523&quot; data-start=&quot;1507&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정적 자원과 CDN&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1866&quot; data-start=&quot;1524&quot; data-ke-size=&quot;size16&quot;&gt;브라우저 캐시는 각 사용자 단위로 동작하기 때문에, 많은 사용자가 동시에 접속할 경우 js, css 등 정적 파일이 대량으로 전송되며 &lt;br /&gt;네트워크가 급격히 포화될 수 있다. 이는 4차선 도로에 갑자기 수십 대의 차량이 몰려들어 교통 체증이 발생하는 것과 비슷하다.&lt;/p&gt;
&lt;p data-end=&quot;1866&quot; data-start=&quot;1524&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이러한 상황에서는 CDN을 사용하는 것이 효과적이다. CDN은 클라이언트가 콘텐츠를 요청하면 가까운 CDN 서버에서 파일을 제공하고, 없는 경우에는 원본 서버에서 가져와 캐시에 저장한다. 이후에는 동일한 요청이 있을 때 캐시에서 바로 응답하게 된다. 이를 통해 원본 서버가 처리해야 할 트래픽이 크게 줄어들어, 전반적인 응답 속도와 안정성이 향상된다.&lt;/p&gt;
&lt;p data-end=&quot;1866&quot; data-start=&quot;1524&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;1866&quot; data-start=&quot;1524&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1895&quot; data-start=&quot;1873&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;선착순, 예매 같은 대기 처리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2335&quot; data-start=&quot;1896&quot; data-ke-size=&quot;size16&quot;&gt;공연 예매처럼 짧은 시간에 수많은 사용자가 몰리는 상황에서는, 서버와 DB 모두 큰 부하를 겪게 된다. 첫 번째 해결 방법은 트래픽 예측에 기반하여 서버와 DB를 미리 증설하는 것이다. 하지만 특히 DB는 증설 후 다시 줄이는 것이 어렵고 비용도 많이 든다.&lt;/p&gt;
&lt;p data-end=&quot;2335&quot; data-start=&quot;1896&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;전체 서비스 시간 중 극히 짧은 순간을 위해 고정된 비용을 계속 부담하는 셈이다. 두 번째 방법은 일정 수준의 트래픽만 수용하고 나머지는 대기 처리하는 방식이다. KTX 예매 서비스처럼, 일정 인원까지만 페이지에 접근을 허용하고, 그 외의 사용자는 대기열에 두는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;2335&quot; data-start=&quot;1896&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 방식은 서버를 증설하지 않더라도 트래픽 폭주를 효과적으로 제어할 수 있고, 무한 새로고침으로 인한 불필요한 부하를 방지할 수 있다. 사용자가 대기하는 것이 불편하긴 해도, 서비스가 아예 접속 불가능한 것보다는 낫기 때문에 합리적인 선택이 될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2335&quot; data-start=&quot;1896&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;2335&quot; data-start=&quot;1896&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2362&quot; data-start=&quot;2342&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;미리 집계하기 (반정규화)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2660&quot; data-start=&quot;2363&quot; data-ke-size=&quot;size16&quot;&gt;count나 sum 같은 집계 쿼리는 데이터가 적을 때는 문제가 없지만, 데이터가 많아질수록 성능 저하가 심해진다. 실제로 로그 데이터가 100만 건이 넘는 상황에서 count 쿼리 하나에 4초 이상 소요되는 사례도 있었다.&lt;/p&gt;
&lt;p data-end=&quot;2660&quot; data-start=&quot;2363&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이럴 경우 첫 번째 방법은 집계 데이터 자체를 UI에서 제거하거나, 담당자와 협의하여 표시하지 않도록 조정하는 것이다. 두 번째 방법은 미리 집계한 결과를 별도의 칼럼에 저장해두는 방식이다. 이를 통해 실시간 집계 연산 없이도 빠르게 데이터를 제공할 수 있으며, 성능 문제도 동시에 해결할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2660&quot; data-start=&quot;2363&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2660&quot; data-start=&quot;2363&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;2698&quot; data-start=&quot;2667&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;상태 변경 기능은 복제 DB에서 조회하지 않기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;3036&quot; data-start=&quot;2699&quot; data-ke-size=&quot;size16&quot;&gt;복제 DB를 사용할 경우, 보통 조회는 복제 DB, 쓰기 작업은 주 DB를 통해 처리하게 된다. 그런데 이 구조를 오해하여 모든 조회 쿼리를 복제 DB에서 실행하게 되면 문제가 발생할 수 있다. 복제 DB는 주 DB의 데이터를 네트워크를 통해 복제받는 구조이기 때문에 일정한 지연이 존재한다.&lt;/p&gt;
&lt;p data-end=&quot;3036&quot; data-start=&quot;2699&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 지연 동안 복제 DB와 주 DB 사이에 데이터 불일치가 발생할 수 있다. 따라서 회원가입, 상태 변경, 삭제 등 변경이 발생하는 시점에 데이터를 조회해야 한다면 반드시 주 DB를 사용해야 한다. 그래야 변경 직후의 정확한 데이터를 기반으로 처리를 할 수 있으며, 복제 지연으로 인한 오류도 방지할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3036&quot; data-start=&quot;2699&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3036&quot; data-start=&quot;2699&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;134&quot; data-start=&quot;105&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;메시지 소비 측에서 고려해야 할 중복 처리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;575&quot; data-start=&quot;135&quot; data-ke-size=&quot;size16&quot;&gt;메시지를 소비하는 쪽에서는 동일한 메시지를 중복으로 처리하는 상황이 발생할 수 있다. 그 이유는 크게 두 가지다. 첫 번째는 메시지 생산자가 동일한 데이터를 가진 메시지를 두 번 보냈을 경우다. 이런 경우 수신자는 메시지를 구분할 수 있는 고유 ID를 기준으로 이미 처리한 메시지인지 확인하고, 중복 처리를 방지할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;575&quot; data-start=&quot;135&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;두 번째는 메시지 처리 중 오류가 발생했을 때다. 예를 들어 외부 API 호출 중 읽기 타임아웃이 발생하면 소비자는 실패했다고 판단해 메시지를 다시 수신하여 재처리할 수 있다. 하지만 실제로는 API 호출이 성공했을 수도 있기 때문에 이 부분이 문제가 될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;575&quot; data-start=&quot;135&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이럴 때는 외부 API 자체가 멱등성을 갖도록 설계하는 것이 좋다. 멱등성(idempotency)을 갖추면 같은 요청을 여러 번 해도 결과가 동일하므로, 중복 요청에 대한 부작용을 줄일 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;575&quot; data-start=&quot;135&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;575&quot; data-start=&quot;135&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;595&quot; data-start=&quot;582&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;궁극적 일관성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;924&quot; data-start=&quot;596&quot; data-ke-size=&quot;size16&quot;&gt;분산 시스템이나 비동기 연동에서 자주 등장하는 개념이 궁극적 일관성(Eventual Consistency)이다. 두 저장소 간에 데이터의 일관성은 보장되지만, 실시간으로 즉시 일치하지는 않고 일정 시간이 지난 뒤에 맞춰지는 방식이다.&lt;/p&gt;
&lt;p data-end=&quot;924&quot; data-start=&quot;596&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;한때 함께 일했던 팀장님이 &amp;ldquo;배송 완료 알림톡 받고 나서 다시 취소됐다는 알림이 온 적 없느냐&amp;rdquo;고 했던 말이 떠오른다. 실제로 쇼핑몰에서 알림 메시지를 보낸 뒤 상태가 번복되어 다시 알림이 오는 경우가 있는데, 이런 현상은 시스템 간 동기화가 완전히 되기 전에 발생하는 데이터 불일치 때문이다. 비동기 방식에서는 이처럼 일시적인 불일치를 감안해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;924&quot; data-start=&quot;596&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;924&quot; data-start=&quot;596&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;949&quot; data-start=&quot;931&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비동기로 돌아가는 세상&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1192&quot; data-start=&quot;950&quot; data-ke-size=&quot;size16&quot;&gt;모든 시스템 연동이 반드시 동기 방식일 필요는 없다. 오히려 비동기로 구현해도 전혀 문제가 없는 경우도 많다. 물론 비동기 방식은 구조가 더 복잡해지고, 시스템 간 데이터 불일치가 발생할 가능성도 있어서 고려할 부분이 많아진다.&lt;/p&gt;
&lt;p data-end=&quot;1192&quot; data-start=&quot;950&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 성능이나 확장성, 자율성 측면에서는 오히려 더 유리할 수 있다. 그래서 비동기 방식은 단순히 기술적인 선택이 아니라, 복잡도 증가 대비 얻을 수 있는 이점이 더 크다면 충분히 고려해볼 가치가 있다.&lt;/p&gt;
&lt;p data-end=&quot;1192&quot; data-start=&quot;950&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1192&quot; data-start=&quot;950&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;1221&quot; data-start=&quot;1199&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HMAC을 이용한 데이터 검증&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1693&quot; data-start=&quot;1222&quot; data-ke-size=&quot;size16&quot;&gt;API 통신에서 클라이언트가 서버로 데이터를 전송할 때, 중간에 누군가가 데이터를 위변조하면 실제 지급해야 하는 포인트보다 더 많은 포인트가 지급되는 등 문제가 발생할 수 있다. 이를 방지하려면 메시지가 위변조되지 않았다는 것을 확인할 수 있는 방법이 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;1693&quot; data-start=&quot;1222&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이럴 때 주로 사용하는 기술이 HMAC이다. HMAC은 Hash-based Message Authentication Code의 약자로, 메시지의 무결성과 인증을 보장하는 암호화 방식이다. 메시지 발신자와 수신자가 공유하는 비밀 키를 기반으로 메시지를 암호화하고 검증할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1693&quot; data-start=&quot;1222&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예전에 shopline이라는 쇼핑몰 서비스에서 HMAC을 사용하는 사례를 접한 적이 있다. 클라이언트가 request body와 함께 HMAC 값을 보내주면, 서버 측에서 동일한 방식으로 생성된 값을 비교하여 메시지가 위변조되지 않았는지 확인할 수 있다. 이런 방식으로 API 보안을 강화할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1693&quot; data-start=&quot;1222&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1693&quot; data-start=&quot;1222&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;1715&quot; data-start=&quot;1700&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;QUIC 프로토콜&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2191&quot; data-start=&quot;1716&quot; data-ke-size=&quot;size16&quot;&gt;TCP는 안정적이지만 느리고, UDP는 빠르지만 신뢰성이 부족하다. 이 두 프로토콜의 장점을 결합한 것이 바로 QUIC이다. QUIC은 UDP를 기반으로 하면서도 TCP처럼 연결을 관리할 수 있도록 설계되었다.&lt;/p&gt;
&lt;p data-end=&quot;2191&quot; data-start=&quot;1716&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들어 연결 ID를 통해 두 노드 간의 세션을 유지하고, 혼잡 제어나 패킷 유실 복구 기능도 QUIC에서 직접 제공한다. 또 하나의 큰 특징은 TLS와의 통합이다. 기존 HTTPS는 TCP 연결을 위한 3-way 핸드셰이크와 TLS 핸드셰이크를 별도로 진행해야 했는데, QUIC은 이 과정을 하나로 통합함으로써 연결 수립 시간을 단축시킨다.&lt;/p&gt;
&lt;p data-end=&quot;2191&quot; data-start=&quot;1716&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;현재 QUIC은 HTTP/3의 기반이 되며, 크롬&amp;middot;엣지&amp;middot;사파리 등 주요 브라우저가 이미 이를 지원하고 있다. 구글, 페이스북 등 대규모 트래픽을 처리하는 기업들도 QUIC 기반의 HTTP/3를 채택하고 있다. 빠르면서도 안정성을 갖춘 통신이 필요한 상황에서는 QUIC이 점점 더 주목받고 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>∙도서</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/77</guid>
      <comments>https://coor.tistory.com/77#entry77comment</comments>
      <pubDate>Wed, 2 Jul 2025 15:18:32 +0900</pubDate>
    </item>
    <item>
      <title>Redis ZSet을 활용한 외부 API 재시도 큐 구현기</title>
      <link>https://coor.tistory.com/76</link>
      <description>&lt;div id=&quot;toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1. 문제 배경&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마케팅 프로젝트를 진행하던 중 외부 서비스인 &lt;b&gt;Shopline API&lt;/b&gt;를 사용해야 했습니다. 하지만 이 API에는 &lt;b&gt;요청 제한(Rate Limit)&lt;/b&gt;이 걸려 있었고, 이 제한은 클라이언트의&amp;nbsp;&lt;b&gt;요금제&lt;/b&gt;&amp;nbsp;&lt;b&gt;플랜&lt;/b&gt;에 따라 달라졌습니다. 기업 요금제에서는 제한이 없지만, 문제는 해당 클라이언트의 요금제 플랜 업그레이드를 &lt;b&gt;우리가 직접 제어할 수 없고 클라이언트 측에서 진행해야 하는 상황&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHVoWk/btsOjheksII/4KBDmS1UrHjfkyv3xdWEw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHVoWk/btsOjheksII/4KBDmS1UrHjfkyv3xdWEw0/img.png&quot; data-alt=&quot;1초에 4회&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHVoWk/btsOjheksII/4KBDmS1UrHjfkyv3xdWEw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHVoWk%2FbtsOjheksII%2F4KBDmS1UrHjfkyv3xdWEw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;243&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1초에 4회&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-end=&quot;583&quot; data-start=&quot;372&quot; data-ke-size=&quot;size16&quot;&gt;Shopline API는 &lt;b&gt;토큰 요청&lt;/b&gt;, &lt;b&gt;리프레시 요청&lt;/b&gt;, &lt;b&gt;기프트 카드 생성&lt;/b&gt;, &lt;b&gt;웹 훅 구독&lt;/b&gt;&amp;nbsp;등 여러 가지 작업에 사용되는데, 이 모든 요청은 &lt;b&gt;1초에 4회&lt;/b&gt;라는 제한이 있었고, 특히 &lt;b&gt;토큰/리프레시 요청은 공용 제한&lt;/b&gt;이라 여러 작업에서 동시에 요청이 몰릴 경우 &lt;b&gt;HTTP 429 Too Many Requests&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;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;heading-1&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2. 해결 방안  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt; 1초에 4번 요청하기&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 처음으로 생각했던 게 &lt;b&gt;&quot;그냥 4번 요청하고 스레드 1초 멈췄다가 다시 4번 보내자 ! &quot; &lt;/b&gt;정말 쉽게 생각했었습니다ㅋㅋㅋ 하지만 이 방법은 안정성이 떨어지고 분산 환경에서 제어하기 어려움이 있습니다. 그리고 토큰 요청과 리프레시 요청은 공용 제한이어서 1초에 4번 확실하지 않아서 문제를 발생할 수 있습니다. 그래서 이 방법은 가볍게 skip~~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 재시도 + 지수 백오프&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;876&quot; data-start=&quot;676&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째 방안은 API 요청을 보내고&amp;nbsp;429 오류 발생 시 재시도&lt;/b&gt;하면서, 일정 시간 간격을 점점 늘리는 &lt;b&gt;지수 백오프 방식&lt;/b&gt;을 적용했습니다. 그러나 이 방법은 API가 지속적으로 실패할 경우, 스레드가 계속해서 &lt;b&gt;블로킹되며&lt;/b&gt; &lt;b&gt;리소스를 점유&lt;/b&gt;하는 문제가 발생했습니다. 특히 스레드 풀 자원이 부족해지면서 다른 비동기 작업까지 영향을 주는 상황이 생겼고, 이는 단순 재시도 방식의 한계를 체감하게 된 계기가 되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;876&quot; data-start=&quot;676&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예전에 Openai API 사용할 때 요청 제한이 있어서 &lt;u&gt;&lt;b&gt;&lt;a href=&quot;https://coor.tistory.com/72&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Feign Client + 재시도 + 지수 백오프 방식&lt;/a&gt;&lt;/b&gt;&lt;/u&gt;으로 진행했는데, 이 때는 주체가 클라이언트가 아니고 프로젝트 단위여서 가능했었습니다. 프로젝트도 mvp 여서 큰 문제 없이 사용했던 기억이 나네요.&lt;/p&gt;
&lt;p data-end=&quot;876&quot; data-start=&quot;676&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;876&quot; data-start=&quot;676&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;[ Redis ZSet 기반 비동기 재시도 처리&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;다이어그램 수정 필요&lt;br /&gt;&amp;gt; 2번 실패 시 fail 내용 추가&lt;br /&gt;&amp;gt; 3번 스케쥴러에 주기적으로 어떻게 돌아가는지 짧은 세부 내용 추가&lt;br /&gt;&amp;gt; 4번 화살표 반대로 그리고 짧은 세부 내용 추가&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dC2ZiH/btsOiDCyHeo/N9xAnuq7qFEEV5jK5o7L2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dC2ZiH/btsOiDCyHeo/N9xAnuq7qFEEV5jK5o7L2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dC2ZiH/btsOiDCyHeo/N9xAnuq7qFEEV5jK5o7L2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdC2ZiH%2FbtsOiDCyHeo%2FN9xAnuq7qFEEV5jK5o7L2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1194&quot; height=&quot;934&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;676&quot; data-end=&quot;876&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세 번째 방안은&amp;nbsp;&lt;/b&gt;&lt;b&gt;비동기 재시도 큐&lt;/b&gt;를 도입하게 되었고, Redis의 ZSet 을 활용하여 &lt;b&gt;요청 실패를 저장하고, 순차적으로 재처리하는 구조&lt;/b&gt;를 설계하게 되었습니다. 설계한 내용을 천천히 설명해보겠습니다.&lt;/p&gt;
&lt;p data-end=&quot;876&quot; data-start=&quot;676&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;Redis ZSet을 선택한 이유&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;876&quot; data-start=&quot;676&quot;&gt;ZSet은 데이터 추가, 삭제, 조회가 모두 용이하며 score를 활용해 시간 기반 정렬이 가능하고,&lt;/li&gt;
&lt;li data-end=&quot;876&quot; data-start=&quot;676&quot;&gt;실패한 요청을 순서대로 재처리할 수 있어 안정적인 요청 흐름을 유지할 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;876&quot; data-start=&quot;676&quot;&gt;만약 재시도 10번이 넘어가면 기프트 카드 발급 같은 api는 클라이언트 측에서 &lt;b&gt;&quot;왜 안오지?&quot;&lt;/b&gt; 생각할 수 있기 때문에 선순위를 보장할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;ZSet 설계 구조&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;768&quot; data-start=&quot;668&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;695&quot; data-start=&quot;668&quot;&gt;&lt;b&gt;Key&lt;/b&gt;: shopline:retry&lt;/li&gt;
&lt;li data-end=&quot;729&quot; data-start=&quot;696&quot;&gt;&lt;b&gt;Score&lt;/b&gt;: 처음 실패 발생 시간 (timestamp)&lt;/li&gt;
&lt;li data-end=&quot;768&quot; data-start=&quot;730&quot;&gt;&lt;b&gt;Value&lt;/b&gt;: 재시도에 필요한 데이터를 담은 JSON 문자열
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;768&quot; data-start=&quot;730&quot;&gt;&lt;b&gt;type&lt;/b&gt;: API 타입&lt;/li&gt;
&lt;li data-end=&quot;768&quot; data-start=&quot;730&quot;&gt;&lt;b&gt;payload:&lt;/b&gt; 타입별로 필요한 데이터&lt;/li&gt;
&lt;li data-end=&quot;768&quot; data-start=&quot;730&quot;&gt;&lt;b&gt;retryCount&lt;/b&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;&lt;span data-token-index=&quot;0&quot;&gt;예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;ZADD shopline:retry 1716114000000 '{&quot;type&quot;:&quot;GIFT&quot;,&quot;payload&quot;:{...},&quot;retryCount&quot;:0}'
ZADD shopline:retry 1820381000021 '{&quot;type&quot;:&quot;WEBHOOK&quot;,&quot;payload&quot;:{...},&quot;retryCount&quot;:3}'
ZADD shopline:retry 1902395400009 '{&quot;type&quot;:&quot;REFRESH&quot;,&quot;payload&quot;:{...},&quot;retryCount&quot;:5}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;스케줄러 재시도 로직&lt;/b&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p data-end=&quot;1546&quot; data-start=&quot;1486&quot; data-ke-size=&quot;size16&quot;&gt;실패 요청은 Redis에 저장된 후, &lt;b&gt;주기적으로 실행되는 스케줄러&lt;/b&gt;가 이를 조회하여 다시 재시도합니다.&lt;/p&gt;
&lt;p data-end=&quot;1546&quot; data-start=&quot;1486&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1546&quot; data-start=&quot;1486&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-end=&quot;1708&quot; data-start=&quot;1559&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1614&quot; data-start=&quot;1559&quot;&gt;@Scheduled(fixedDelay = 10_000) 으로 &lt;b&gt;10초 간격&lt;/b&gt;으로 실행&lt;/li&gt;
&lt;li data-end=&quot;1642&quot; data-start=&quot;1615&quot;&gt;Redis에서 오름차순으로 전체 조회&lt;/li&gt;
&lt;li data-end=&quot;1684&quot; data-start=&quot;1643&quot;&gt;재시도 실행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1684&quot; data-start=&quot;1643&quot;&gt;성공 시 데이터 삭제&lt;/li&gt;
&lt;li data-end=&quot;1684&quot; data-start=&quot;1643&quot;&gt;실패 시 continue&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1708&quot; data-start=&quot;1685&quot;&gt;&lt;b&gt;재시도 횟수는 30회까지&lt;/b&gt; 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중복 방지는&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1708&quot; data-start=&quot;1559&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1810&quot; data-start=&quot;1721&quot;&gt;서비스가 &lt;b&gt;분산 환경&lt;/b&gt;으로 구성되어 있어, 하나의 인스턴스만 스케줄러가 실행되도록 &lt;b&gt;@SchedulerLock&lt;/b&gt;을 활용하여 &lt;b&gt;분산 락&lt;/b&gt;을 걸었습니다.&lt;/li&gt;
&lt;li data-end=&quot;1810&quot; data-start=&quot;1721&quot;&gt;그리고 아직 처리 중인 스레드가 10초를 넘기게 되면, 다른 스레드에서 아직 처리가 덜 끝난 데이터를 조회하여 &lt;b&gt;중복이 일어날 수 있습니다. &lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1810&quot; data-start=&quot;1721&quot;&gt;그래서 &lt;b&gt;fixedDelay&lt;/b&gt; 값을 설정하여 스레드 작업이 끝나고 10초 딜레이 한 다음 다른 스레드에서 작업을 진행하도록 하여 중복을 방지하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;재시도 최대 횟수 제어&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1944&quot; data-start=&quot;1891&quot;&gt;요청 실패가 &lt;b&gt;30회를 초과&lt;/b&gt;하면 Slack 알림을 통해 개발자에게 자동으로 전달됩니다.&lt;/li&gt;
&lt;li data-end=&quot;1991&quot; data-start=&quot;1945&quot;&gt;이로 인해 &lt;b&gt;장기 미처리 요청을 자동 감지&lt;/b&gt;하고 대응할 수 있도록 했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>∙Java &amp;amp; Spring</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/76</guid>
      <comments>https://coor.tistory.com/76#entry76comment</comments>
      <pubDate>Thu, 29 May 2025 21:35:26 +0900</pubDate>
    </item>
    <item>
      <title>비동기 메시징 유실 없는 Queue 시스템 구축</title>
      <link>https://coor.tistory.com/75</link>
      <description>&lt;div id=&quot;toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이번 글에서는 동기 방식 통신의 한계를 해결하기 위해 메시지 큐를 도입 + 메세지 유실이 없는 이야기를 소개하겠습니다 &lt;span style=&quot;color: #333e4c; text-align: start;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1. 문제 배경&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ys7AG/btsNKv59GIe/Z5aN3yK2biW8rbmTGRmEVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ys7AG/btsNKv59GIe/Z5aN3yK2biW8rbmTGRmEVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ys7AG/btsNKv59GIe/Z5aN3yK2biW8rbmTGRmEVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fys7AG%2FbtsNKv59GIe%2FZ5aN3yK2biW8rbmTGRmEVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;704&quot; height=&quot;419&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 구조상 통계 서버와 API 서버가 분리되어 있었고, 처음에는 이 두 서버 간의 통신을 동기 방식의 API 호출로 처리하고 있었습니다. 그러나 이 방식은 여러 가지 문제를 야기했습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;가장 큰 문제는 &lt;b&gt;한쪽 서버에 장애나 지연이 발생하면 다른 쪽 서버도 그 영향을 직접적으로 받는다&lt;/b&gt;는 점이었습니다. &lt;br /&gt;예를 들어, 통계 서버에 문제가 생기면 API 서버의 요청도 지연되거나 실패하게 되어 시스템 전체의 안정성이 떨어질 수밖에 없었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한, &lt;b&gt;속도 측면에서도 비효율적&lt;/b&gt;이었습니다. &lt;br /&gt;API 서버가 통계 처리 결과를 기다리는 동안 스레드가 블로킹되고, 이는 곧 리소스 낭비로 이어졌습니다. &lt;br /&gt;서버 간 강한 의존성으로 인해 유지보수와 확장성에도 제약이 따랐습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;이러한 문제를 해결하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;서버 간 직접 통신 대신 중간에 메시지 큐를 두고, 비동기 방식으로 전환&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;하게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2. 비동기 + 큐 도입  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt; 어떤 큐 사용하지?&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 큐를 도입하기 전, &lt;b&gt;어떤 방식의 큐를 사용할 것인지 고민이 필요&lt;/b&gt;하였습니다.&lt;br /&gt;크게 &lt;b&gt;RabbitMQ, Kafka, AWS SQS&lt;/b&gt; 3가지 방식의 큐가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;RabbitMQ는 큐 서버를 별도로 구성하고 운영해야 하므로 &lt;b&gt;인프라 관리 부담이 늘어나는 단점&lt;/b&gt;이 있었고,&lt;br /&gt;Kafka는 러닝 커브가 높고 &lt;b&gt;초당 수천 TPS 이상의 대규모 트래픽 처리에 적합한 시스템&lt;/b&gt;이기 때문에 현재 규모의 트래픽에서는 &lt;b&gt;과한 선택&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;반면, SQS는 AWS에서 제공하는 &lt;b&gt;완전 관리형 서비스&lt;/b&gt;로 별도 인프라 운영 없이 아마존 웹에서&lt;b&gt;&amp;nbsp;쉽게 큐를 생성하고 관리할 수 있는 장점&lt;/b&gt;이 있었고, 기존 인프라가 AWS에 구성되어 있어 &lt;b&gt;연동도 자연스러웠습니다&lt;/b&gt;. 다만, 메시지 유실을 방지하려면 DLQ 구성이나 수신 측의 예외 처리, 재시도 로직을 직접 보완해야 한다는 점은 고려해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이런 이유로 최종적으로&amp;nbsp;&lt;b&gt;AWS SQS를 선택&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;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;b&gt;AWS SQS 도입&amp;nbsp;&lt;/b&gt;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/32kPI/btsNKfJb5zP/KeVE8kxNFH780QkJBBxZS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/32kPI/btsNKfJb5zP/KeVE8kxNFH780QkJBBxZS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/32kPI/btsNKfJb5zP/KeVE8kxNFH780QkJBBxZS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F32kPI%2FbtsNKfJb5zP%2FKeVE8kxNFH780QkJBBxZS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;493&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;API 서버와 통계 서버 간에 비동기 방식으로 진행하기 위해 각각 &lt;b&gt;SQS 큐&lt;/b&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; data-start=&quot;719&quot; data-end=&quot;839&quot;&gt;
&lt;li data-end=&quot;780&quot; data-start=&quot;754&quot;&gt;요청 처리 속도 개선&lt;/li&gt;
&lt;li data-end=&quot;780&quot; data-start=&quot;754&quot;&gt;서버 간 강한 결합도 감소&lt;/li&gt;
&lt;li data-end=&quot;780&quot; data-start=&quot;754&quot;&gt;시스템 유연성 향상&lt;/li&gt;
&lt;li data-start=&quot;719&quot; data-end=&quot;753&quot;&gt;한쪽 서버의 장애가 다른 서버에 영향을 주는 문제 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;841&quot; data-end=&quot;907&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;841&quot; data-end=&quot;907&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;결과적으로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;각 서버는 서로의 상태를 몰라도 독립적으로 동작할 수 있게 되었고, 시스템은 보다 유연하고 안정적으로 개선되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;841&quot; data-end=&quot;907&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;841&quot; data-end=&quot;907&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-1&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;3. 메세지 유실 방지  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer, Consumer, AWS SQS 간에 메시지 유실이 발생할 수 있는 &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;Producer&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; SQS 보낼 때 네트워크 오류 및 타임아웃 발생하는 경우&lt;/li&gt;
&lt;li&gt;SQS 권한 또는 IAM 정책 오류&lt;/li&gt;
&lt;li&gt;SQS 메세지 수신했지만 Consumer 처리 실패&lt;/li&gt;
&lt;li&gt;Consumer 서버 재기동 시 재기동 사이에 SQS 에 메시지가 적재될 경우&lt;/li&gt;
&lt;li&gt;개발자의 오타로 잘못된 ARN 으로 메세지를 보내는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;메세지 재시도 전략&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doC6VN/btsNLnsYuOP/UDKADDHIDBVk9fycp1XfGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doC6VN/btsNLnsYuOP/UDKADDHIDBVk9fycp1XfGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doC6VN/btsNLnsYuOP/UDKADDHIDBVk9fycp1XfGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoC6VN%2FbtsNLnsYuOP%2FUDKADDHIDBVk9fycp1XfGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;380&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;SQS 설정에서 &lt;b&gt;&quot;배달 못한 편지 대기열&quot;&lt;/b&gt; 부분에 최대 수신 수를 설정할 수 있습니다.&lt;br /&gt;1로 설정하면 한 번 수신하고 실패하면 끝입니다.&lt;br /&gt;3 이상으로 설정하여 SQS &amp;rarr; Consumer 에서 메세지가 실패하더라도 재시도를 할 수 있도록 합니다.&lt;/p&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 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;DLQ 도입&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;653&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvKTkb/btsNKz1LyaT/kbJm8fXJHR79betyCh9Yfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvKTkb/btsNKz1LyaT/kbJm8fXJHR79betyCh9Yfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvKTkb/btsNKz1LyaT/kbJm8fXJHR79betyCh9Yfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvKTkb%2FbtsNKz1LyaT%2FkbJm8fXJHR79betyCh9Yfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;653&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;653&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은 DLQ(Dead Letter Queue) 도입하는 것입니다. &lt;br /&gt;DLQ는 소프트웨어 시스템에서 오류로 인해 처리할 수 없는 메시지를 임시로 저장하는 특수한 유형의 메시지 대기열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;SQS에서 메시지를 수신한 이후&lt;b&gt; Consumer 측의 처리 실패, 네트워크 오류, 타임아웃&lt;/b&gt; 등으로 인해 처리되지 못한 메시지는 &lt;b&gt;DLQ &lt;/b&gt;로 이동합니다. &lt;b&gt;DLQ에는 Lambda가 연결&lt;/b&gt;되어 있어, 메시지가 들어오면 트리거되어 &lt;b&gt;리드라이브 정책(재처리)&lt;/b&gt;에 따라 원래 큐로 다시 전송을 시도합니다. 이 과정에서 최대 재시도 횟수를 초과하면, 해당 메시지는 &lt;b&gt;2차 DLQ&lt;/b&gt;로 이동됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2차 DLQ&lt;/b&gt;에 메시지가 쌓이면 Lambda를 통해 &lt;b&gt;Slack 알림&lt;/b&gt;을 전송하여 운영자가 문제 상황을 즉시 인지할 수 있도록 했습니다.&lt;br /&gt;운영자는 2차 DLQ에 쌓인 메시지를 바탕으로 &lt;b&gt;문제 원인을 분석하고 수동 조치&lt;/b&gt;를 수행함으로써 메시지 유실 가능성을 최소화했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;236&quot; data-start=&quot;66&quot; data-ke-size=&quot;size16&quot;&gt;하지만 DLQ는 &lt;b&gt;SQS로 메시지가 정상적으로 도달한 이후의 실패 상황&lt;/b&gt;에 대응하는 구조이기 때문에, &lt;b&gt;Producer &amp;rarr; SQS로 메시지를 전송하는 단계에서 발생하는 네트워크 오류나 예외 상황&lt;/b&gt;으로 인해 메시지가 &lt;b&gt;아예 SQS에 도달하지 못하고 유실되는 경우에는 대응이 불가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;236&quot; data-start=&quot;66&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;300&quot; data-start=&quot;238&quot; data-ke-size=&quot;size16&quot;&gt;이러한 한계로 인해, &lt;b&gt;Producer 측의 메시지 전송 실패에 대한 보완이 추가로 필요한 상황&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;SNS + Transactional Outbox Pattern 도입&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;b&gt; &lt;/b&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1613&quot; data-origin-height=&quot;1043&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nU1sH/btsNMzlImGz/RXURPPPuPiDqDwPEFLMAd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nU1sH/btsNMzlImGz/RXURPPPuPiDqDwPEFLMAd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nU1sH/btsNMzlImGz/RXURPPPuPiDqDwPEFLMAd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnU1sH%2FbtsNMzlImGz%2FRXURPPPuPiDqDwPEFLMAd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1613&quot; height=&quot;1043&quot; data-origin-width=&quot;1613&quot; data-origin-height=&quot;1043&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-end=&quot;249&quot; data-start=&quot;94&quot; data-ke-size=&quot;size16&quot;&gt;Producer 측에서 메시지 전송 중 유실되는 문제를 해결하기 위해 &lt;b&gt;Transactional Outbox 패턴&lt;/b&gt;을 도입하였습니다.&lt;br /&gt;Producer는 메시지를 먼저 Outbox 테이블에 저장한 뒤, 이후에 SQS로 전송하는 &lt;b&gt;안정적인 전송 플로우&lt;/b&gt;를 구성하였습니다.&lt;/p&gt;
&lt;p data-end=&quot;249&quot; data-start=&quot;94&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 구조를 통해 SQS로 메시지를 전송하기 전까지 데이터를 안전하게 보관할 수 있고,&lt;br /&gt;&lt;b&gt;전송이 성공한 경우에는 해당 메시지를 DB에서 삭제&lt;/b&gt;하여 &lt;b&gt;중복 전송을 방지&lt;/b&gt;할 수 있도록 했습니다.&lt;/p&gt;
&lt;p data-end=&quot;249&quot; data-start=&quot;94&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;삭제 작업의 신뢰성을 높이기 위해 두 가지 방식의 이중 안전 장치를 적용했습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;574&quot; data-start=&quot;411&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;455&quot; data-start=&quot;411&quot;&gt;SQS 전송 성공 응답을 받은 경우, 해당 메시지를 즉시 DB에서 삭제&lt;/li&gt;
&lt;li data-end=&quot;574&quot; data-start=&quot;456&quot;&gt;동시에, &lt;b&gt;삭제 전용 SQS 큐&lt;/b&gt;를 별도로 구성하여 메시지 전송 시 삭제 요청 메시지를 함께 발행하고,&lt;br /&gt;Producer가 이 큐의 리스너 역할을 하여 해당 메시지를 수신한 후 DB 레코드를 삭제&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;619&quot; data-start=&quot;576&quot; data-ke-size=&quot;size16&quot;&gt;이와 같은 설계를 통해 삭제 실패나 메시지 중복 전송 가능성을 최소화했습니다.&lt;/p&gt;
&lt;p data-end=&quot;619&quot; data-start=&quot;576&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;br /&gt;스케쥴러&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;716&quot; data-start=&quot;626&quot; data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;네트워크 오류나 일시적인 장애로 인해 SQS 전송이 실패하는 경우&lt;/b&gt;, 메시지는 SQS에 도달하지 못하고 Outbox 테이블에 그대로 남게 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;716&quot; data-start=&quot;626&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이러한 상황을 대비하여, &lt;b&gt;주기적으로 Outbox 테이블을 조회하는 스케줄러&lt;/b&gt;를 별도로 운영합니다. 해당 스케줄러는 전송되지 않은 메시지를 확인하고 SQS로 재전송함으로써, 일시적인 오류가 발생하더라도 &lt;b&gt;메시지 유실 없이 안정적으로 복구&lt;/b&gt;될 수 있도록 보완했습니다.&lt;/p&gt;
&lt;p data-end=&quot;716&quot; data-start=&quot;626&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;716&quot; data-start=&quot;626&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SNS 도입 이유&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;960&quot; data-start=&quot;881&quot; data-ke-size=&quot;size16&quot;&gt;또한, &lt;b&gt;Producer가 두 개의 SQS로 각각 메시지를 전송하는 구조&lt;/b&gt;는 확장성 저하 및 관리 복잡도 증가라는 문제를 유발했습니다.&lt;/p&gt;
&lt;p data-end=&quot;960&quot; data-start=&quot;881&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이를 개선하기 위해 &lt;b&gt;AWS SNS(Simple Notification Service)&lt;/b&gt; 를 도입하여 구조를 단순화했습니다.&lt;br /&gt;SNS 주제(Topic)에 두 개의 SQS를 구독시키고, Producer는 오직 SNS에만 메시지를 발행하도록 하여,&lt;br /&gt;&lt;b&gt;확장성과 관리 편의성을 동시에 확보&lt;/b&gt;할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;4. 결론&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로, &lt;b&gt;재시도 전략 + DLQ + SNS + Transactional Outbox Pattern &lt;/b&gt;를 결합한 메시지 처리 구조를 통해, &lt;br /&gt;메시지 유실을 방지하고, 장애에 강하며 운영이 용이한 비동기 아키텍처를 구축할 수 있었습니다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>∙Infra</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/75</guid>
      <comments>https://coor.tistory.com/75#entry75comment</comments>
      <pubDate>Mon, 5 May 2025 21:13:22 +0900</pubDate>
    </item>
    <item>
      <title>라이브러리 등록하기 - Maven</title>
      <link>https://coor.tistory.com/74</link>
      <description>&lt;div id=&quot;toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Maven&amp;nbsp;중앙&amp;nbsp;저장소에 라이브러리 등록부터 배포까지 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1. Maven 저장소 회원가입&amp;nbsp;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저, Maven 저장소( &lt;a href=&quot;https://central.sonatype.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://central.sonatype.com/&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;)에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;회원가입해야 합니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;회원가입이 완료되면 Publish 클릭하여 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Namespace를&amp;nbsp;자동으로&amp;nbsp;등록해&amp;nbsp;주는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUT4oT/btsMPyiwJ6P/CeV1XiAwfl0HO6qkoaEwAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUT4oT/btsMPyiwJ6P/CeV1XiAwfl0HO6qkoaEwAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUT4oT/btsMPyiwJ6P/CeV1XiAwfl0HO6qkoaEwAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUT4oT%2FbtsMPyiwJ6P%2FCeV1XiAwfl0HO6qkoaEwAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;686&quot; height=&quot;83&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;144&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;964&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu5kBI/btsMQzAJxaW/TQTVRrgxbhqLSbbIpxA1hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu5kBI/btsMQzAJxaW/TQTVRrgxbhqLSbbIpxA1hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu5kBI/btsMQzAJxaW/TQTVRrgxbhqLSbbIpxA1hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu5kBI%2FbtsMQzAJxaW%2FTQTVRrgxbhqLSbbIpxA1hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;361&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-1&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2. GPG&amp;nbsp;키&amp;nbsp;생성&amp;nbsp;및&amp;nbsp;다운로드  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Maven 중앙 저장소에 배포하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;GPG 키&lt;/b&gt;를 생성하여 라이브러리에 서명해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성하는 이유는 라이브러리의 무결성과 신뢰성을 보장하기 위해 키를 생성해서 서명하기 위함입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt; GPG 다운로드 및 설치&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;설치하는 사이트 &lt;a href=&quot;https://gnupg.org/download/index.html#sec-1-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gnupg.org/download/index.html#sec-1-2&lt;/a&gt; 클릭하여 자신의 운영체제에 맞는 걸로 설치하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;저는 mac 사용하고 있어서 밑에 사진들은 mac 환경 기준입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;GPG 키 생성&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qLpzG/btsMP8X7aAs/5NtdNHVtXhkTKjjKiVFw8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qLpzG/btsMP8X7aAs/5NtdNHVtXhkTKjjKiVFw8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qLpzG/btsMP8X7aAs/5NtdNHVtXhkTKjjKiVFw8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqLpzG%2FbtsMP8X7aAs%2F5NtdNHVtXhkTKjjKiVFw8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;331&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;설치한 GPG 에서 새로운 키를 생성합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8AwuO/btsMPk6bscO/qsz4sVpxPrL57p4n0F3n2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8AwuO/btsMPk6bscO/qsz4sVpxPrL57p4n0F3n2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8AwuO/btsMPk6bscO/qsz4sVpxPrL57p4n0F3n2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8AwuO%2FbtsMPk6bscO%2Fqsz4sVpxPrL57p4n0F3n2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;370&quot; height=&quot;392&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 입력한 패스워드를 사용하기 위해 Use Simple Password 클릭합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;1238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RsRGH/btsMPvfk4N2/7JGR54Hh5LDpFIttrhiLh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RsRGH/btsMPvfk4N2/7JGR54Hh5LDpFIttrhiLh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RsRGH/btsMPvfk4N2/7JGR54Hh5LDpFIttrhiLh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRsRGH%2FbtsMPvfk4N2%2F7JGR54Hh5LDpFIttrhiLh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;330&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;1238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Upload Public Key를 눌러줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Public Key 키를 업로드하면, 자동으로 &lt;a href=&quot;https://keys.openpgp.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;keys.openpgp.org&lt;/a&gt;&amp;nbsp;사이트에 업로드가 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;보통 공캐 키 서버는 3개가 있습니다. 그 중에서 2번째 서버로 업로드가 되었네요.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;a href=&quot;http://keyserver.ubuntu.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;keyserver.ubuntu.com&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;a href=&quot;http://keys.openpgp.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;keys.openpgp.org&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;a href=&quot;http://pgp.mit.edu/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pgp.mit.edu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvyLTm/btsMXwyLb3p/kS4aiIFmwGM7LjRaenpQC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvyLTm/btsMXwyLb3p/kS4aiIFmwGM7LjRaenpQC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvyLTm/btsMXwyLb3p/kS4aiIFmwGM7LjRaenpQC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvyLTm%2FbtsMXwyLb3p%2FkS4aiIFmwGM7LjRaenpQC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1426&quot; height=&quot;662&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;메일로 자동으로 키가 업로드 됐다고 알려주네요 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt; 지금까지 gpg 계정과 Public Key 가 생성되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이제 Secrey 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt; Secrey Key&lt;/span&gt;&amp;nbsp;생성&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1743067340701&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;gpg --export-secret-keys 5D5A9392 &amp;gt; /Users/jinseong/Downloads/gpg/gpg-key.asc&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일로 온 OpenPGP 키에서 끝 8자리만 추출한 다음, 비밀 키를 추출합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&quot;/Users/jinseong/Downloads/gpg/gpg-key.asc&quot; 이 값은 자신의 컴퓨터에 저장하려는 경로입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;저는 gpg 폴더에 gpg-key.asc 파일명으로 저장해두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이렇게 &lt;b&gt;GPG 계정과 Public Key, Secret Key&lt;/b&gt; 생성이 완료되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;3. 프로젝트 Gradle 설정 &lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;라이브러리를 배포하려면 build.gradle 또는 pom.xml 파일에 설정을 추가해야 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;저는 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;gradle 사용하고 있어서 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;build.gradle 수정해보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;플러그인 추가&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1743061570983&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id &quot;com.vanniktech.maven.publish&quot; version &quot;0.28.0&quot; // 대체 플러그인
    id 'signing' // GPG 서명을 위한 플러그인 추가
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리를 Maven Central에 배포하기 위해 com.vanniktech.maven.publish 플러그인을 사용하며, 배포 시 필요한 GPG 서명을 위해 signing 플러그인을 추가합니다.&lt;/p&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 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; Maven Central 배포 설정&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1743062044769&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.vanniktech.maven.publish.SonatypeHost
 
mavenPublishing {
  publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
 
  signAllPublications()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven Central에 라이브러리를 배포하도록 설정하는 코드입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;206&quot; data-start=&quot;43&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;150&quot; data-start=&quot;43&quot;&gt;publishToMavenCentral(..): Sonatype Central을 통해 Maven Central에 업로드하도록 지정&lt;/li&gt;
&lt;li data-end=&quot;206&quot; data-start=&quot;151&quot;&gt;signAllPublications(): 배포되는 모든 아티팩트에 GPG 서명을 추가&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 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 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; POM&lt;/span&gt;&amp;nbsp;설정&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1743062177848&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mavenPublishing {
    publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
    signAllPublications()
    
    coordinates(&quot;io.github.coorr&quot;, &quot;excel-common&quot;, &quot;0.0.1&quot;)

    // POM 설정
    pom {
        name = 'excel-common'
        description = 'SpringBoot common maven validation enums'
        url = '&amp;lt;https://github.com/coorr/excel-common&amp;gt;'

        // 라이선스 정보
        licenses {
            license {
                name = 'The Apache License, Version 2.0'
                url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
            }
        }

        // 개발자 정보
        developers {
            developer {
                id = 'coorr'
                name = 'JinSeong Kim'
                email = 'wlsdiqkdrk@gmail.com'
            }
        }

        scm {
            connection = 'scm:git:github.com/coorr/excel-common.git'
            developerConnection = 'scm:git:ssh://github.com:coorr/excel-common.git'
            url = '&amp;lt;https://github.com/coorr/excel-common/tree/master&amp;gt;'
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 라이브러리를 Maven Central에 배포할 때 필요한 메타데이터(POM 파일)를 설정하는 부분입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;471&quot; data-start=&quot;68&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;172&quot; data-start=&quot;68&quot;&gt;&lt;b&gt;coordinates(&quot;io.github.coorr&quot;, &quot;excel-common&quot;, &quot;0.0.1&quot;)&lt;/b&gt;: 배포될 라이브러리의 그룹 ID, 아티팩트 ID, 버전을 지정&lt;/li&gt;
&lt;li data-end=&quot;471&quot; data-start=&quot;173&quot;&gt;&lt;b&gt;pom &lt;/b&gt;: 라이브러리의 메타데이터를 정의합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;471&quot; data-start=&quot;214&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;282&quot; data-start=&quot;214&quot;&gt;&lt;b&gt;name, description, url&lt;/b&gt;: 라이브러리 이름, 설명, 프로젝트 URL을 설정&lt;/li&gt;
&lt;li data-end=&quot;340&quot; data-start=&quot;285&quot;&gt;&lt;b&gt;licenses :&lt;/b&gt; 라이브러리의 라이선스를 정의 (Apache 2.0)&lt;/li&gt;
&lt;li data-end=&quot;401&quot; data-start=&quot;343&quot;&gt;&lt;b&gt;developers &lt;/b&gt;: 라이브러리 개발자의 정보를 추가 (이름, 이메일 등)&lt;/li&gt;
&lt;li data-end=&quot;471&quot; data-start=&quot;404&quot;&gt;&lt;b&gt;scm &lt;/b&gt;: 소스 코드 관리 정보를 설정 (GitHub 저장소 URL 및 연결 방식)&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 라이브러리&amp;nbsp;배포 설정&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;라이브러리 배포를 할려면 Gradle 빌드 설정 파일에서 라이브러리 배포 및 GPG 서명을 추가해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;gradle.properties&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743062336724&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;mavenCentralUsername= ..
mavenCentralPassword= ..

signing.keyId= 5D5A9392
signing.password= ..
signing.secretKeyRingFile= /Users/jinseong/Downloads/gpg/gpg-key.asc&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;mavenCentralUsername, mavenCentralPassword 값은 Maven 저장소의 계정이 아닌 따로 임시 토큰을 생성해서 넣어야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2478&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnetjx/btsMZyBASr9/5vhkz5TuLdIcTkGCqzyvQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnetjx/btsMZyBASr9/5vhkz5TuLdIcTkGCqzyvQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnetjx/btsMZyBASr9/5vhkz5TuLdIcTkGCqzyvQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcnetjx%2FbtsMZyBASr9%2F5vhkz5TuLdIcTkGCqzyvQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2478&quot; height=&quot;944&quot; data-origin-width=&quot;2478&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ok 를 누르면 나오는 mavenCentralUsername 과 mavenCentralPassword 를 각각 작성하시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그 다음 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;signing.keyId, password,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;secretKeyRingFile 부분은 GPG 서명하는 부분입니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;740&quot; data-start=&quot;693&quot;&gt;&lt;b&gt;signing.keyId=5D5A9392&lt;/b&gt;:  아까 위에서 비밀키를 추출할 때 쓰였던 Key ID&lt;/li&gt;
&lt;li data-end=&quot;798&quot; data-start=&quot;741&quot;&gt;&lt;b&gt;signing.password=...&lt;/b&gt;: GPG 키를  생성할 때 계정에 대한 Password&lt;/li&gt;
&lt;li data-end=&quot;910&quot; data-start=&quot;799&quot;&gt;&lt;b&gt;signing.secretKeyRingFile= &lt;/b&gt;아까 위에서 Key ID 통해서 비밀 키를 저장했던 경로&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;참고로 Github 에 올리실 때 이 보안 정보는 올리시면 안됩니다 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;.gitignore 파일에 gradle.properties 추가해서 다른 사용자가 사용하지 못하도록 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; Maven 배포하기&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;1134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JHVn6/btsMZE2TMQv/xKp7eKaC8ThoCmj5AEYiSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JHVn6/btsMZE2TMQv/xKp7eKaC8ThoCmj5AEYiSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JHVn6/btsMZE2TMQv/xKp7eKaC8ThoCmj5AEYiSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJHVn6%2FbtsMZE2TMQv%2FxKp7eKaC8ThoCmj5AEYiSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;588&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;1134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;인텔리제이에서 publishAllPublicationsToMavenCentralRepository 클릭하여 배포합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1743069059008&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./gradlew publishAllPublicationsToMavenCentralRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 인텔리제이가 아닌 경우 명령어로 배포해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1743123919056&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Execution failed for task ':compileJava'.
&amp;gt; Could not resolve all files for configuration ':compileClasspath'.
   &amp;gt; Could not find org.apache.poi:poi:.
     Required by:
         root project :
   &amp;gt; Could not find org.apache.poi:poi-ooxml:.
     Required by:
         root project :

Possible solution:
 - Declare repository providing the artifact, see the documentation at https://docs.gradle.org/current/userguide/declaring_repositories.html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포하니깐 오류가 발생하였는데, 이 오류를 확인해본 결과 dependencies 에서 라이브러리의 버전을 기입하지 않아서 실패했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;특정 라이브러리 버전이 존재하지 않으면 Could not resolve 오류가 발생합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1743124026804&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'

	// Apache POI
	implementation 'org.apache.poi:poi:5.2.3'  // 버전 기입
	implementation 'org.apache.poi:poi-ooxml:5.2.3'  // 버전 기입
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 기입을 하고 다시 재배포하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2444&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nwxBk/btsMZOYHDZX/Z2iQST3HexSBfgGkEukiTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nwxBk/btsMZOYHDZX/Z2iQST3HexSBfgGkEukiTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nwxBk/btsMZOYHDZX/Z2iQST3HexSBfgGkEukiTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnwxBk%2FbtsMZOYHDZX%2FZ2iQST3HexSBfgGkEukiTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;867&quot; height=&quot;232&quot; data-origin-width=&quot;2444&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 배포되면 이런 화면으로 나오게 됩니다. 초록색을 보면 기분이 좋네요ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;이제 실제로 Maven 배포가 되었는지 확인해보겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qmF3K/btsMY504wTB/D90KM9hFKcN7Kxtkyo7mcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qmF3K/btsMY504wTB/D90KM9hFKcN7Kxtkyo7mcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qmF3K/btsMY504wTB/D90KM9hFKcN7Kxtkyo7mcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqmF3K%2FbtsMY504wTB%2FD90KM9hFKcN7Kxtkyo7mcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2362&quot; height=&quot;556&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; Maven central 사이트의 Deployments를 확인해 보면 Drop 과 publish 버튼이 활성화가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;잘못 배포한 경우 Drop을 눌러 지워주시면 되고 Publish 버튼을 눌러 라이브러리 발행을 계속 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;대략 1분 정도 지나니깐 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;초록색으로&lt;span&gt; Published 되었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Maven 배포 확인&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포&amp;nbsp;후&amp;nbsp;라이브러리가&amp;nbsp;정상적으로&amp;nbsp;등록되었는지&amp;nbsp;&lt;a href=&quot;https://central.sonatype.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Maven&amp;nbsp;Repository&lt;/a&gt;에서 확인해보겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2482&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bju7rJ/btsMYu1BCBp/xsqNWEn2czbBdeK3r95B20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bju7rJ/btsMYu1BCBp/xsqNWEn2czbBdeK3r95B20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bju7rJ/btsMYu1BCBp/xsqNWEn2czbBdeK3r95B20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbju7rJ%2FbtsMYu1BCBp%2FxsqNWEn2czbBdeK3r95B20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2482&quot; height=&quot;682&quot; data-origin-width=&quot;2482&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;사이트의 Search 검색을 통해 등록한 라이브러리 정보를 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1922&quot; data-origin-height=&quot;1206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ppRcH/btsMZPcyoK5/9cCIzdb0n1XRIM6PKE5emK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ppRcH/btsMZPcyoK5/9cCIzdb0n1XRIM6PKE5emK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ppRcH/btsMZPcyoK5/9cCIzdb0n1XRIM6PKE5emK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FppRcH%2FbtsMZPcyoK5%2F9cCIzdb0n1XRIM6PKE5emK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1922&quot; height=&quot;1206&quot; data-origin-width=&quot;1922&quot; data-origin-height=&quot;1206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이제 사용할 프로젝트에서 의존성을 추가해준다면 여러 프로젝트에서 사용 가능한 라이브러리 배포가 완료됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>∙Java &amp;amp; Spring</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/74</guid>
      <comments>https://coor.tistory.com/74#entry74comment</comments>
      <pubDate>Fri, 28 Mar 2025 11:44:35 +0900</pubDate>
    </item>
    <item>
      <title>ChatGPT API 요청 제한, Feign Retry 해결하기</title>
      <link>https://coor.tistory.com/72</link>
      <description>&lt;div id=&quot;toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1. 문제 배경&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장문 텍스트 요약 기능을 개발하면서 AI 모듈을 활용하게 되었고, 여러 후보 중&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;OpenAI ChatGPT&lt;/b&gt;를 선택했다. 하지만 ChatGPT API에는 &lt;b&gt;요청 제한(Rate Limit)&lt;/b&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;&lt;br /&gt;API에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RPM(분당 요청 수), RPD(일일 요청 수), TPM(분당 토큰 수)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 제한이 존재하며, 이 한도를 초과하면 요청이 차단된다. 따라서 요청 제한을 효과적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;관리하는 방법이 필요했고&lt;/b&gt;, 이번 글에서는 이 문제를 어떻게 해결했는지 정리해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;티어별로 API 요청 제한을 보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델 : gpt-3.5-turbo&lt;/li&gt;
&lt;/ul&gt;
&lt;table id=&quot;19df208f-1da9-805a-a9f6-d1ebed79f207&quot; style=&quot;border-collapse: collapse; width: 100%; height: 133px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr id=&quot;19df208f-1da9-80c9-9c35-fff4ec213843&quot; style=&quot;height: 19px;&quot;&gt;
&lt;td id=&quot;z|ki&quot; style=&quot;text-align: center; height: 19px;&quot;&gt;&lt;b&gt;Tier&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;uM|m&quot; style=&quot;text-align: center; height: 19px;&quot;&gt;&lt;b&gt;RPM (1분 동안 최대 요청 수)&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;aWFD&quot; style=&quot;text-align: center; height: 19px;&quot;&gt;&lt;b&gt;RPD (1일 동안 최대 요청 수)&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;R{tx&quot; style=&quot;text-align: center; height: 19px;&quot;&gt;&lt;b&gt;TPM (1분 동안 최대 토큰 수)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;19df208f-1da9-8063-9423-fbf0e71dcf24&quot; style=&quot;height: 19px;&quot;&gt;
&lt;td id=&quot;z|ki&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;무료&lt;/td&gt;
&lt;td id=&quot;uM|m&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;3&lt;/td&gt;
&lt;td id=&quot;aWFD&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;200&lt;/td&gt;
&lt;td id=&quot;R{tx&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;40,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;19df208f-1da9-80a6-afdb-c9deb0490ad3&quot; style=&quot;height: 19px;&quot;&gt;
&lt;td id=&quot;z|ki&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;1단계&lt;/td&gt;
&lt;td id=&quot;uM|m&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;3,500&lt;/td&gt;
&lt;td id=&quot;aWFD&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;10,000&lt;/td&gt;
&lt;td id=&quot;R{tx&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;200,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;19df208f-1da9-8082-8851-d3efb98a3ea4&quot; style=&quot;height: 19px;&quot;&gt;
&lt;td id=&quot;z|ki&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;2단계&lt;/td&gt;
&lt;td id=&quot;uM|m&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;3,500&lt;/td&gt;
&lt;td id=&quot;aWFD&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td id=&quot;R{tx&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;2,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;19df208f-1da9-806b-90d8-c650aaf478a1&quot; style=&quot;height: 19px;&quot;&gt;
&lt;td id=&quot;z|ki&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;3단계&lt;/td&gt;
&lt;td id=&quot;uM|m&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;3,500&lt;/td&gt;
&lt;td id=&quot;aWFD&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td id=&quot;R{tx&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;4,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;19df208f-1da9-8094-bab2-fc390032b4f6&quot; style=&quot;height: 19px;&quot;&gt;
&lt;td id=&quot;z|ki&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;4단계&lt;/td&gt;
&lt;td id=&quot;uM|m&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;10,000&lt;/td&gt;
&lt;td id=&quot;aWFD&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td id=&quot;R{tx&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;10,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;19df208f-1da9-8002-aa31-f8527895e819&quot; style=&quot;height: 19px;&quot;&gt;
&lt;td id=&quot;z|ki&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;5단계&lt;/td&gt;
&lt;td id=&quot;uM|m&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;10,000&lt;/td&gt;
&lt;td id=&quot;aWFD&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td id=&quot;R{tx&quot; style=&quot;height: 19px; text-align: center;&quot;&gt;50,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 티어별로 RPM, RPD, TPM 제한이 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;내용은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;a href=&quot;https://platform.openai.com/docs/guides/rate-limits#free-tier-rate-limits&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;&lt;/u&gt;를 참고하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-2&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2. API 요청 제한! 고민의 흔적들  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;방안 1.&lt;span&gt; 요금제&amp;nbsp;Tier&amp;nbsp;올리기&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만&amp;nbsp;근본적인&amp;nbsp;해결책이&amp;nbsp;아니기&amp;nbsp;때문에&amp;nbsp;지속적인&amp;nbsp;해결책이&amp;nbsp;필요했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;방안 2.&lt;span&gt;&lt;span&gt; 슬라이딩 윈도우 알고리즘&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;슬라이딩 윈도우는 고정된 시간(1분, 하루) 동안의 요청 개수를 정확히 계산하는 알고리즘을 사용해 요청을 제한하는 방법이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;사용하는 자료구조는 &lt;b&gt;Redis의 ZSET(Sorted Set)&lt;/b&gt; 사용하여, 특정 시간 동안의 요청 개수를 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Redis 키 설계&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;Key: request:{date - 현재 날짜}
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Score : 요청 시간(초 단위)&lt;/li&gt;
&lt;li&gt;Value: 토큰 수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;RPM(분당 요청 수): 현재 시간에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;60초 이전의 요청 개수를 계산하여 초과 여부 판단&lt;/li&gt;
&lt;li&gt;RPD(일일 요청 수): 현재 날짜 기준으로 요청 횟수를 카운트하여 초과 여부 판단&lt;/li&gt;
&lt;li&gt;TPM(분당 토큰 수) :
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰 수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;입력 토큰과&lt;span&gt;&amp;nbsp;&lt;/span&gt;출력 토큰으로 나뉨&lt;/li&gt;
&lt;li&gt;입력 토큰 수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;tiktoken&lt;span&gt;&amp;nbsp;&lt;/span&gt;라이브러리를 사용하여 미리 계산할 수 있지만, &lt;b&gt;출력 토큰 수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;API 응답을 받아야 확인 가능함&lt;/b&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;요청 제한을 확인하고 API 응답을 받고 출력 토큰 수를 저장해야 하는데, 이 과정에서 &lt;b&gt;동시성 문제&lt;/b&gt;가 발생함&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;동시성 문제를 해결하기 위해서, &lt;b&gt;다른 스레드를 블로킹&lt;/b&gt;하면 기다리는 시간이 오래 걸려서 &lt;b&gt;성능이 저하&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;이로 인해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;TPM 핸들링이 어렵고, 이 방법을 사용하기 어려움...&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;왜 출력 토큰 수까지 계산하는 것이냐!!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;출력 토큰 수를 미리 알 수 없기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;TPM 을 효과적으로 처리할 방법이 없어서,&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 방법은 사용이 어려웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt; 방안 3. &lt;/span&gt;&lt;span&gt;&lt;span&gt;재시도 전략&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 방안은 API 요청을 했는데 요청 제한되었을 때, 일정 시간 대기 후 재시도하는 방식이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;재시도 간격을 2배씩 증가시키는 지수 백오프(Exponential Backoff) 적용하여,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;만약 5번 재시도 한다면 2 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;/span&gt;4초 &amp;rarr; 8초 &amp;rarr; 16초 &amp;rarr; 32초 순으로 요청을 하는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그러면 언제 재시도를 해야할까?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;재시도&amp;nbsp;시점을&amp;nbsp;결정하기&amp;nbsp;위해&amp;nbsp;OpenAI&amp;nbsp;API&amp;nbsp;Errors&amp;nbsp;문서를&amp;nbsp;확인했다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1466&quot; data-origin-height=&quot;1338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OFdB4/btsMlAHUdti/t8Y7zKUDC3wCRVVwU9lBkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OFdB4/btsMlAHUdti/t8Y7zKUDC3wCRVVwU9lBkk/img.png&quot; data-alt=&quot;API Errors Status Code&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OFdB4/btsMlAHUdti/t8Y7zKUDC3wCRVVwU9lBkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOFdB4%2FbtsMlAHUdti%2Ft8Y7zKUDC3wCRVVwU9lBkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1466&quot; height=&quot;1338&quot; data-origin-width=&quot;1466&quot; data-origin-height=&quot;1338&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;API Errors Status Code&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;OpenAI에서 제공하는 상태 코드를 보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;401 &amp;rarr; 인증 문제 (잘못된 API 키 등)&lt;/li&gt;
&lt;li&gt;403 &amp;rarr; 지원되지 않는 국가 또는 지역&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 외에 재시도가 필요한 오류는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;429 &amp;rarr; 요청 할당량 초과 (Rate Limit Exceeded)&lt;/li&gt;
&lt;li&gt;500 &amp;rarr; 서버 오류 (Internal Server Error)&lt;/li&gt;
&lt;li&gt;503 &amp;rarr; 서버 과부하 (Service Unavailable)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;OpenAI&amp;nbsp;문서에서&amp;nbsp;429,&amp;nbsp;500,&amp;nbsp;503&amp;nbsp;오류&amp;nbsp;해결&amp;nbsp;방법(Solution)을&amp;nbsp;보면&amp;nbsp;&lt;b&gt;&quot;retry&quot;&lt;/b&gt;라는&amp;nbsp;단어가&amp;nbsp;자주&amp;nbsp;등장한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 오류들은 일시적인 문제일 가능성이 높아, 일정 시간을 두고 다시 요청하면 정상 처리될 가능성이 클 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;429, 500, 503 상태 코드가 발생하면 일정 시간 대기 후 재시도를 진행하는 것이 적절하다고 판단하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;기술 스택 : Java 17, Sprint boot 3,  Feign Client 3&lt;/blockquote&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;3. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;문제를 해결할 재시도 전략 도입!&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;API 요청 시 Feign Client 사용하고 있어서 이 라이브러리의 재시도 기능을 알아보았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Feign Client에는 기본적으로 재시도 기능이 있지만, IOException이 발생할 때만 동작한다는 제한이 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Read Timeout, Socket Timeout, Broken Pipe 같은 네트워크 관련 저수준 예외가 발생할 경우에만 IOException으로 감싸져 자동 재시도가 이루어진다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;IOException 뿐만 아니라 429, 500, 503 상태 코드에도 재시도가 필요하여 내부 로직을 어떻게 되어있는지 분석해보았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt; 내부 로직 분석&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 가장 먼저 확인한 부분은 &lt;b&gt;HTTP 요청하는 부분과 에러 핸들링&lt;/b&gt;을 어떻게 하는지 확인하는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;SynchronousMethodHandler.class&lt;/b&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;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CBCBQ/btsMkBAo2Do/tx2KPUTcVPTO8kGT9RSbkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CBCBQ/btsMkBAo2Do/tx2KPUTcVPTO8kGT9RSbkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CBCBQ/btsMkBAo2Do/tx2KPUTcVPTO8kGT9RSbkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCBCBQ%2FbtsMkBAo2Do%2Ftx2KPUTcVPTO8kGT9RSbkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1272&quot; height=&quot;998&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;invoke 함수를 통해서 HTTP 요청이 실제로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;RequestTemplate 객체는 HTTP 요청 정보를 세팅하는 역할이고 Options 객체는 Read, Connect 타임아웃 등 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;중요한 건 &lt;b&gt;Retryer 객체&lt;/b&gt;인데, 이 객체는 재시도 유무와 어떤 식으로 재시도를 할건지 정보가 담겨있는 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;만약 등록된 bean 없으면 재시도를 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그&amp;nbsp;다음&amp;nbsp;&lt;b&gt;excuteAndDecode&lt;/b&gt;&amp;nbsp;함수를&amp;nbsp;보면&amp;nbsp;HTTP&amp;nbsp;요청을&amp;nbsp;하고&amp;nbsp;응답을&amp;nbsp;Decode&amp;nbsp;하게&amp;nbsp;되는데&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uXYPs/btsMlW4VKWp/wuHCAXOznaxgWIATAHXxE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uXYPs/btsMlW4VKWp/wuHCAXOznaxgWIATAHXxE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uXYPs/btsMlW4VKWp/wuHCAXOznaxgWIATAHXxE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuXYPs%2FbtsMlW4VKWp%2FwuHCAXOznaxgWIATAHXxE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1036&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 요청은 client.execute 함수에서 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;만약 네트워크 관련 저수준 예외가 발생하면 catch (IOException) {..} 캐치하는 모습을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;throw errorExecuting 내부 코드를 보면&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQOGrf/btsMlOeA8Oj/cO96sbMMW5o6MCRDlBk7J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQOGrf/btsMlOeA8Oj/cO96sbMMW5o6MCRDlBk7J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQOGrf/btsMlOeA8Oj/cO96sbMMW5o6MCRDlBk7J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQOGrf%2FbtsMlOeA8Oj%2FcO96sbMMW5o6MCRDlBk7J0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1450&quot; height=&quot;354&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;함수 안에서 RetryableException 던진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;다시 처음 실행부 invoke(..) 함수로 돌아오면&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;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfmZFZ/btsMo1YuWFJ/huz44SNDCjHrqkikmRcrhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfmZFZ/btsMo1YuWFJ/huz44SNDCjHrqkikmRcrhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfmZFZ/btsMo1YuWFJ/huz44SNDCjHrqkikmRcrhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfmZFZ%2FbtsMo1YuWFJ%2Fhuz44SNDCjHrqkikmRcrhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1272&quot; height=&quot;998&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;RetrableException 예외를 캐치한 다음 retryer에게 이후 실행을 위임하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&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;IOException 발생하면 RetrableException 던지게 되고 RetrableException 캐치하여 재시도를 하는 것&lt;/li&gt;
&lt;li&gt;Retryer 객체 Bean 등록을 해야지만, 재시도 한다는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;retryer.continueOrPropagate() 함수에서 Retryer 객체가 bean 등록되지 않았을 때 함수 내용을 보면&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pljyh/btsMmr4qJ24/neRxW8YOjzQK1v9lrADMQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pljyh/btsMmr4qJ24/neRxW8YOjzQK1v9lrADMQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pljyh/btsMmr4qJ24/neRxW8YOjzQK1v9lrADMQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPljyh%2FbtsMmr4qJ24%2FneRxW8YOjzQK1v9lrADMQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1148&quot; height=&quot;390&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;아무것도 하지 않고 예외만 throw e 던진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 bean 등록되어 있을 때 함수 내용을 보면&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGnLfG/btsMmX9pLn0/AebNXP6JxTHDGNnmZfbkc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGnLfG/btsMmX9pLn0/AebNXP6JxTHDGNnmZfbkc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGnLfG/btsMmX9pLn0/AebNXP6JxTHDGNnmZfbkc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGnLfG%2FbtsMmX9pLn0%2FAebNXP6JxTHDGNnmZfbkc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1114&quot; height=&quot;960&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;지정한&amp;nbsp;최대&amp;nbsp;요청&amp;nbsp;횟수&amp;nbsp;안에서&amp;nbsp;interval&amp;nbsp;동안&amp;nbsp;대기하고&amp;nbsp;retry를&amp;nbsp;하게&amp;nbsp;되어&amp;nbsp;있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;interval&amp;nbsp;대기하는&amp;nbsp;계산식을&amp;nbsp;보면&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;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJdVjV/btsMlo1z9Mk/xJnLVhSx1pshkyWdoKDZq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJdVjV/btsMlo1z9Mk/xJnLVhSx1pshkyWdoKDZq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJdVjV/btsMlo1z9Mk/xJnLVhSx1pshkyWdoKDZq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJdVjV%2FbtsMlo1z9Mk%2FxJnLVhSx1pshkyWdoKDZq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;170&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;1.5배&amp;nbsp;증가하는&amp;nbsp;지수&amp;nbsp;백오프&amp;nbsp;방식으로&amp;nbsp;되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IOException&amp;nbsp;아닌&amp;nbsp;다른&amp;nbsp;에러는&amp;nbsp;내부적으로&amp;nbsp;어떻게&amp;nbsp;처리될까?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;다시&amp;nbsp;executeAndDecode&amp;nbsp;함수를&amp;nbsp;보게&amp;nbsp;되면&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvfsaj/btsMlVxSXCl/2excjVDVt1WJr5KuNWXgcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvfsaj/btsMlVxSXCl/2excjVDVt1WJr5KuNWXgcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvfsaj/btsMlVxSXCl/2excjVDVt1WJr5KuNWXgcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdvfsaj%2FbtsMlVxSXCl%2F2excjVDVt1WJr5KuNWXgcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1522&quot; height=&quot;1038&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;responseHandler.handleResponse(...) 함수가 실행되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;함수 내용을 보면&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DjGKQ/btsMmZsBuyW/xOCPT3klzpJa32ePtTrTqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DjGKQ/btsMmZsBuyW/xOCPT3klzpJa32ePtTrTqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DjGKQ/btsMmZsBuyW/xOCPT3klzpJa32ePtTrTqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDjGKQ%2FbtsMmZsBuyW%2FxOCPT3klzpJa32ePtTrTqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1550&quot; height=&quot;392&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;executionChain.next(...)를 호출하게 되는데, 이 때 함수형 인터페이스로 chain 되어 있어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;InvocationContext.proceed(...) 함수가 실행되게 된다. 이 함수의 내부를 봐보자.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dovstX/btsMlFPAWZG/cJFFHrtPoWv3onBp3Epz5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dovstX/btsMlFPAWZG/cJFFHrtPoWv3onBp3Epz5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dovstX/btsMlFPAWZG/cJFFHrtPoWv3onBp3Epz5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdovstX%2FbtsMlFPAWZG%2FcJFFHrtPoWv3onBp3Epz5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1304&quot; height=&quot;566&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;shouldDecodeResponseBody 변수는 상태코드가 2xx번대가 아니게 되면 decodeError를 호출하게 된다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUAFi0/btsMms9PJLc/W8kfRQOjm07BDEXdt9cgJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUAFi0/btsMms9PJLc/W8kfRQOjm07BDEXdt9cgJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUAFi0/btsMms9PJLc/W8kfRQOjm07BDEXdt9cgJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUAFi0%2FbtsMms9PJLc%2FW8kfRQOjm07BDEXdt9cgJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1228&quot; height=&quot;276&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;ErrorDecoder에서 decode를 호출하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;ErrorDecoder는 Customizing 하여 에러 핸들링을 할 수 있는 클래스이다. (&lt;a href=&quot;https://www.baeldung.com/feign-retrieve-original-message&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 문서&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 클래스를 overring 해서 에러 핸들링을 하게 되면, 내가 원하는 429, 500, 503 상태 코드가 되었을 때 RetrableException 던져서 재시도를 할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;즉, IOException 발생했을 때와 같은 플로우로 동작할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&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;Feign Client는 기본적으로 IOException 발생 시 RetryableException을 던져서 재시도를 함&lt;/li&gt;
&lt;li&gt;재시도를 하려면 Retryer 객체를 Bean으로 등록 필수&lt;/li&gt;
&lt;li&gt;HTTP 응답이 2xx(성공) 상태 코드가 아니면 ErrorDecoder가 호출됨&lt;/li&gt;
&lt;li&gt;재시도를 위해 ErrorDecoder를 Customizing 해서 RetryableException 던지도록 해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[ &lt;span&gt;&lt;span&gt;구현하기&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 OpenAI API 요청 실패 시 재시도할 수 있도록 ErrorDecoder를 Customizing 하였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;OpenAiErrorDecoder.class&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739785220949&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
public class OpenAiErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        log.error(&quot;[OpenAi] API 실패 에러 status : {}, reason : {}&quot;, response.status(), response.reason());

        // 요청 할달량 초과 &amp;amp;&amp;amp; 서버 에러 시 재시도
        if(response.status() == 429 || response.status() &amp;gt;= 500) {
            return new RetryableException(response.status(),
                    response.reason(),
                    response.request().httpMethod(),
                    (Long) null,
                    response.request());
        }
        throw new OpenAiApiException();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패 상태 코드가 429, 500 이상이면 RetryableException 예외를 던져서 재시도할 수 있도록 해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;나머지 실패 케이스는 재시도 하지 않고 예외 OpenAiApiException(..) 던져주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;그 다음 Retryer 객체를 Bean 등록한다.&lt;/p&gt;
&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;OpenAiFeignConfig.class&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739785210497&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OpenAiFeignConfig {
    @Bean
    public Retryer retryer() {
        /**
         * period = 2 (초기 대기 시간)
         * maxPeriod = 32 (최대 대기 시간)
         * maxAttempts = 5 (최대 재시도 횟수)
         */
        return new Retryer.Default(5, 32, 5);
    }
    
    @Bean
    public ErrorDecoder openaiErrorDecoder() {
        return new OpenAiErrorDecoder();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retryer 객체에는 최대 몇번 재시도, 최대 대기 시간, 초기 대기 시간을 설정할 수 있는 변수들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;API 성공률이 높아질 수 있도록 아래와 같이 설정하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최대 재시도 횟수 : 5번&lt;/li&gt;
&lt;li&gt;최대 대기 시간 : 32초&amp;nbsp;&lt;/li&gt;
&lt;li&gt;초기 대기 시간 : 2초&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이렇게 하면 첫번째 재시도 2초 &amp;rarr; 두번째 재시도 4초 &amp;rarr; 세번째 재시도 8초 &amp;rarr; 네번째 재시도 16초 &amp;rarr; 다섯번째 시도 32초로 일정 시간 대기한 후 요청을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 Retryer 객체에 지수 백오프 방식이 2배가 아닌 1.5배 증가로 되어있었다.&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;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJdVjV/btsMlo1z9Mk/xJnLVhSx1pshkyWdoKDZq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJdVjV/btsMlo1z9Mk/xJnLVhSx1pshkyWdoKDZq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJdVjV/btsMlo1z9Mk/xJnLVhSx1pshkyWdoKDZq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJdVjV%2FbtsMlo1z9Mk%2FxJnLVhSx1pshkyWdoKDZq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;170&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이거를 어떻게 할지 고민하다가, Retryer 인터페이스의 함수를 재정의해서 내 입맛대로 재시도할 수 있도록 구성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;OpenAiRetryer.class&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739856705090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * OpenAi API 예외 발생 시 재시도 하는 클래스
 */
@Builder
public class OpenAiRetryer implements Retryer {
    private final long period;
    private final long maxPeriod;
    private final int maxAttempts;
    int attempt;
    int multiplier;

    /**
     * 재시도 하기 전, 일정 시간 대기
     * @param e 재시도할 예외
     * @throws RetryableException 재시도 횟수를 초과한 경우 예외를 던진다.
     */
    @Override
    public void continueOrPropagate(RetryableException e) {
        // 최대 재시도 횟수 체크
        if (attempt++ &amp;gt;= maxAttempts) {
            throw e;
        }

        // 대기 시간 계산
        long interval = nextMaxInterval();
        try {
            // 쓰레드 대기 (초 단위)
            Thread.sleep(interval * 1000);
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
            throw e;
        }
    }

    /**
     * 대기 시간을 계산하여 반환
     * 지수 증가 방식(period * multiplier^(attempt - 1)) 사용
     * @return 다음 재시도까지의 대기 시간
     */
    long nextMaxInterval() {
        long interval = (long) (period * Math.pow(multiplier, attempt - 1));
        return Math.min(interval, maxPeriod);
    }

    @Override
    public Retryer clone() {
        return OpenAiRetryer.builder()
                .period(period)
                .maxPeriod(maxPeriod)
                .maxAttempts(maxAttempts)
                .multiplier(multiplier)
                .attempt(attempt)
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;continueOrPropagate(..)와 clone(..) 함수를 재정의해서, 일정시간 대기한 후 재시도를 할 수 있도록 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;지수 백오프 증가 방식은 multiplier 변수로 관리할 수 있도록 해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;만약 multiplier = 2 설정되면 2배 증가하는 방식으로 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그 다음 OpenAiRetryer 구현 클래스로 실행시킬 수 있도록 Bean 등록하는 부분을 수정해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1739857022027&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OpenAiFeignConfig {
    @Bean
    public Retryer retryer() {
        return OpenAiRetryer.builder()
                .period(2)                  // 초기 대기 시간
                .maxPeriod(32)              // 최대 대기 시간
                .maxAttempts(5)             // 최대 재시도 횟수
                .multiplier(2)              // 각 재시도 시 대기 시간이 증가하는 지수 배율
                .build();
    }
    
    @Bean
    public ErrorDecoder openaiErrorDecoder() {
        return new OpenAiErrorDecoder();
    }
}&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;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-3&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[ 테스트&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1분 동안 허용할 수 있는 범위까지 요청한 다음, 한번 더 요청해보았다.&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2468&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byGSXK/btsMmOFDdGa/qgThyR7vsUBRw1lCmPBotk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byGSXK/btsMmOFDdGa/qgThyR7vsUBRw1lCmPBotk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byGSXK/btsMmOFDdGa/qgThyR7vsUBRw1lCmPBotk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyGSXK%2FbtsMmOFDdGa%2FqgThyR7vsUBRw1lCmPBotk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2468&quot; height=&quot;240&quot; data-origin-width=&quot;2468&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;에러 로그에서 초 단위를 보면 39 &amp;rarr; 41 &amp;rarr; 45 &amp;rarr; 53 &amp;rarr; 10 &amp;rarr; 42초로 재시도할 때마다 일정 대기 시간이 2배씩 늘어나는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이렇게 최대 재시도 횟수와 지수 백오프 방식이 정상적으로 실행이 되는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;4.&lt;span&gt;&amp;nbsp;재시도 도입 완료! 성공 확률이 어떻게 될까?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 성공 확률을 알기 위해서 jMeter로 신뢰성 테스트를 해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;테스트를 진행하기 위해 OpenAI Rate 세팅은 아래와 같이 구성하였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tier : 1&lt;/li&gt;
&lt;li&gt;Model : gpt-3.5-turbo&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RPM(분당 요청 수) : 500&lt;/li&gt;
&lt;li&gt;RPD(일일 요청 수) : 10000&lt;/li&gt;
&lt;li&gt;TPM(분당 토큰 수) : 200000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QZbjd/btsMo0yz2wC/dlwSYMZJDdt5Pgz0bdTkB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QZbjd/btsMo0yz2wC/dlwSYMZJDdt5Pgz0bdTkB0/img.png&quot; style=&quot;width: 41.8829%; margin-right: 10px;&quot; width=&quot;572&quot; height=&quot;186&quot; data-widthpercent=&quot;42.38&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;344&quot; data-origin-width=&quot;1056&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QZbjd/btsMo0yz2wC/dlwSYMZJDdt5Pgz0bdTkB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQZbjd%2FbtsMo0yz2wC%2FdlwSYMZJDdt5Pgz0bdTkB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1056&quot; height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4iKM8/btsMnzh1j84/kBnihSOgWSYgS676oKE9K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4iKM8/btsMnzh1j84/kBnihSOgWSYgS676oKE9K0/img.png&quot; style=&quot;width: 56.9543%;&quot; width=&quot;676&quot; height=&quot;162&quot; data-widthpercent=&quot;57.62&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;344&quot; data-origin-width=&quot;1436&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4iKM8/btsMnzh1j84/kBnihSOgWSYgS676oKE9K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4iKM8%2FbtsMnzh1j84%2FkBnihSOgWSYgS676oKE9K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1436&quot; height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;Rate Limit 이 1분당 500개 요청을 받을 수 있어서, 10초 안에 1000개의 요청을 보내보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아마도 초과 요청을 보냈으니깐 에러가 발생할 것으로 예측된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;재시도 도입 전&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1908&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehqhCC/btsMm5BzvTg/569V6S0mN42I3WrGW9g4A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehqhCC/btsMm5BzvTg/569V6S0mN42I3WrGW9g4A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehqhCC/btsMm5BzvTg/569V6S0mN42I3WrGW9g4A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FehqhCC%2FbtsMm5BzvTg%2F569V6S0mN42I3WrGW9g4A1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1908&quot; height=&quot;168&quot; data-origin-width=&quot;1908&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;1000개 요청을 보냈을 때 582건 성공적으로 요청이 됐고 &lt;b&gt;418개 실패&lt;/b&gt;가 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;평균 시간은 659ms -&amp;gt; 0.6초 걸리고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;성공률은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;58.2%&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;재시도 도입 후&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GEevS/btsMnCZ21gw/54aD8Qthq9d89a58Gb5afK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GEevS/btsMnCZ21gw/54aD8Qthq9d89a58Gb5afK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GEevS/btsMnCZ21gw/54aD8Qthq9d89a58Gb5afK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGEevS%2FbtsMnCZ21gw%2F54aD8Qthq9d89a58Gb5afK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1898&quot; height=&quot;166&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;1000개 요청을 보냈을 때 1000건 모두 다 성공이 되었다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;평균 시간은 14709ms -&amp;gt; 14.7초 걸리고 성공률은 &lt;b&gt;100%&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;아무래도 5번 재시도를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;지수 백오프 방식으로 일정 시간 대기한 후 요청을 보내니깐 평균 시간이 오래 걸리게 된 거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;결론적으로 재시도 전략 도입으로 성공 확률이 약 &lt;b&gt;72%&lt;/b&gt; 증가했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;5.&lt;span&gt; 추후 고도화 적용하기  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;API 요청 제한을 재시도 전략을 도입해서 해결했지만, 단순히 동일한 요청을 반복하는 방식은 한계가 있을 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;보다 안정적인 운영을 위해,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;API Key를 변경하는 방식&lt;/b&gt;을 적용하는 것이 더 효과적인 해결책이 될 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;또한, OpenAI API에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;TPM(분당 토큰 수)&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;제한이 존재하는데, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1분 동안 허용된 최대 토큰 수를 초과하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;아무리 재시도를 하더라도 실패할 수밖에 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;이러한 문제를 해결하는 방법 중 하나는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;요금제를 업그레이드(Tier 상승)하는 것&lt;/b&gt;이지만, 실제 운영 환경에서는 즉시 적용되기 어려워 현실적인 해결책이 되지 않는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;따라서 보다 효율적인 접근 방식은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&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;&lt;br /&gt;예를 들어, gpt-3.5-turbo를 사용하다가 토큰 제한에 걸리면 gpt-4-turbo로 변경하면 정상적으로 요청을 처리할 수 있다.&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-start=&quot;716&quot; data-end=&quot;790&quot; data-ke-size=&quot;size16&quot;&gt;1️⃣&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;첫 번째 재시도:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;API Key를 변경하여 다시 요청&lt;br /&gt;2️⃣&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;두 번째 재시도:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;모델을 변경하여 다시 요청&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;716&quot; data-end=&quot;790&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;792&quot; data-end=&quot;875&quot; data-ke-size=&quot;size16&quot;&gt;이 방식으로 구성하면, 요청 실패 확률을 크게 줄일 수 있고, API Key 변경과 모델 변경을 통해 보다 안정적인 API 요청이 가능해진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;792&quot; data-end=&quot;875&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이러한 전략을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;요청 제한 문제를 효과적으로 해결&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>∙Java &amp;amp; Spring</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/72</guid>
      <comments>https://coor.tistory.com/72#entry72comment</comments>
      <pubDate>Wed, 19 Feb 2025 16:37:08 +0900</pubDate>
    </item>
    <item>
      <title>소프트 스킬</title>
      <link>https://coor.tistory.com/68</link>
      <description>&lt;div id=&quot;toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; 지인 추천으로 읽게 된 책으로, 엔지니어로써 교훈과 마케팅, 연봉, 건강, 은퇴, 마음 문제, 업무 문제 등등 여러 가지 카테고리로 많은 이야기를 해주는 책이다. 개인적으로 울림이 있는 내용들이 있어서 좋았다. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;앞으로 개발자로 살아가기 위해 어떤 생각을 하면서 살아야 할지 알게 된 책이다. &lt;/span&gt;총 74개의 chapter 있는데 그 중에서 마음에 들었던 부분만 내용을 추려서 정리했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; 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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어디서, 어떻게 경험을 쌓을 수 있을까?&amp;nbsp;시작하기에는 사내 발표가 좋다. 회사에 근무하는 동안 다양한 주제로 발표할 기회가 있기 마련이고, 특히 자신이 일하는 분야와 관련한 강연 기회가 많이 찾아온다. 팀에서 사용하는 기술 혹은 팀에 필요한 분야에 관해 발표하겠다고 자원하라. 전문가인 척할 필요는 없다. 배운 내용을 공유한다는 마음으로 성의껏 임하라.&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;이렇게 어느 정도 연습한 후에는 개발자 콘퍼런스에 참여하라.&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;&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;&quot;저는 웹 사이트를 디자인할 수 있습니다. HTML5, CSS, 웹 디자인을 능숙하게 다룹니다. 동종 업계 회사 웹 사이트 제작 경험 또한 많습니다.&quot;&lt;br /&gt;&lt;br /&gt;아니면&lt;br /&gt;&lt;br /&gt;&quot;현재 귀사의 웹 사이트가 최대 트래픽을 내고 있습니까? 트래픽이 고객으로 잘 전환되고 있습니까? 대부분 소규모 기업의 웹 사이트는 그렇지 않습니다. 하지만 저는 트 래픽의 고객 전환율을 높이도록 설계한 맞춤형 웹 사이트를 제작할 수 있습니다. 제 도움을 받아서 고객 수가 두 배, 세 배로 늘어난 소규모 기업이 많습니다. 귀사에도 이와 똑같은 도움을 드릴 수 있습니다.&amp;rdquo;&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;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;퇴사하기 전에 SNS로 친구나 지인에게 프리랜서 일을 시작해서 일거리를 찾고 있다고 알려라. 할 수 있는 일, 해결할 수 있는 문제를 정확히 밝혀라. 이럴 때 전문성이 있으면 큰 도움이 된다.&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;지금까지 당신이 제시한 금액을 거절하거나 너무 높 다고 말한 잠재 고객이 단 한 명도 없었다면 금액을 올려보라. 금액이 높다고 거절하는 고객이 등장할 때까지 올려라. 당신이 제공하는 서비스를 기꺼이 제값 주고 살 고객은 생각보다 많다.&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;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;블로그 포스트: 개인 블로그에 쓰든 다른 사람의 블로그에 쓰든 상관없다.&lt;/li&gt;
&lt;li&gt;팟캐스트: 직접 팟캐스트를 운영하거나 다른 팟캐스트에 게스트로 참여하라.&lt;/li&gt;
&lt;li&gt;동영상: 유튜브 같은 사이트에 시의적절한 동영상이나 교육용 동영상을 올려라.&lt;/li&gt;
&lt;li&gt;잡지 기사: 소프트웨어 개발 잡지에 기고하라.&lt;/li&gt;
&lt;li&gt;책: 내가 이 책을 쓴 것처럼 당신도 책을 써라. 자가 출판을 해도 좋다.&lt;/li&gt;
&lt;li&gt;코드 캠프: 코드 캠프는 대부분 누구에게나 강연 기회를 준다.&lt;/li&gt;
&lt;li&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Q1. 아직 블로그가 없다면 개설할 것을 권한다. 만약 개설한다면 어떤 주제를 집중적으로 다루겠는가?&lt;br /&gt;- 블로그 개설함. 기술 관련 내용 적기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Q2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;새 블로그에 올릴 포스트를 최소 20개 이상 생각해보라.&lt;br /&gt;- 현재 50개 정도 작성 완료&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Q3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;실제 블로그 개설 일정을 짜고, 콘텐츠를 만들어보라.&lt;br /&gt;- skip&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Q4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;유튜브 채널은 어떤가?&lt;br /&gt;- 유튜브 운영 고민중. 만약 하게 된다면 기술적 이슈에 대한 이야기와 라이브러리 사용법에 대한 영상을 찍을 거 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Q5.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;소프트웨어 개발자로서 자신을 마케팅할 수 있는 모든 방법을 목록으로 작성하라.&lt;br /&gt;- 블로그 &amp;gt; 링크드인 &amp;gt; 유튜브 &amp;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;&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;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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동영상은 다른 매체에서 근접할 수도 없는 방식으로 시청자와 연결될 길을 제공한다. 누군가가 당신을 동영상에서 본다는 것은 당신이 TV에 출연한 것이나 마찬가지다. 그들의 마음속에서 당신은 실제 유명인이 된다. 그러한 스타 효과는 브랜드를 만드는 데 강력한 효과를 낸다.&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;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;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;1. 시간이 가장 오래 걸리는 지점&lt;br /&gt;- 코딩 테스트, 한 권 책 읽기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 개선의 여지가 있는 반복 작업&lt;br /&gt;- 코딩 테스트, 개인 운영 사이트 만들기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. 완벽히 이해하지 못한 부분&lt;br /&gt;- DFS&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4. 답할 수 없는 면접 질문&lt;br /&gt;- 클러스터 환경 시 문제점 및 시나리오, CS의 질문에서 꼬리 질문&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;집중할 영역을 최대한 구체적이고 명확하게 설정하라. 자신의 약점이 물리학이라고 생각할 수 있다.&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;/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;&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;뽀모도로 기법은 단순하다. 먼저 하루 동안 해야 할 일을 계획한다. 그 후 타이머를 25분으로 설정하고 계획했던 첫 번째 일을 시작한다. 작업은 한 번에 한 개만 진행하고, 25분 동안 완전히 집중해서 일해야 한다. 25분이 끝나면 다시 타이머를 5분으로 설정하고 휴식을 취한다. 여기까지가 1 뽀모도로다. 뽀모도로를 4번 반복하고 나면 15분 동안 긴 휴식을 취한다.&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;당신이 하루 평균 8시간 근무를 한다면 30분마다 뽀모도로가 하나씩 완료되므로 이론적으로는 하루 동안 뽀모도로를 16개 해낼 수 있어야 한다. 하지만 실제로는 하루에 12시간 내내 일해도 뽀모도로 16개를 채우기는 몹시 어렵다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;실천하기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뽀모도로 기법을 시도해보자. 지금은 하루에 뽀모도로를 몇 개나 완료해내는지 고민하지 않아도 되지만, 이 기법을 시도해보고 한 주 동안 얼마나 해내는지 기록해보자.&lt;/li&gt;
&lt;li&gt;일주일 안에 완료할 수 있는 뽀모도로 개수를 예측할 수 있다면 다음 주에 완수할 목표를 설정해보자. 실제로 해낸 일의 양에 주의를 기울이고, 목표했던 뽀모도로 개수를 완료한 날에는 어떤 기분이었는지 생각해보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p 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;&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;작은 문제로 나눌 때, 가장 먼저 이 작업을 차례대로 연결된 작은 작업으로 나눠야 한다.&amp;nbsp;&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;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;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;이 말은 공감된다. 힘든 일을 하면 그 때의 순간은 힘들지만, 나중에 지나고 나면 많은 것을 얻게 된다.&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;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;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;br /&gt;- 지금까지 한 일, 앞으로 한 일, 업무 방해 요소를 얘기를 할 때 즉석으로 말하는 경우가 많음&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;시간 관리&lt;/b&gt;&lt;br /&gt;- 현재 시간을 어떻게 관리하는가? 아침이면 오늘 어떤 일을 할지 예상할 수 있는가? 일상적인 일을 처리하는 데 어느 정도 시간이 들지 예상할 수 있는가?&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;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;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;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;br /&gt;- 좋은 습관 : 강의가 길든 짧든 끝까지 다 듣기, 한번 공부하기 마음먹으면 2시간 이상 공부하는 것, 나의 대한 부정적인, 긍정적인 요소를 인정하는 것, 스트레스 관리, 블로그 글 작성하기&lt;br /&gt;- 나쁜 습관 : 공부하다가 휴대폰 보는 것, 오전에 시간 관리를 잘 못하는 것, 흥미를 느끼지 못하면 안 하는 것, 하고 싶은 개 N개 이면 시간 관리를 못하는 것&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;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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;연봉 협상 팁&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 가장 좋은 협상은 담당자에게 얼마를 생각하는지 여쭤보고 대답을 듣는 것이다. 하지만 이 방법은 쉽지 않다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 한 번은 도전해봐야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 연봉을 묻기 힘든 상황이고 담당자가 희망 연봉을 물어본다면 바로 대답하지 마라. 그럴 때는 밑에 있는 것처럼 전략적으로 대응해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;사실 구체적인 금액은 회사에 관해, 또 제가 하게 될 일에 관해 더 자세히 들은 후에 이야기하는 게 맞겠지요. 하지만 서로 시간 낭비하지 않으려면 대략적인 수준이라도 맞아야 한다는 뜻으로 하시는 말씀이신 듯합니다. 제가 잘 이해했나요?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상대는 보통 그렇다고 답할 것이다. 그러면 다음과 같은 후속 질문을 하라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그 자리에 할당된 예산이 어느 정도 정해져 있지 않나요?&quot;&lt;/p&gt;
&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;&quot;정확한 금액을 제시하기에는 아직 정보가 부족하긴 하지만 그래도 예산 범위를 말씀해 주신다면 제 예상과 맞는지 정도는 말씀드릴 수 있습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렵다고 생각할지 모르지만 상대가 당신에게 금액 제시를 요구했다면 당신도 상대에게 말해달라고 부탁하지 못할 이유가 없다. 상대가 먼저 금액을 제시하도록 최선을 다해보라.&lt;/p&gt;
&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 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;은퇴 계획 세우기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곧 30살을 앞두고 있지만 은퇴 계획을 세워본 적이 없다. 이 책에서는 &quot;&lt;b&gt;불로 소득&lt;/b&gt;&quot;을 만들어서 은퇴 계획을 세우라고 한다.&lt;br /&gt;불로 소득은 노동을 제공하지 않고도 얻을 수 있는 소득을 말한다. 즉 부동산, 온라인 강의, 책, 유튜브, 주식(배당금) 등 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 불로 소득을 만들기 위해서 어떤 게 좋을까? 불로 소득을 만들기 위해서 하나를 선택하고 그 분야에 전문가가 되어야 한다.&lt;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;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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;월 100만원 어디에 투자해야 할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;글쓴이는 투자할 100만원 &lt;b&gt;주식, 부동산, 채권에 투자하지 말라고 한다&lt;/b&gt;. 특히 코인은 안된다. &lt;b&gt;대신 자신에게 투자해라&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;더 많은 돈을 벌어서 더 많은 투자금을 모을 수 있도록 100만원 교육이나 연수에 투자해라. 나의 몸 값을 올려라.&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;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;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;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;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;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;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;그리고 새로운&amp;nbsp;사고방식이&amp;nbsp;잠재의식에&amp;nbsp;잘&amp;nbsp;뿌리내릴&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;자신에게&amp;nbsp;긍정적인&amp;nbsp;말을&amp;nbsp;많이&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;&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;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;이&amp;nbsp;책에서&amp;nbsp;단&amp;nbsp;한&amp;nbsp;가지만&amp;nbsp;기억하겠다면&amp;nbsp;이&amp;nbsp;말을&amp;nbsp;기억하라.&amp;nbsp;&lt;b&gt;실패를&amp;nbsp;수용하는&amp;nbsp;법을&amp;nbsp;배워라.&lt;/b&gt;&amp;nbsp;실패가&amp;nbsp;예상되더라도&amp;nbsp;정면으로&amp;nbsp;맞을&amp;nbsp;준비를&amp;nbsp;해두어라.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;실패를 두려워하지 않는 것만으로는 부족하다. 실패를 적극적으로 찾아다녀야 한다. 성장하고 싶다면 실패할 수밖에 없는 상황에 자신을 던져라. 위험하다는 이유로 아무런 도전도 하지 않으면 정체되기 십상이다. 사람은 편안한 곳을 찾는다. 밖에 비가 오면 문을 닫고 판자로 막고 밖으로 나가지 않으려 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;가끔은 젖을 필요가 있다. 당신을 성장하게 하는 불편한 상황에 기꺼이 뛰어들 필요가 있다. 배를 실패의 바다로 몰아넣을수록 반대편에서 성공의 바람이 더욱 강하게 불어온다는 믿음을 갖고 가끔은 그런 상황을 적극적으로 찾아다녀야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;당신은 실패를 어떻게 받아들이는가? 성난 파도 속으로 뛰어들도록 자신을 어떻게 설득하겠는가? 실패를 삶의 일부로 받아들이는 일부터 시작하라. 살면서 많은 실패를 경험할 것이고 그 대부분은 피할 수 없으리라는 사실을 자각하라. 모든 일을 처음부터 완벽하게 할 수는 없다. 실수도 하게 될 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;실패해도 괜찮다는 사실 또한 깨달아야 한다. 실패해도 괜찮다. 실패를 줄이려고 노력하는 건 괜찮지만 실패 때문에 자아가 상처 입을까 두려워서 기회를 놓치는 일은 없게 하라. 실패해도 괜찮다는 것, 실패보다 실패에 어떻게 대처하는지가 당신의 정체성을 규정한다는 것을 깨달으면 실패에 대한 두려움을 훨씬 쉽게 극복할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;마지막으로&amp;nbsp;실패에&amp;nbsp;최대한&amp;nbsp;많이&amp;nbsp;노출되길&amp;nbsp;권하고&amp;nbsp;싶다.&amp;nbsp;일부러라도&amp;nbsp;불편한&amp;nbsp;일을&amp;nbsp;자꾸&amp;nbsp;해보라.&amp;nbsp;실패할&amp;nbsp;수밖에&amp;nbsp;없는&amp;nbsp;상황에&amp;nbsp;의도적으로&amp;nbsp;자신을&amp;nbsp;노출하라.&amp;nbsp;포기하지&amp;nbsp;않는&amp;nbsp;것이&amp;nbsp;열쇠다.&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;&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;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;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;바보같이 보였다 해서 신체에 문제가 생기는 것도 아니다. 발표를 아무리 크게 망쳐도 사실 크게 신경 쓰는 사람은 없다. 끝나고 나면 기억조차 못 하는 사람이 대부분일 것이다.&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;바보 같아 보일 것을 두려워하지 마라. 작든 크든 도전하라.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>∙도서</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/68</guid>
      <comments>https://coor.tistory.com/68#entry68comment</comments>
      <pubDate>Thu, 2 Jan 2025 00:32:16 +0900</pubDate>
    </item>
    <item>
      <title>Redis&amp;amp;RabbitMQ를 활용한 선착순 이벤트 개발 도전기</title>
      <link>https://coor.tistory.com/67</link>
      <description>&lt;div id=&quot;toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;toc-contents&quot; class=&quot;toc-contents&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이 글에서는 동시에 많은 사용자가 접속하는 상황에도 대응 가능한 &lt;b&gt;&quot;선착순 이벤트&quot;&lt;/b&gt; 시스템 설계 경험을 공유해 드리고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기술 스택 : Java 17, Sprint boot 3, Redis, RabbitMQ, AWS, Mysql 8, Pinpoint 3, nGrinder&lt;/blockquote&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1. 들어가며&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;선착순 이벤트는 무료로 이력서 피드백을 제공하는 사이트에서&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;nbsp;이벤트를 진행했습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;b&gt;선착순 이벤트 요구사항&lt;/b&gt;은 다음과 같습니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;1. 이벤트 기간 동안, 매일 특정 시간 오픈하며 총 신청 인원을 한정한다. &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;2. 신청 인원은 당일 정해진 양을 초과해서는 안된다. &lt;br /&gt;3. 신청은 1인당 1번만 신청할 수 있다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2. 동시성 이슈 문제&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bccHBc/btsKg7gets1/VPDyYtSPGrTdxVepWIWXa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bccHBc/btsKg7gets1/VPDyYtSPGrTdxVepWIWXa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bccHBc/btsKg7gets1/VPDyYtSPGrTdxVepWIWXa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbccHBc%2FbtsKg7gets1%2FVPDyYtSPGrTdxVepWIWXa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;902&quot; height=&quot;484&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;b&gt;RDB&lt;/b&gt;에 의존하여 수량 체크를 하면, 동시성 이슈로 인하여 선착순 신청 인원이 초과될 위험이 있었습니다. 또한 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;수백에서 수천 명이 동시에 접속하여 이력서를 제출하려는 상황에서 일부 사용자들이 중복으로 이력서를 제출하는 경우가 있었습니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;동시성&amp;nbsp;이슈를&amp;nbsp;해결하고&amp;nbsp;위&amp;nbsp;요구사항을&amp;nbsp;충족하기&amp;nbsp;위해&amp;nbsp;&lt;b&gt;Redis&lt;/b&gt;를&amp;nbsp;활용하기로&amp;nbsp;하였습니다.&amp;nbsp;그&amp;nbsp;이유는,&amp;nbsp;Redis가&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;데이터&amp;nbsp;타입과&amp;nbsp;커맨드를&amp;nbsp;제공해&amp;nbsp;주기&amp;nbsp;때문에&amp;nbsp;모든&amp;nbsp;요구사항들을&amp;nbsp;만족시킬&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;거라&amp;nbsp;생각했기&amp;nbsp;때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;3. Redis&amp;nbsp;를&amp;nbsp;활용한&amp;nbsp;선착순&amp;nbsp;이벤트!&amp;nbsp;고민의&amp;nbsp;흔적들&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 id=&quot;heading-4&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt; 방안 1. &lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;SET 자료형을 통한 선착순 인원 관리&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;SET 자료형은 중복값을 제거해 주는 특성을 이용해, 3번째 요구사항인 중복 신청을 막을 수 있어서 선택하게 되었습니다. &lt;br /&gt;또한 삽입 --&amp;gt; O(1), 삭제 --&amp;gt; O(1), 카운트 조회 --&amp;gt; O(1), 전체 조회 --&amp;gt; O(N) 이러한 상수 시간 복잡도 덕분에 대규모 데이터에 대해서도 빠른 처리가 가능합니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SET 자료형을 활용해 사용자의 유니크한  email를 value로 사용하여 기존에 이미 신청했는지 확인하여, 중복 신청을 막을 수 있게 되었습니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729695457787&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;KEY 정보
- eventId : 이벤트 PK ID
- email : 사용자 이메일

이벤트 조회
redis&amp;gt; GET event::{eventId}

중복 신청 체크
redis&amp;gt; SISMEMBER event.request.eventId={eventId} {email}

이벤트 신청 인원 수 조회
redis&amp;gt; SCARD event.request.eventId={eventId}

신청 가능 여부 체크
if(이벤트 최대 신청자 수 &amp;lt; 신청 인원 수) {
  SADD event.request.eventId=1 {email} // 신청 확정 처리
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;SISMEMBER 커맨드를 통해서 중복 신청인지 확인합니다.&amp;nbsp;&lt;br /&gt;SCARD 커맨드를 통해 이벤트 신청 인원을 조회하여 현재 몇 명 신청했는지 알 수 있습니다.&lt;br /&gt;중복 신청을 하지 않았고 이벤트 신청 인원이 초과하지 않았다면, SADD 커맨드를 통해서 사용자의 이메일 값을 넣어서 신청 확정 처리를 합니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;위와 같은 방식으로 개발하고 성능 테스트를 진행해 보았습니다. 그런데&amp;nbsp;이&amp;nbsp;방식에서는&amp;nbsp;트래픽이&amp;nbsp;몰릴&amp;nbsp;경우&amp;nbsp;동시성&amp;nbsp;이슈를&amp;nbsp;야기하며&amp;nbsp;아래&amp;nbsp;그림처럼&amp;nbsp;&lt;b&gt;초과&amp;nbsp;신청&lt;/b&gt;이&amp;nbsp;발생하는&amp;nbsp;케이스를&amp;nbsp;확인했습니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nTQuC/btsKiaiEZEI/ALWMWDBBA8zO2zrcADbb11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nTQuC/btsKiaiEZEI/ALWMWDBBA8zO2zrcADbb11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nTQuC/btsKiaiEZEI/ALWMWDBBA8zO2zrcADbb11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnTQuC%2FbtsKiaiEZEI%2FALWMWDBBA8zO2zrcADbb11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;453&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;실제로 nGrinder 툴을 이용하여 동시 1000건 요청을 하고 250명 신청할 수 있도록 테스트를 해보았습니다.&lt;br /&gt;아래 그림처럼 성공한 테스트 263명으로 &lt;b&gt;13명이 초과 신청&lt;/b&gt;이 되었습니다..!&lt;/span&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;504&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fb24I/btsKhdN3uNA/FQaoHxIvH84IiZyEL30ghk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fb24I/btsKhdN3uNA/FQaoHxIvH84IiZyEL30ghk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fb24I/btsKhdN3uNA/FQaoHxIvH84IiZyEL30ghk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFb24I%2FbtsKhdN3uNA%2FFQaoHxIvH84IiZyEL30ghk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;490&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;&lt;br /&gt;이를 해결하고자 Redis Lock 을 활용하기로 하였습니다. Lock 은 여러 개의 동시 요청을 순차 처리를 보장합니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-4&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt; 방안 2. &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;SET 자료형 + Redis Lcok 을 통한 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;선착순 인원 관리 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Redis에서 Lock 을 구현하는 대표적인 Named Lock, Lettuce Lock, Redisson Lock 이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 71px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.3797%; height: 17px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 44.3798%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;개념&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.2404%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.3797%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;Named Lock&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 44.3798%; height: 18px;&quot;&gt;특정 키 이름으로 락을 설정하는 방식&lt;/td&gt;
&lt;td style=&quot;width: 41.2404%; height: 18px;&quot;&gt;락을 생성하고, 해당 키가 존재할 경우 다른 클라이언트가 락을 얻지 못하도록 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.3797%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt; Lettuce Lcok&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 44.3798%; height: 18px;&quot;&gt;SETNEX&amp;nbsp;명령어를&amp;nbsp;사용하여&amp;nbsp;분산&amp;nbsp;환경에서&amp;nbsp;락을&amp;nbsp;처리&lt;/td&gt;
&lt;td style=&quot;width: 41.2404%; height: 18px;&quot;&gt;Spinlock 방식으로 락 획득 요청을 하기 때문에 Redis에 많은 부하를 줄 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.3797%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;Redisson Lcok&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 44.3798%; height: 18px;&quot;&gt;pub/sub 기능을 활용하여 분산 환경에서 안전하게 락을 처리&lt;/td&gt;
&lt;td style=&quot;width: 41.2404%; height: 18px;&quot;&gt;tryLock()과&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;지원&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이 3개 중에서 &lt;b&gt;Redisson&amp;nbsp;Lcok 선택&lt;/b&gt;하였습니다. 그 이유는 다음과 같습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Named Lock 은 특정 자원에 대해 하나의 락을 설정합니다. &lt;b&gt;선착순 이벤트의 대규모 동시성 처리&lt;/b&gt;와 &lt;b&gt;빠른 응답&lt;/b&gt;이 요구되는 상황에서는 성능 및 공정성 측면에서 한계가 있기 때문에 부합하지 않습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Lettuce Lcok 은 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;분산락을 사용하기 위해서는&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;setnx,&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;setex&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;등을 이용해 분산락을 직접 구현해야 합니다. 또한 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;retry, timeout과 같은 기능을 구현해 주어야 한다는 번거로움이 있습니다. &lt;b&gt;이에 비해 Redisson 는 Lock Interface 지원&lt;/b&gt;하기 때문에 락을 보다 안전하게 사용할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;락을 획득하는 방식에도 차이가 있습니다. &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Lettuce Lcok 은&lt;span&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;지속적으로 Redis에게 락이 해제되었는지 요청을 보내는 스핀락 방식으로 동작합니다. 요청이 많을수록 Redis가 받는 부하는 커지게 됩니다. &lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이에 비해&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;Redisson은 Pub/Sub 방식&lt;/b&gt;을 이용하기에 락이 해제되면 락을 subscribe 하는 클라이언트는 락이 해제되었다는 신호를 받고 락 획득을 시도하게 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WEFex/btsKgbcPkzd/dfq04Id5vcEVpqrx6jLfTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WEFex/btsKgbcPkzd/dfq04Id5vcEVpqrx6jLfTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WEFex/btsKgbcPkzd/dfq04Id5vcEVpqrx6jLfTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWEFex%2FbtsKgbcPkzd%2Fdfq04Id5vcEVpqrx6jLfTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;481&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;RDB 를 의존하는 기본 방식에서 SET 자료형과 Redisson&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;nbsp;Lcok 바꾸어서 아래 그림처럼 적용하였습니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FYX8X/btsKgtEeUS9/IpwI7w54pNNT63JsQLDg00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FYX8X/btsKgtEeUS9/IpwI7w54pNNT63JsQLDg00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FYX8X/btsKgtEeUS9/IpwI7w54pNNT63JsQLDg00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFYX8X%2FbtsKgtEeUS9%2FIpwI7w54pNNT63JsQLDg00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;972&quot; height=&quot;554&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;락 범위는 트랜잭션 범위보다 큰 범위에서 진행하였습니다.&lt;/span&gt;&lt;br /&gt;&lt;b&gt;그 이유는 데이터 정합성을 맞추기 위해서입니다.&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;락의&amp;nbsp;해제가&amp;nbsp;트랜잭션&amp;nbsp;커밋보다&amp;nbsp;먼저&amp;nbsp;이뤄지면&amp;nbsp;데이터&amp;nbsp;정합성이&amp;nbsp;깨질&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;4. 동시성 이슈는 해결 완료! 성능은 어떨까?  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;성능 테스트는 nGrinder를 통해 진행하였습니다. 이 테스트를 진행하며 모니터링한 결과... 2가지 문제점이 있었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-4&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제점 1.&lt;span&gt; 락 경쟁으로 인한 TPS 감소 및 병목 현상&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/56Rdr/btsKhJl81lT/NzrfKYBk7PnHl7JisgTCq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/56Rdr/btsKhJl81lT/NzrfKYBk7PnHl7JisgTCq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/56Rdr/btsKhJl81lT/NzrfKYBk7PnHl7JisgTCq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F56Rdr%2FbtsKhJl81lT%2FNzrfKYBk7PnHl7JisgTCq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1218&quot; height=&quot;363&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동시 요청은 1875번, 신청 가능한 인원은 300명으로 설정하고 테스트를 해보았습니다.&lt;br /&gt;하지만 성공 테스트 수가 300명이 아닌 126명 테스트에 성공하였습니다.&amp;nbsp;&lt;br /&gt;그 이유는 다음과 같습니다.&lt;br /&gt;1. 트랜잭션 범위보다 큰  Lcok 으로 인해 다른 스레드가 Blocking 되어 TPS 감소 및 성능 저하&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cT0KTp/btsKhef3wOP/dvre8pwtZfHFhJGjtEo2G0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cT0KTp/btsKhef3wOP/dvre8pwtZfHFhJGjtEo2G0/img.png&quot; data-alt=&quot;실제 응답 속도 6000ms 성능 저하&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cT0KTp/btsKhef3wOP/dvre8pwtZfHFhJGjtEo2G0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcT0KTp%2FbtsKhef3wOP%2Fdvre8pwtZfHFhJGjtEo2G0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;238&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 응답 속도 6000ms 성능 저하&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 트랜잭션의 범위가 길어서 MySQL 부하 증가&lt;br /&gt;3. 트랜잭션 롤백 시 이벤트 조회부터 다시 수행해야 하는 비효율성 발생&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제점 2.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;DB 부하&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;319&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjDN3h/btsKg5ipZoH/bO1HueSnTf6VZmJYp0kQVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjDN3h/btsKg5ipZoH/bO1HueSnTf6VZmJYp0kQVk/img.png&quot; data-alt=&quot;AWS RDS CloudWatch&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjDN3h/btsKg5ipZoH/bO1HueSnTf6VZmJYp0kQVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjDN3h%2FbtsKg5ipZoH%2FbO1HueSnTf6VZmJYp0kQVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;319&quot; height=&quot;236&quot; data-origin-width=&quot;319&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS RDS CloudWatch&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 선착순 이벤트 API 비즈니스 로직 내에서 DB 에 저장하는 Insert Query가 부하가 발생하는 점을 확인했습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;DB 에 부하가 발생하여 장애가 발생하면 전체 시스템에 큰 영향을 줄 수 있는 문제점을 파악하였습니다.&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Read DB와&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Write DB 분리해서 진행해 볼까? 생각해 보았는데, &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Read DB의 경우 API의 트래픽이 몰리는 경우를 대비하여 Scale-out을 통해 트래픽 분산 및 대응이 가능하지만, Write DB의 경우 Scale-up을 통해 대비를 하더라도 Single Point of Failure가 발생할 경우 서비스 전체에 영향을 주게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 EDA 기반 RabbitMQ 를 활용하여 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;RabbitMQ 애플리케이션에서 신청 완료 처리하도록 변경하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;5. 문제를 해결할 RabbitMQ 도입! &lt;span data-token-index=&quot;0&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3Dn9N/btsKhCAXBxN/cDBomojB9OfhQSicPBEMK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3Dn9N/btsKhCAXBxN/cDBomojB9OfhQSicPBEMK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3Dn9N/btsKhCAXBxN/cDBomojB9OfhQSicPBEMK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3Dn9N%2FbtsKhCAXBxN%2FcDBomojB9OfhQSicPBEMK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1092&quot; height=&quot;574&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;검증이 끝난 요청을&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;nbsp;Queue 저장하여 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;RabbitMQ 애플리케이션에서 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Consume 하여 신청 완료 처리하도록 작업하였습니다.&lt;br /&gt;이렇게 함으로써, 첫 캐싱 작업 외에 MySQL Access 없어지게 되고 트랜잭션과 락이 분리가 되어 범위가 적어지게 되었습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;다시 성능 테스트를 해보았습니다.&lt;br /&gt;- 동시 요청 : 3000건&lt;br /&gt;- 신청 가능한 인원 : 1000명&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7azPs/btsKhJ031Nk/9Tktrvin5yQD7azKK4czMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7azPs/btsKhJ031Nk/9Tktrvin5yQD7azKK4czMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7azPs/btsKhJ031Nk/9Tktrvin5yQD7azKK4czMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7azPs%2FbtsKhJ031Nk%2F9Tktrvin5yQD7azKK4czMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1216&quot; height=&quot;370&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;TPS는 45.3으로 개선 전의 13.1 보다 &lt;b&gt;245.8% 증가&lt;/b&gt;하였습니다.&lt;br /&gt;성공한 테스트 수도 1000개 정상적으로 요청이 되었습니다.&lt;br /&gt;이렇게 첫 번째 문제점인 락 경쟁으로 인한 TPS 감소 및 병목 현상 문제를 해결할 수 있었습니다.&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: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;두 번째 문제인 DB 부하를 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzSFkW/btsKipVp1x4/BlTncrSE7GKTPJo6EVVLz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzSFkW/btsKipVp1x4/BlTncrSE7GKTPJo6EVVLz0/img.png&quot; data-alt=&quot;AWS RDS CloudWatch&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzSFkW/btsKipVp1x4/BlTncrSE7GKTPJo6EVVLz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzSFkW%2FbtsKipVp1x4%2FBlTncrSE7GKTPJo6EVVLz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;240&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS RDS CloudWatch&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;RabbitMQ 적용하기 전에는 13% CPU 사용률이 보였는데, 적용 후에는 &lt;b&gt;8% &lt;/b&gt;로 떨어진 것을 확인할 수 있습니다.&lt;br /&gt;이렇게 두 번째 문제점인 DB 부하 문제를 10% 아래로 감소시키는 데 성공하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이를 통해 실제 이벤트 서비스 API가 DB에 접근하지 않고 신청 가능 여부만 판단하여 고객에게 응답을 주기 때문에 처리 속도가 개선되었고, 사용자 경험 또한 향상되는 결과를 얻을 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-0&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f15f5f;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;6. 오류 케이스&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;heading-4&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[ &lt;span&gt;비동기&amp;nbsp;스레드&amp;nbsp;풀의&amp;nbsp;최대&amp;nbsp;크기&amp;nbsp;초과&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;nGirnder 성능 테스트를 하다가 50건까지는 정상적으로 동작하다가 50건 이후로는 아래 사진과 같이 TaskRejectedException 예외가 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;발생하였습니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQvIHB/btsKkuH66Ri/Z4ZRQHq6CaZA2O6NfqjL51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQvIHB/btsKkuH66Ri/Z4ZRQHq6CaZA2O6NfqjL51/img.png&quot; data-alt=&quot;Pinpoint Transaction&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQvIHB/btsKkuH66Ri/Z4ZRQHq6CaZA2O6NfqjL51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQvIHB%2FbtsKkuH66Ri%2FZ4ZRQHq6CaZA2O6NfqjL51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1422&quot; height=&quot;415&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Pinpoint Transaction&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lRlFN/btsKjuWG68d/8JVCSdk5Jp0yeiTYwLLfZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lRlFN/btsKjuWG68d/8JVCSdk5Jp0yeiTYwLLfZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lRlFN/btsKjuWG68d/8JVCSdk5Jp0yeiTYwLLfZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlRlFN%2FbtsKjuWG68d%2F8JVCSdk5Jp0yeiTYwLLfZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1342&quot; height=&quot;235&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;TaskRejectedException: ExecutorService in active state did not accept task 에러는 Spring에서 비동기 작업을 처리하기 위해 사용하는 ExecutorService가 더 이상 새로운 작업을 수락할 수 없는 상태일 때 발생하는 에러입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;애플리케이션에서 신청이 완료되면 메일을 보내고 있었습니다. 이때 비동기로 보내고 있었는데  동시 요청 1000건 성능 테스트 하던 중에&amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;스레드 풀의 작업 대기열이 가득 차서 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;TaskRejectedException 발생되고 있었습니다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;해결 방법으로는 &lt;br /&gt;1. 스레드 풀(CorePoolSize, MaxPoolSize)과 대기열 크기(QueueCapacity) 크기 증가&lt;br /&gt;2. 부하 분산&lt;br /&gt;3. 메세지 큐 사용&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이 3개 중에서&lt;span&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;메세지 큐 사용&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&amp;nbsp;선택&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;하였습니다. 그 이유는 다음과 같습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;1번 방법은 스레드 풀과 대기열 크기를 증가하여도 대용량 트래픽을 감당하기에는 위험할 수 있습니다. 만약 너무 높게 설정하면 시스템 리소스(CPU, 메모리) 사용량이 과도하게 증가할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;2번 방법은 애플리케이션을&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;오버헤드가&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있으며,&amp;nbsp;리소스&amp;nbsp;낭비와&amp;nbsp;관리&amp;nbsp;비용&amp;nbsp;증가로&amp;nbsp;이어질&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;3번 방법은 기존에 &lt;b&gt;RabbitMQ 사용하고 있어서&lt;/b&gt; 선택하였습니다. Queue 추가하여, 소비자(consumer)가 이를 하나씩 처리하면 스레드 풀에 대한 부담을 줄일 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&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;b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;[출처]&lt;br /&gt;&lt;a href=&quot;https://helloworld.kurly.com/blog/distributed-redisson-lock/#3-%EB%B6%84%EC%82%B0%EB%9D%BD%EC%9D%84-%EB%B3%B4%EB%8B%A4-%EC%86%90%EC%89%BD%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%A0-%EC%88%98%EB%8A%94-%EC%97%86%EC%9D%84%EA%B9%8C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;풀필먼트&amp;nbsp;입고&amp;nbsp;서비스팀에서&amp;nbsp;분산락을&amp;nbsp;사용하는&amp;nbsp;방법&amp;nbsp;-&amp;nbsp;Spring&amp;nbsp;Redisson&lt;/a&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://strong-park.tistory.com/entry/%EC%BF%A0%ED%8F%B0-%EB%B0%9C%EA%B8%89%EC%97%90-%EB%8C%80%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%B2%98%EB%A6%AC-2-MySQL%EC%9D%98-NamedLock-Redis%EC%9D%98-%EB%B6%84%EC%82%B0%EB%9D%BDLettuce-Redisson&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;쿠폰 발급에 대한 동시성 처리&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://techblog.woowahan.com/2631/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MySQL을&amp;nbsp;이용한&amp;nbsp;분산락으로&amp;nbsp;여러&amp;nbsp;서버에&amp;nbsp;걸친&amp;nbsp;동시성&amp;nbsp;관리&lt;/a&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>∙Java &amp;amp; Spring</category>
      <author>coor</author>
      <guid isPermaLink="true">https://coor.tistory.com/67</guid>
      <comments>https://coor.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 25 Oct 2024 15:13:04 +0900</pubDate>
    </item>
  </channel>
</rss>