<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩일탈</title>
    <link>https://bin-repository.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 30 May 2026 22:43:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>피곤핑</managingEditor>
    <image>
      <title>코딩일탈</title>
      <url>https://tistory1.daumcdn.net/tistory/3162408/attach/55d7d298bdb2487988b26d3b42e27ec0</url>
      <link>https://bin-repository.tistory.com</link>
    </image>
    <item>
      <title>HTTP Keep-Alive, 성능 최적화의 핵심일까? 쓸데없는 설정일까?</title>
      <link>https://bin-repository.tistory.com/185</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;웹 성능을 최적화할 때 자주 등장하는 개념 중 하나가 HTTP Keep-Alive다. Keep-Alive는 TCP 연결을 유지해 여러 개의 HTTP 요청을 처리할 수 있도록 도와주지만, 모든 환경에서 무조건 좋은 선택이 되는 것은 아니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;이번 글에서 &lt;b&gt;Keep-Alive의 개념, 동작 원리, 장단점&lt;/b&gt;을 분석해&amp;nbsp;이것이 성능 최적화의 필수 요소인지, 아니면 특정 환경에서는 피해야 할 설정인지 살펴보자.&lt;/blockquote&gt;
&lt;p data-end=&quot;385&quot; data-start=&quot;289&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;385&quot; data-start=&quot;289&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Keep-Alive 가 뭔데?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img.jpg&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JXzxs/btsMkwE4g7E/nmICKps1QQJQOgb73kg4q1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JXzxs/btsMkwE4g7E/nmICKps1QQJQOgb73kg4q1/img.jpg&quot; data-alt=&quot;https://hackernoon.com/http-made-easy-understanding-the-web-client-server-communication-yz783vg3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JXzxs/btsMkwE4g7E/nmICKps1QQJQOgb73kg4q1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJXzxs%2FbtsMkwE4g7E%2FnmICKps1QQJQOgb73kg4q1%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;1024&quot; height=&quot;499&quot; data-filename=&quot;img.jpg&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://hackernoon.com/http-made-easy-understanding-the-web-client-server-communication-yz783vg3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 HTTP 는 요청과 응답이 끝나면 연결을 끊는 방식으로 동작한다.&amp;nbsp; 하지만 Keep-Alive 가 활성화되면 한번 맺은 TCP 연결을 재사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Keep-Alive 가 비활성화된 기본 HTTP 동작은 어떻게 하는거지?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 클라이언트가 서버에 요청을 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 서버가 응답을 반환하고 TCP 연결을 종료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 다음 요청이 오면 새로운 TCP 연결을 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Keep-Alive 가 활성화 되면 어떻게 동작할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 클라이언트가 서버에 요청을 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 서버가 응답을 반환하지만 TCP 연결을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 이후 추가 요청이 같은 연결을 재사용한다.&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;이 방식은 HTTP 1.1 부터 기본적으로 활성화되어 있으며, 수동으로 비활성화 하려면 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Connection: close&lt;/b&gt;&lt;/span&gt; 를 명시해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;설정 방법&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. HTTP 1.0&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본적으로 연결이 요청-응답 후 종료됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Connection: Keep-Alive&lt;/b&gt;&lt;/span&gt; 헤더를 추가하면 지속적인 연결 유지 가능&lt;/p&gt;
&lt;pre id=&quot;code_1739709103132&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET / HTTP/1.0
Connection: Keep-Alive&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;위 헤더가 포함되면 서버는 TCP 연결을 닫지 않고 유지하려고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 HTTP/1.0 에서는 요청이 끝나면 연결이 닫히므로 반드시 명시적으로 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Connection: Keep-Alive&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 추가해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. HTTP 1.1&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본적으로 Keep-Alive 가 활성화됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 연결을 종료하려면&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; Connection: close&lt;/b&gt;&lt;/span&gt; 헤더를 명시해야함&lt;/p&gt;
&lt;pre id=&quot;code_1739709238232&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET / HTTP/1.1
Connection: close&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;oplus; 서버 기준으로 설정하는 방법도 존재한다.&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서도 Keep-Alive 를 관리할 수 있으며, 주요 설정 옵션은 다음과 같다.&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;- Keep-Alive Timeout: 연결을 유지하는 시간 (ex. 5초)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Max Requests: 하나의 연결에서 처리할 수 있는 최대 요청 개수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Buffer Size: 데이터 전송 시 사용되는 버퍼크기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어떻게 연결을 유지하는 걸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keep-Alive 가 활성화되면 클라이언트와 서버는 같은 TCP 연결을 재사용하는데 이를 유지하는 메커니즘은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 클라이언트가 첫 요청을 보낼 때&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 클라이언트가 Connection: Keep-Alive 를 포함하여 HTTP 요청을 보낸다. (HTTP/1.0 인 경우)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버가 이를 받아들이고, 응답 헤더에 Connection: Keep-Alive 를 포함하여 응답한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TCP 연결은 닫히지 않고 계속 유지된다.&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2) 추가 요청을 같은 TCP 연결에서 전송&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 클라이언트는 새로운 HTTP 요청을 보낼 때 기존 TCP 연결을 재사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버는 Keep-Alive 설정이 적용된 경우 같은 연결에서 응답을 보낸다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) Keep-Alive Timeout 시간이 지나면 연결 종료&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 클라이언트가 일정 시간동안 요청을 보내지 않고 타임아웃 시간이 지나면 서버는 연결을 닫는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이후 클라이언트가 새로운 요청을 보내면 다시 새로운 TCP 연결이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;그래서 뭐가 좋은데?&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅&amp;nbsp; 성능 최적화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TCP 연결을 새로 맺는 과정 (3-way handshake) 이 생략되면서 요청-응답 속도가 향상된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 브라우저에서 여러개의 리소스를 요청할 때 (ex. CSS, JS, 이미지) 빠르게 처리가능하다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅&amp;nbsp; 네트워크 및 서버 부하 감소&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TCP 연결을 재사용하므로 불필요한 연결 생성 비용이 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 초당 많은 요청을 처리하는 서버에서 효과적이다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅&amp;nbsp; 페이지 로딩 속도 개선&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Keep-Alive 가 없으면 브라우저가 리소스를 가져올 때마다 새로운 연결을 맺어야 하지만 Keep-Alive 를 사용하면 병렬 다운로드가 더 효율적으로 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그럼 Keep-Alive 의 사용이 무조건 좋은 것 아냐?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Keep-Alive 의 사용이 무조건 좋아보이는데 사용하지 않을 이유가 있을까? 심지어 HTTP/1.1 에서는 Keep-alive 의 사용이 기본설정이다.&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;하지만 우리가 사용하고 개발하는 서비스는 모두 같은 인프라 환경을 가지고 있지 않다. 따라서 다음에 환경에서는 Keep-Alive 의 사용이 비효율적일 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;☑️ 서버 리소스 낭비&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 많은 Keep-Alive 연결을 유지하면 메모리와 CPU 사용량이 증가할 수 있다. 따라서 timeout 값을 적절하게 조정하여 사용해야한다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;☑️ 로드 밸런서와의 충돌 (쿠버네티스 환경 포함)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 인스턴스로 운영되고 있는 환경이 아니라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keep-Alive 가 활성화되면 클라이언트가 항상 같은 서버 인스턴스(=파드) 로 요청을 보낼 가능성이 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 기본적으로 여러 개의 파드로 부하를 분산해야하지만 Keep-Alive 로 인해 특정 인스턴스에 부하가 집중될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위 단점을 개선하고자 한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ timeout 을 짧게 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 로드 밸런서에서 연결을 강제로 끊고 새로운 서버로 라우팅, 쿠버네티스 환경이라면 keep alive 활성화가 되더라도 트래픽이 분산될 수 있도록 아키텍쳐 개선이 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ HTTP/2 사용 (멀티플렉싱)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하는 등의 해결책이 있을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;oplus; 추가적으로 HTTP/2.0 에서 멀티플렉싱에 대해서 좀 더 알아보자&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/1.1 에도 문제점이있다. HTTP/1.1 에서는 한 개의 TCP 연결에서 하나의 요청-응답만 처리할 수 있다. 따라서 여러개의 요청을 동시에 처리하려면 추가적인 TCP 연결을 만들어야한다. 브라우저는 성능을 위해 여러 개의 TCP 연결을 병렬로 사용하지만 이는 오버헤드가 발생하게된다. 따라서 &lt;span style=&quot;color: #1a5490;&quot;&gt;HOL Blocking&lt;/span&gt; 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;* HOL Blocking(Head-of-Line Blocking) : 하나의 요청이 지연되면 같은 연결을 공유하는 다른 요청들도 영향을 받음&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;HTTP/2.0 에서의 멀티플렉싱은 &lt;span style=&quot;color: #1a5490;&quot;&gt;하나의 연결에서 여러개의 요청을 동시처리&lt;/span&gt;한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청과 응답을 스트림 단위로 처리하여 각 요청이 독립적으로 병렬처리된다. 따라서 한 요청이 지연되더라도 다른 요청은 영향을 받지 않는다. 별도의 추가적인 TCP 연결을 만들 필요가 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 각 요청이 독립적인 스트림으로 전송되므로 동시에 응답을 받을 수 있다.!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 HTTP/2.0 환경에서는 Keep-Alive 를 따로 설정할 필요가 없다.&lt;/p&gt;
&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;font-size: 1.62em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;쿠버네티스 환경에서 Keep-Alive 를 사용했을 때 실제로 어떤 일이 일어날 수 있을까?&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;무서운 실무이야기를 해보자면... 이 keep-alive 옵션으로 인해 운영에서 장애를 겪은 적이있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 쿠버네티스 환경에서 운영하고 있고 클라이언트(웹) 으로 부터 요청을 받는 server 가 있고 또 그 요청을 받아서 IDC 에 있는 디비와 connection 하는 내부 서버가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/1.1 을 사용하고 있고 서버와 내부서버들은 같은 클러스터에 존재하고 있다. (server 의 경우도 인스턴스가 여러대 있지만 간략한 설명을 위해 생략했다)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없음-2025-02-16-2232.png&quot; data-origin-width=&quot;2569&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZvpUL/btsMjQjQXvp/i9xPbSlI6PLeYKa8MpYZkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZvpUL/btsMjQjQXvp/i9xPbSlI6PLeYKa8MpYZkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZvpUL/btsMjQjQXvp/i9xPbSlI6PLeYKa8MpYZkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZvpUL%2FbtsMjQjQXvp%2Fi9xPbSlI6PLeYKa8MpYZkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2569&quot; height=&quot;988&quot; data-filename=&quot;제목 없음-2025-02-16-2232.png&quot; data-origin-width=&quot;2569&quot; data-origin-height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0. 클라이언트로 부터 요청을 받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. HTTP/1.1 를 사용하여 기본적으로 Keep-Alive 설정이 켜져 있는 상황이고 timeout 은 30초로 설정되어 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 쿠버네티스 환경에서 부하분산이 제대로 이루어지지않아 내부서버 1 로만 요청이 몰리게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 데이터 베이스와 커넥션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. slow query 혹은 trigger lock 의 이슈로 DB 와 의 커넥션이 길어지게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 하나의 요청이 지연되면서 같은 연결을 공유하는 요청들도 영향을 받게되고 DB 자체의 부하가 생기게 되면서 모든 요청들의 대한 이슈가 생김&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 설상가상으로 클라이언트에서는 요청의 대한 응답이 오지 않았을 경우 30초마다 retry 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. &lt;span style=&quot;color: #1a5490;&quot;&gt;부하분산이 제대로 이루어지지 않아 지속적인 요청과 타임아웃으로 인한 과부하 &amp;amp; 헬스체크 실패로 내부서버1 파드가 죽어버림&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 데이터 베이스 문제였기 때문에 애플리케이션 단에서 바로 해결할 수는 없었지만 keep-Alive 활성화 설정으로 인해 내부 서버 2로의&amp;nbsp; fail over 가 제대로 이루어지지 않았음을 볼 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 사례로 인해 현재 내부서버간에는 일단 keep-alive 옵션을 꺼두고 인프라 개선을 앞두고 있는 상황이다. (언제 개선할지는 모르겠지만ㅠㅠ) 그리고 HTTP/2 멀티플렉싱을 알게되었는데 요것도 한번 검토해볼 예정이다.&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;▶️ 결론&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, Keep-Alive 가 항상 최적의 선택은 아니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쿠버 네티스 환경에서는 Keep-Alive 가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단일 인스턴스에 부하를 집중시킬 가능성&lt;/span&gt;이 있기 때문에 로드 밸런싱과 함께 고려해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #f6e199;&quot;&gt;&lt;b&gt;일반 웹 서버에서는 성능 최적화에 유리하지만 쿠버네티스처럼 부하 분산이 중요한 환경에서는 부작용이 발생할 수 있다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;최신 웹에서는 HTTP/2 를 사용하면 Keep-Alive 설정을 고민할 필요가 없지만 아직 그래도 HTTP/1.1 을 사용하는 환경이 더 많은 것 같다 (우리회사도...)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>네트워크</category>
      <category>HTTP</category>
      <category>http/1.1</category>
      <category>HTTP/2</category>
      <category>keep-alive</category>
      <category>네트워크</category>
      <category>쿠버네티스</category>
      <category>킵얼라이브</category>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/185</guid>
      <comments>https://bin-repository.tistory.com/185#entry185comment</comments>
      <pubDate>Sun, 16 Feb 2025 23:06:00 +0900</pubDate>
    </item>
    <item>
      <title>[책리뷰] 개발자를 위한 커리어 관리 핸드북 - 실리콘밸리 개발자의 소프트 스킬 노하우</title>
      <link>https://bin-repository.tistory.com/184</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9791169212342.jpg&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o34lw/btsL1BmB3Zx/iaCeri8BZil9vsSeOS5ig1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o34lw/btsL1BmB3Zx/iaCeri8BZil9vsSeOS5ig1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o34lw/btsL1BmB3Zx/iaCeri8BZil9vsSeOS5ig1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo34lw%2FbtsL1BmB3Zx%2FiaCeri8BZil9vsSeOS5ig1%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;450&quot; height=&quot;656&quot; data-filename=&quot;9791169212342.jpg&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;도서정보&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000213023182&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;개발자를 위한 커리어 관리 핸드북 (실리콘밸리 개발자의 소프트 스킬 노하우) | 마이클 롭 지음 | 한빛미디어&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1737968517171&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;개발자를 위한 커리어 관리 핸드북 | 마이클 롭 - 교보문고&quot; data-og-description=&quot;개발자를 위한 커리어 관리 핸드북 | 1:1 멘토링하듯 알려주는 커리어 관리 노하우 * 네이버, 배민, 토스, 틱톡, 트위니 등 국내 개발자 10인의 커리어 이야기 수록커리어가 어느 시점에 이르면, 코&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000213023182&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000213023182&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qF8GC/hyX7YhM6o2/ARMMCfV6jk7CZ3y7Hk2imK/img.jpg?width=458&amp;amp;height=668&amp;amp;face=0_0_458_668,https://scrap.kakaocdn.net/dn/EXcoG/hyX4nDxnME/NKtrBw3KZ70fyanuR4hKF1/img.jpg?width=458&amp;amp;height=668&amp;amp;face=0_0_458_668&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000213023182&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000213023182&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qF8GC/hyX7YhM6o2/ARMMCfV6jk7CZ3y7Hk2imK/img.jpg?width=458&amp;amp;height=668&amp;amp;face=0_0_458_668,https://scrap.kakaocdn.net/dn/EXcoG/hyX4nDxnME/NKtrBw3KZ70fyanuR4hKF1/img.jpg?width=458&amp;amp;height=668&amp;amp;face=0_0_458_668');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;개발자를 위한 커리어 관리 핸드북 | 마이클 롭 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개발자를 위한 커리어 관리 핸드북 | 1:1 멘토링하듯 알려주는 커리어 관리 노하우 * 네이버, 배민, 토스, 틱톡, 트위니 등 국내 개발자 10인의 커리어 이야기 수록커리어가 어느 시점에 이르면, 코&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이 책을 선택한 이유에 대해서&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부제목으로 쓰여있는 &lt;b&gt;실리콘밸리 개발자의 소프트 스킬 노하우&amp;nbsp;&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;b&gt;  &lt;/b&gt;면접관이라는 생명체&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 면접이든 아니든 우리의 면접에 참여하는 사람들의 목적은 모두 다르다. 그 목적이 무엇인지 빠르게 파악할수록 우리가 면접에서 해야하는 유일한 일을 빠르게 처리할 수 있다. 바로 면접관들이 우리에게 말을 하게 만드는 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리의 목표는 그들의 목표와 정확하게 같다. &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;서로의 말을 듣고 서로를 이해하는 것이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관들의 성격은 다양하며 각각의 성격마다 버튼이 하나씩 있다. 그 버튼을 누르면 그들은 말하고 싶어할 것이다. 면접관들이 그들의 직무와 회사에 관해 가지고 있는 생각을 파악하기 위한 필수적인 작업이다. 어떤 사람들은 상대적으로 이 버튼을 잘 숨기지만 대부분의 사람들은 버튼을 적어도 하나씩은 드러내고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 책에서는 화난피트의 불편버튼, 수다쟁이의 수다버튼, 시인의 낭송버튼, 그레그는 다알지의 정답버튼, 번지르르한 스티브의 찔러보기 버튼 등 면접관으로 만날 수 있는 여러가지 유형의 사람들의 버튼을 누르는 방법들의 대해 필자가 경험했던 바를 토대로 서술하고 있다.&lt;br /&gt;&lt;br /&gt;생각보다 구체적이고 흥미로운 예시로 주제를 풀어가서 매우 재미있게 읽었다. 해당 챕터를 다 읽은뒤에 실제로 내가 면접관이 되었을 때 나는 어떤 유형일까? 라는 생각을 하게됐다. 아직 면접관으로 참여한 적은 없지만 아마도 나는 구체적으로 질문했을 때 원하는 답을 내어주는 시인 스타일에 가깝지 않을까하는 생각이다.&amp;nbsp;&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  다른 무언가를 위한 공간&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조직 내 사람들과 의사소통하는 것은 사람들 간의 의견을 조율하기 위한 끊임없는 활동이다. &lt;/b&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;지루함이 존재할 시공간을 정의하면 여러분은 다른 모든 순간에 창의적인 잠재력을 발휘할 수 있다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개발자라면 강박적이고 많은 회의로 인해 실무개발을 할 시간이 확보되지않아 늦게까지 야근을 해본 경험들이 있을 것이다. 그리고 그렇게 잦은 회의를 하면서도 누락된 정책들이 생겨 결국 기능의 스펙을 축소하거나 데드라인을 미루게될 수도 있다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;이 장에서는 그럼에도 불구하고 이러한 시간이 팀장 또는 팀원에게 창의적인 잠재력을 발휘할 수 있는 순간이라고 말하고 있다. 이 장에서 나는 탁상공론이라고 느껴졌다. 그래서.. 어떻게 효율적으로 회의를 진행할 수 있는거지? 아직까지 잘 와닿지는 않았다. &lt;br /&gt;&lt;br /&gt;하지만 회의나 1:1 미팅 등을 잘 하지 않는 팀이라면 주기적인 회의에 대한 필요성에 대해서는 아주 잘 설명하고 있으니 읽어보기를 추천한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; ️ 생산성 미니멀리즘 실천&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 여러 가지 생산성 관련 도구들의 장단점에 대해 논하지 않는다. 사용할 생산선 도구는 여러분의 개인적인 취향이나 성격에 맞는 것을 사용하면 된다.&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;이다. '오늘' 목록에 있는 일들 중 완료하지 못한 일들을 스크럽한다. 완료하지 못한 일이 있다면 질문해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;내가 이 일을 왜 완료하지 못했지?&quot;&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;/p&gt;
&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;font-family: 'Noto Serif KR';&quot;&gt;&quot;오늘 아침에만 해도 '오늘' 목록에 있었던 작업이 어떻게 갑자기 아무 상관없는 작업이 된 거지?&quot;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;그래. 지금 당장은 중요하지 않을 수 있어. 하지만 만약에&amp;hellip;&amp;hellip;..&quot;&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;그만하고 지우세요. 중요하지도 않고 재미도 없는 작업에 쓸데없는 미련을 가지느라 37초나 허비했습니다. 정말 중요한 작업이라면 다시 돌아올테니 걱정할 필요가 없다. &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;지우는 것은 고도의 작업 관리 기술&lt;/b&gt;&lt;/span&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;정말 우선순위 정리를 잘 못하는 사람 + 백로그만 123353123123억만개 인 사람으로써 이번장은 큰 도움이 되었다. 이 내용을 읽으면서 효율적으로 우선순위를 관리할 수 있는 방법을 찾아보다가&amp;nbsp;&lt;b&gt;para 노트 정리법&amp;nbsp;&lt;/b&gt;이라는 것을 알게되었는데 나에게 매우 유용해보여 이번 기회에 적용해 보기로 했다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;책을 다 읽고난 뒤,&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여타 자기계발서들이 그러하듯 조금은 지루할 것이라고 생각했다. 하지만 여러가지 유형의 사람들의 대한 톡톡튀는 비유와 사례들로 인해 너무 재미있게 잘 읽었다. 또한, 5년차인 나에게는 꽤나 많이 겪어왔던 유형들이라서 실제 겪었던 경험과 비교해가면서 읽는 재미도 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 3가지 주제외에도 너드 설명서, 빌런 처리하기 등 매우 흥미로운 주제들이 많고 (키워드만 봐도 궁금증이 솟는 ^^) 사례 중심이기 때문에 어렵게 말을 해석하려고 하면서 읽지 않아도 된다. 매우 쉽게 쉽게 읽혔던 책인 것 같다.&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;엄청나게 다양한 유형과 사례들이 나오기 때문에 아직 겪어 보지 않았다면 어느정도 사회의 대한 때가 탄 후 읽어보는 것을 추천한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;추천독자&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어느 정도 사회의 대한 때가 탄 개발자 (3-4년차 이상~?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최소한 2개의 회사를 경험해 본 개발자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어느 정도의 면접을 경험해본 사회초년생 혹은 재직자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;한빛 미디어의 후원으로 책을 받아 작성합니다.&lt;/blockquote&gt;</description>
      <category>독서</category>
      <category>개발자를 위한 커리어 관리 핸드북</category>
      <category>개발자커리어</category>
      <category>마이클 롭</category>
      <category>서평</category>
      <category>책리뷰</category>
      <category>한빛미디어</category>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/184</guid>
      <comments>https://bin-repository.tistory.com/184#entry184comment</comments>
      <pubDate>Mon, 27 Jan 2025 18:59:21 +0900</pubDate>
    </item>
    <item>
      <title>키워드로 다시 마주하는 2024년, 그리고 2025년~</title>
      <link>https://bin-repository.tistory.com/183</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmQgAd/btsLA68HHQF/xv48rSe6srAuTH2fRDVo7K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmQgAd/btsLA68HHQF/xv48rSe6srAuTH2fRDVo7K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmQgAd/btsLA68HHQF/xv48rSe6srAuTH2fRDVo7K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmQgAd%2FbtsLA68HHQF%2Fxv48rSe6srAuTH2fRDVo7K%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;860&quot; height=&quot;484&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이번 2024년의 마지막 날은 강원도 강릉에서 보내고 있다. 평소 여러가지 것들을 혼자서 하는 편인데 한번도 혼자 여행을 떠난 적은 없어서 무계획으로 떠나버렸다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;2024년을 돌아보면서 기억에 남는 키워드 위주로 회고록을 작성해보려 한다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2024년 기억에 남는 키워드&lt;/b&gt;&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;커리어의 대한 고민과 조급함&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;24년 1,2월에는 커리어의 대한 고민이 무지막지하게 컸던 것 같다. 백엔드 개발자로 일을한지 3년 6개월 정도에 접어들었고 조급한 마음을 굉장히 굉장히 많이 가지고 있었다. 그런데 넥스트스텝에서 주관하는 커리어 NEXTSTEP 교육을 듣고 어느정도 해소가 되었다. 사실 이직의 대한 멘토링을 해주는 강의인줄 알았는데 일하는 환경을 바꾸는 것은 쉽지 않기 때문에 그 환경안에서 어떻게 하면 더 &lt;b&gt;잘 &lt;/b&gt;성장할 수 있을지의 대한 인사이트를 가르쳐주는 강의였다. 좋은 개발자 분들도 많이 만날 수 있었다. 연차 상관없이 비슷한 고민들을 하고있구나 하고 조급함을 내려놓기로 했다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대규모 권고사직&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;초여름 쯤 회사에서는 전체 인원의 50% 를 권고사직을 진행했다. 이제 좀 마음 다잡고 하려했더니..? 부서 단위로 권고사직을 진행하는 것이 아닌 팀단위로 과반수의 인원을 삭감했다. 다행인지는 아직도 모르겠지만… 나는 대상이 아니어서 남아있게 됐다. 하루 아침에 1인의 몫 이상의 일을 해야했다. 이 때의 나는 6개정도의 기업에 서류를 넣고 채용 프로세스를 진행하고있었다. 일주일에 과제를 3개를 받기도 하고 면접을 2개씩 보기도 했다. 서류나 과제는 통과했지만 면접에서 번번히 고꾸라지니 면접 지식이 아직 많이 부족하구나 하고 느끼게 됐다.&amp;nbsp;&lt;br&gt;회사에서는 인원 감축으로 봐야할 범위가 늘어났지만 오히려 의사결정을 진행하거나 레거시 프로젝트를 개선하는데는 훨씬 편해져서 오히려 소수의 환경이 나에게 잘맞는건가? 라는 생각을 하게 됐다. 그리고 다시 한번 조급함을 내려놓고 좀 더 처해진 환경에 딥다이브 해보기로 결심하는 계기가 되었다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;이 때 넥스트스텝에서 만들면서 배우는 spring 교육도 같이 들었다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;낭만 강릉 여행&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2024년도의 상반기는 계속 계속 공부만 했던 것 같다. 딱히 놀았던 기억도 없고 매일매일 코딩테스트 문제를 풀거나 강의를 듣거나.. 공부를 하지 않으면 그냥 멍때리거나 잤다. 공부를 해도 응용해보지 않고 계속해서 새로운 것을 공부했다. 에너지가 점점 부족해지는 것을 느꼈고 남는것이 없다는 것을 느꼈다. 오랜만에 친구들과 약속을 잡아 강릉 여행을 갔다.&amp;nbsp;&lt;br&gt;원래도 밤바다를 무지~~~~~~~~ 좋아하는데 정말 인생에서 제일 기억하고 싶은 장면의 베스트 순위에 들정도로 너무너무 예쁜 바다와 밤하늘을 보았다. 친구들과 새벽까지 모래사장에 돗자리를 펴고 누워서 파도 소리를 들으면서 별을 봤다. &lt;b&gt;이게 낭만이지&lt;/b&gt; 라고 생각했다. 그 동안 조급하고 불안하고 걱정했던 순간들이 주마등처럼 스쳐지나갔는데 지금 이 순간 하나로 나는 정말 행복한 사람이라고 생각했다. 계속해서 원하는 것을 이루면 놀아야지 놀아야지 하고 미루기만 하면 안좋은 감정들이 쌓이기만 하고 더 목표를 이루기 힘들어지는 것을 느꼈다.&amp;nbsp;&lt;br&gt;나는 왜 지금에 만족하지 못하고 무엇을 위해서 계속 달리고 있는 건지도 생각해보게됐다.&amp;nbsp;&lt;br&gt;여행을 다녀온 후에는 기술 서적이 아닌 자기계발서나 소설같은 것도 읽어보면서 나를 되살펴보는 시간을 가졌다. 그리고 책을 읽으면서 인상깊었던 구절이 하나있는데 &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;4032&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QuCvg/btsLCFIZ1tU/lkEvRHorqyckqwoeodHqpk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QuCvg/btsLCFIZ1tU/lkEvRHorqyckqwoeodHqpk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QuCvg/btsLCFIZ1tU/lkEvRHorqyckqwoeodHqpk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQuCvg%2FbtsLCFIZ1tU%2FlkEvRHorqyckqwoeodHqpk%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;860&quot; height=&quot;484&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;글또와의 만남&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;상반기에 넥스트스텝에서 만난 분에게 글또의 대한 이야기를 들었다. 사실 이전부터 글또를 알고있었는데 이번에 10기가 마지막 기수이니 꼭 신청해보라고 했다. 사실 처음에는 그냥 블로그에 꾸준히 글만 기고하는 스터디인줄 알았는데 들어가보니 정말 많은 것을 하고 있었다. 세세하게 들어가면 모두가 같은 분야는 아니었지만 그래도 개발 직군이라는 공통 카테고리안에 운동, 요리 등 개발 이외의 것들도 엄청 활발하게 진행하고 있었다. 그래서 너무 놀랐고 또 느꼈다.. 세상엔… 정말.. 열심히 사는 사람이 많구나 ㅠ 또 새로운 세상에 눈을 뜬 기분이었다.&lt;br&gt;나는 항상 배울 점이 많은 사람이나 모임을 갈구하는 편인데 이제서야 안것이 비통(?) 했다. 흑흑&amp;nbsp;&lt;br&gt;글또 안에서는 정말 정말 여러가지 인사이트를 얻지만 그 중에서도 나는 사람의 에너지의 대한 부분도 눈여겨 보고 있다. 매일 매일 꾸준히라는 것을 나는 되게 힘들어하는 편인데 정말 꾸준히 이타적으로 정보를 공유하고 독려해주는 분위기가 나는 정말 좋았다. 계속해서 인연을 이어갔으면 좋겠다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;저속노화&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;건강에 관련된 것들에 엄청나게 관심을 가진 한해였다. 나는 작년(23년)에 피티를 받은이후로 혼자서 꾸준히 웨이트를 하고있는데 식단을 하게되면서 혈당스파이크나.. 저속노화, 치매예방 등에 굉장히 많은 관심을 가지고있다. 그렇다고 해서.. 엄청나게 극단적으로 건강 관리를 하고 있는 것은 아닌데 24년에 꼭 지키려고 했던 것들을 나열해보자면,,,&lt;br&gt;&amp;nbsp;&lt;br&gt;- 일주일에 2번은 웨이트+유산소 운동하기&lt;br&gt;- 아침에는 미지근한 물한잔을 꼭 마시기&lt;br&gt;- 밥먹을 때 야채부터…&lt;br&gt;- 7시간 이상씩 자기 (1시에는 깨어있지않기!) &amp;lt;---- 이게 정말 굉장히 굉장히 어렵다 반은 지키고 반은 못지키고...&lt;br&gt;&amp;nbsp;&lt;br&gt;위와 같은데 나는 원채 유혹에 약하고 스스로에게 당근만을 주는 사람이라 위의 것들도 루틴화하는데 꽤나 힘들었다. 마지막것을 지키기 위해서 새벽 6시에 일어나서 아침에 운동을 갔고 저녁에는 내 공부를 하고 일찍 자려고 노력했다. 한 2~3개월 정도 하니 그래도 이제 얼추 알람없이도 일어날 수 있게됐다! (얏호)&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2025년 액션 아이템&lt;/b&gt;&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;운전&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;계속해서 미뤄오던 것이 있는데 바로 운전하기이다. 면허를 딴지는 10년이 넘었는데 장롱속에 박아둔지도 10년이 넘었다. 이제는... 정말 미룰수 없다는 생각이 들었다. 25년 목표는 차 렌트해서 옆에 누구든 태우고 강화도 찍고 오기!&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;책읽기&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;어릴 때 이후로 책을 정말 정말 안읽고 있었는데 (1년에 1권?) 24년 하반기부터 조금씩 책을 읽고있다. 25년엔 두 달에 1권씩은 읽으려고 한다. 그리고 아래 읽다만 책들도 꼭 완독해야지.&lt;br&gt;(글또에서 책읽어또 라는 소모임안에 들어가있는데 흥미로워보이는 책들이 많아서 후에 조금 더 리스팅해보도록 하겠다!)&lt;br&gt;&amp;nbsp;&lt;br&gt;- 가상 면접 사례로 배우는 대규모 시스템 설계 기초 2 (앞부분 읽다가 말았음)&lt;br&gt;- JVM 밑바닥까지 파헤치기 (진행중)&lt;br&gt;- 오브젝트 (중간까지 읽다가 말았음)&lt;br&gt;- 도둑맞은 집중력 (앞부분 읽다가 말았음)&lt;br&gt;- 인간관계론 (앞부분 읽다가 말았음)&lt;br&gt;- 예민함이란 무기 (앞부분 읽다가 말았음)&lt;br&gt;- ….&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;요가하기&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;24년에 기억에 남았던 키워드 중에 &lt;b&gt;저속노화&amp;nbsp;&lt;/b&gt;가 있는데.. 이 저속노화의 조건중에는 유연함도 있다는 영상을 보고 25년에는 헬스도 헬스지만 요가에 도전해보기로 마음먹었다. 그리고 이건 되게 인상깊어서 아직까지 기억하고 있는건데 몇년전에 세상에이런일이가 아직 방영하던시절… 엄청 건강하게 사시는 99살(?) 할머니가 나오셨는데 엄청나게 유연하셨던걸로 기억한다. 그래서 그때부터 건강 = 유연함 요런 연결고리가 내 머릿속에 자연스레 생겼던 것 같다. 그냥 요가하기로 끝내면 흐지부지 될 것 같으니 &lt;b&gt;다리찢기 90도 이상되기!&lt;/b&gt; 로 목표를 정해본다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;원래같으면 내년 목표로 뭐 좋은회사가기 이런 것들을 적었을테지만 24년은 내려놓는 한해가 되었기 때문에 딱히 적지는 않겠다.&amp;nbsp;&lt;br&gt;내려놨다고 해서 좋은회사가기! 를 내려놓은 것은 아니지만 내 환경안에서부터 스스로 성장할 수 있는 힘을 기르는게 우선이라는 생각이 들었다. 그렇게 되면 원하는 회사도 자연스럽게 가게되지 않을까? 24년 막바지에 이르러서는 5년차 개발자가 되었는데 내년에는 좀더 여유있는 한계를 두지 않고 성장하는 개발자가 되고싶다 ~!&lt;br&gt;&amp;nbsp;&lt;br&gt;2025년 나자신 화이팅! 올해도 잘 지냈다~&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>나의 이야기 &amp;amp; 회고</category>
      <category>강릉</category>
      <category>개발자</category>
      <category>글또</category>
      <category>낭만회고</category>
      <category>회고</category>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/183</guid>
      <comments>https://bin-repository.tistory.com/183#entry183comment</comments>
      <pubDate>Tue, 31 Dec 2024 19:09:39 +0900</pubDate>
    </item>
    <item>
      <title>JVM 에서 지연시간 최적화 하기</title>
      <link>https://bin-repository.tistory.com/182</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;가비지 컬렉터는 주기적으로 JVM의 heap 메모리를 점검하여 스택에서 참조되지 않는 객체를 메모리에서 해제하는 장치이다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;JVM (자바 가상 머신) 에는 여러가지 가비지 컬렉터가 존재한다.&lt;br /&gt;그 여러가지 컬렉터들 중에서 최근 많이 사용되는 가비지컬렉터(GC) 는 무엇이고 어떤 지표를 가지고 가비지컬렉터(GC) 를 선택해야할 지 알아보자.&lt;br /&gt;&lt;br /&gt;이 글에서는 가비지 컬렉터가 어떤 것인지 자세히 설명하기보다는 저지연 컬렉터의 특징과 동작원리에 대해서 설명하고 있기 때문에 가비지 컬렉터가 어떤 것인지 최소한의 개념은 가진 상태로 읽는 것을 추천한다.&amp;nbsp;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;가비지 컬렉터를 측정하는 지표들에는 무엇이 있을까?&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 처리량&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리량은 애플리케이션이 실제 작업(유용한 계산)을 수행하는 시간의 비율을 의미한다. 처리량 같은 경우 CPU 스펙에 의존할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIs69U/btsLp8e7e6f/kxziFLTKSxBrsOWCvc9yg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIs69U/btsLp8e7e6f/kxziFLTKSxBrsOWCvc9yg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIs69U/btsLp8e7e6f/kxziFLTKSxBrsOWCvc9yg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIs69U%2FbtsLp8e7e6f%2FkxziFLTKSxBrsOWCvc9yg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;105&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2. 지연시간&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연 시간은 가비지 컬렉션으로 인해 애플리케이션이 중단되는 시간(Stop-the-World 시간)을 의미한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 메모리 사용량&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 사용량은 애플리케이션이 사용하는 메모리의 양과 가비지 컬렉션을 통해 확보한 메모리의 양을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에서도 &lt;b&gt;지연시간&lt;/b&gt;의&amp;nbsp;중요성이 커지고 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;왜 지연시간의 중요성이 커졌을까? 지연시간이 뭐길래?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;0dcb6beac2a59588571c6127e7065575.jpg&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5RGPH/btsLrW5Deec/leuuojJJvrC3Lz7FWDCkrk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5RGPH/btsLrW5Deec/leuuojJJvrC3Lz7FWDCkrk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5RGPH/btsLrW5Deec/leuuojJJvrC3Lz7FWDCkrk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5RGPH%2FbtsLrW5Deec%2FleuuojJJvrC3Lz7FWDCkrk%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;860&quot; height=&quot;645&quot; data-filename=&quot;0dcb6beac2a59588571c6127e7065575.jpg&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 책상이 더 이상 집중하기에 방해될 정도로 어지럽다고 해보자. 그러면 우리는 책상정리를 할 것이다. 만약 과제 중이었다고 한다면 책상 정리를 하는 시간동안은 과제를 하지 못하게 된다. 그리고 더러움의 정도에 따라서 책상 정리하는 시간이 오래걸리게 될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 걸리는 청소시간을 지연시간이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어가 발전하면서 메모리를 좀 더 사용하는 건 큰 문제가 되지 않는다. 하지만 &lt;b&gt;메모리가 커지면 당연히 지연시간에는 악영향&lt;/b&gt;을 준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이 지연시간을 줄이는 것에 대한 중요성이 최근에 많이 커지게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연시간의 단축의 중요성이 커지게 되면서 &lt;b&gt;최근 자바 버전에서 제공하고 있는 가비지 컬렉터는 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;저지연 가비지 컬렉터&lt;/span&gt;이다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;저지연 가비지 컬렉터란 뭘까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정량적인 기준으로 정의하자면 일시중단시간(STW: stop-the-world) 이 10ms 이내로 유지하는 가비지 컬렉터를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저지연 가비지 컬렉터 중 하나인 셰넌도어 (초록색) 을 보면 다른 컬렉터들에 비해 상당히 latency(지연시간) 가 낮다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCIbJS/btsLsNtyxoM/3BXKMDl4zoECr2SUcEEFVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCIbJS/btsLsNtyxoM/3BXKMDl4zoECr2SUcEEFVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCIbJS/btsLsNtyxoM/3BXKMDl4zoECr2SUcEEFVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCIbJS%2FbtsLsNtyxoM%2F3BXKMDl4zoECr2SUcEEFVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1500&quot; height=&quot;622&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;저지연 가비지 컬렉터의 종류를 알아보자.&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 셰넌도어&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차세대 저지연 가비지 컬렉터이다. 셰넌도어보다는 아래에서 설명할 ZGC 를 더 많이 알고있을텐데 셰넌도어는 오라클이 아닌 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Red Hat 에서 개발하고 OpenJDK 12에 출시되었다.&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: #212529; text-align: start;&quot;&gt;간단한 동작원리를 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yka7a/btsLszoFYM5/UVa6UsBkiOq459W7zzQY31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yka7a/btsLszoFYM5/UVa6UsBkiOq459W7zzQY31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yka7a/btsLszoFYM5/UVa6UsBkiOq459W7zzQY31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYka7a%2FbtsLszoFYM5%2FUVa6UsBkiOq459W7zzQY31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;204&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 최초 표시 (init mark)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- GC 가 시작되기전 루트 객체를 표시한다. 이 때 짧은 STW 를 동반한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 동시표시 (concurrent mark)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 힙의 객체 그래프를 탐색하여 사용중인 객체를 식별한다. &lt;b&gt;이 때 애플리케이션 실행 스레드와 동시에 수행된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 최종표시 (final mark)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 2번의 단계에서 애플리케이션 실행 중 변경된 객체를 다시 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 동시청소 (concurrent clean up)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 마킹 작업에서 식별된 객체를 회수할 준비를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 동시이주 (concurrent evaluation)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- &lt;b&gt;메모리 단편화를 줄이고, 가용 공간을 최적화한다. 이 때 brooks forwarding pointer 방식을 사용한다.&lt;/b&gt; (아래에서 설명)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 최초참조갱신 (init update refs)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 5번 단계에서 이동된 객체의 대한 참조를 업데이트 하기위한 초기화 단계를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 동시참조갱신 (concurrent update references)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- &lt;b&gt;애플리케이션 실행과 함께 참조를 업데이트 한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 최종참조갱신 (final update references)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 동시청소 (concurrent cleanup)&lt;/p&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;주요 특징에는 힙의 세대간 이동을 하면서도 긴 지연시간을 피하는 것에 있다.&amp;nbsp;&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;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;셰넌도어의 주요 특징&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[연결행렬]&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xd1KE/btsLriH8QxW/K17dRf2PxiSRxR0sMHHDxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xd1KE/btsLriH8QxW/K17dRf2PxiSRxR0sMHHDxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xd1KE/btsLriH8QxW/K17dRf2PxiSRxR0sMHHDxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxd1KE%2FbtsLriH8QxW%2FK17dRf2PxiSRxR0sMHHDxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;355&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 행렬은 힙의 객체 간 참조를 기록하는 데이터 구조이다. &lt;b&gt;Region 기반 메모리 관리&lt;/b&gt;와 결합되어 동작한다. 회수할 객체의 마킹단계에서 연결행렬을 사용하여 객체&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 연결 행렬은 참조 그래프의 &lt;b&gt;정확성&lt;/b&gt;을 유지하기 위해, Stop-the-World 구간에서의 Final Mark 단계와 병행으로 실행되는 Evacuation 단계에서 중요한 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 객체의 위치가 변경되더라도 연결 행렬을 통해 모든 참조가 올바르게 업데이트되도록 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[&lt;b&gt;forwarding pointer 방식&lt;/b&gt;]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 사용자 프로그램이 옛 객체가 저장된 메모리 공간에 접근하려 하면 메모리 보호 트랩이 발동하여 미리 설정해둔 예외 처리기가 실행되고 이 처리기에서 복사된 새 객체를 이용하게 했다. 이는 운영체제의 지원 없이는 사용자 모드와 커널 모드를 수시로 전환해야해서 비용이 많이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰넌도어는 메모리 보호 트랩 대신에 포워딩 포인터를 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;1202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zeDKJ/btsLqBIcfwh/Rn6sLfSdtNa9dfeqXuand1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zeDKJ/btsLqBIcfwh/Rn6sLfSdtNa9dfeqXuand1/img.png&quot; data-alt=&quot;최근 개선을 통해 헤더 안에 포워딩 포인터를 포함시켰으나 그림에서는 이해하기 쉽도록 분리했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zeDKJ/btsLqBIcfwh/Rn6sLfSdtNa9dfeqXuand1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzeDKJ%2FbtsLqBIcfwh%2FRn6sLfSdtNa9dfeqXuand1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;312&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;1202&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최근 개선을 통해 헤더 안에 포워딩 포인터를 포함시켰으나 그림에서는 이해하기 쉽도록 분리했다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 원래의 객체 레이아웃 구조 상단에 참조 필드를 하나 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;concurrent evaluation 이 아닌 경우 참조 필드가 객체 자기 자신을 가리킨다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우회하는 방식의 구조면에서 포워딩 포인터는 몇몇 초기 자바 가상머신이 사용하던 핸들 방식과 비슷하다고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점은 우회하여 객체에 접근하는 방식은 오버헤드가 결국 각 객체 모두에 더해진다. (옛객체, 새객체 모두 수정이 필요함)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 실행 중 객체 위치 찾기는 수시로 일어나므로 실행 시간에 무시할 수 없는 비용을 치뤄야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점으로는 포인터의 값 하나만 수정하면 끝이다. 이 장점이 매우 커서 단점을 무마시킨다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 옛 객체가 아직 회수되지 않았더라도 기존 참조를 통해 자동으로 새로운 객체로 포워딩 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaXq2N/btsLsMnT1EB/NesTzsr4mamgE1MJbN2rx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaXq2N/btsLsMnT1EB/NesTzsr4mamgE1MJbN2rx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaXq2N/btsLsMnT1EB/NesTzsr4mamgE1MJbN2rx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaXq2N%2FbtsLsMnT1EB%2FNesTzsr4mamgE1MJbN2rx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1476&quot; height=&quot;660&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방법은 데이터 읽기에는 문제가 없지만 데이터 쓰기에서 문제가 발생한다. &lt;b&gt;반드시 새로 복사된 객체에만 써야한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 시나리오를 상상해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. GC 스레드가 객체의 복사본을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용자 스레드가 객체의 필드를 덮어쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. GC 스레드가 옛 객체의 포워딩 포인터 값을 복사본의 주소로 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 작업이 아무런 보호 장치 없이 1번과 3번 사이에 수행된다면 사용자 스레드는 옛 객체를 변경하게 된다. 따라서 포워딩 포인터에 접근하는 동작을 동기화해야한다. 즉, GC 스레드와 사용자 스레드 중 하나만 포워딩 포인터에 접근할 수 있고 다른 스레드는 순서를 기다려야한다. 셰넌도어는 &lt;a title=&quot;CAS&quot; href=&quot;https://ko.wikipedia.org/wiki/%EB%B9%84%EA%B5%90%EC%99%80_%EA%B5%90%ED%99%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CAS&lt;/a&gt; 기법을 사용하여 concurrent evaluation 중에도 객체 접근시 문제가 없도록 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[SATB: Snapshot-at-the-beginning]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SATB 는 가비지 컬렉터에서 사용하는 마킹알고리즘이고 &quot;마킹이 시작될 때의 메모리 상태를 스냅샷으로 찍어둔다&quot; 라는 의미를 가지고 있다. concurrent marking 단계가 애플리케이션 스레드와 동시에 진행될 수 있게 하는 중요한 매커니즘이며, 이를 통해 애플리케이션의 작동을 최대한 방해하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SATB 를 사용하면 가비지 컬렉터는 객체가 라이브인지 아닌지를 결정하는 동안에도 애플리케이션이 계속 실행될 수 있다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[LRB: Load-Reference-Barrier]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰넌도어에서 라이브 객체를 옮기는 동안 발생할 수 있는 문제를 처리하는 매커니즘이다. 셰넌도어는 라이브 객체를 다른곳으로 이동시킬 수 있으며 이는 힙 공간을 최적화하는데 도움을 준다. 그러나 이로 인해 애플리케이션 스레드가 옮겨진 객체에 접근하는 문제가 있을 수 있다. (위에서 언급한 데이터 쓰기시 객체 접근 문제)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 셰넌도어는 LRB 를 사용하고 애플리케이션 스레드가 객체에 접근할 때마다 LRB 가 이 객체에 옮겨진 객체인지 확인하고 만약 그렇다면 이동된 새 위치를 반환한다. 이를 통해 셰넌도어는 애플리케이션의 동작을 방해하지 않으면서도 라이브 객체를 안전하게 이동시킬 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. ZGC&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ZGC 또한 최소한의 지연시간을 목표로 설계되었다. ZGC 는 java11 부터 도입되었으며 대규모 힙 메모리를 다룰 수 있으면서도 짧은 중단시간을 보장하는 것이 특징이다.&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;1. Root Scanning (STW)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- GC 작업의 시작 단계로 루트객체를 식별한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. concurrent Marking&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 마킹 단계에서는 살아있는 객체를 식별한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 루트 객체에서 시작하여 객체 그래프를 탐색하면서 살아있는 객체를 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;- 애플리케이션과 동시에 실행된다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 이 때 객체의 상태는 &lt;b&gt;Colored Pointer&lt;/b&gt; 를 통해 관리된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Prepare for Relocation (STW)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 재할당을 준비하기 위해 이동 대상 객체와 관련된 정보를 수집한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. concurrent Relocation&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 살아있는 객체를 새로운 Region 으로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 객체가 이동된 후 기존의 참조는 &lt;b&gt;Read Barrier&lt;/b&gt; 를 통해 업데이트된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. concurrent Compaction&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 메모리 단편화를 방지하기 위해, 재배치 후 남은 빈공간을 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- &lt;b&gt;compacting 작업은 애플리케이션 실행과 동시 실행된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Post GC cleanup&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- GC 작업이 완료된 후 사용하지 않는 Region 을 반환하거나 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(ZGC 자세한 동작원리는 &lt;a title=&quot;Naver D2 기술블로그&quot; href=&quot;https://d2.naver.com/helloworld/0128759&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Nave D2 기술블로그&lt;/a&gt;에 잘 정리되어있다. (설명이 어렵긴 하다 ㅠㅠ))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[Colored Pointer]&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ncTIy/btsLqXc5iX3/93YoV4vCjNnyiaJUhga3D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ncTIy/btsLqXc5iX3/93YoV4vCjNnyiaJUhga3D1/img.png&quot; data-alt=&quot;https://hub.packtpub.com/getting-started-with-z-garbage-collectorzgc-in-java-11-tutorial/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ncTIy/btsLqXc5iX3/93YoV4vCjNnyiaJUhga3D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FncTIy%2FbtsLqXc5iX3%2F93YoV4vCjNnyiaJUhga3D1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;280&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://hub.packtpub.com/getting-started-with-z-garbage-collectorzgc-in-java-11-tutorial/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ZGC 는 객체를 찾아낸 뒤, 마킹하고, 재배치하는 등의 작업을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이러한 알고리즘 방식은 64bit 메모리 공간을 필요로 하기 때문에 32bit 기반의 플랫폼에서는 사용이 불가능하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;64bit 의 공간에서 18bit 의 미사용공간, 42bit 의 객체의 참조 주소와 총 4bit 공간을 차지하는 4개의 color pointer 가 존재하고 이 bit 들을 &lt;b&gt;meta bits&lt;/b&gt; 라고 부른다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Finalizable : 이 포인터가 마킹되어있다면 살아있지 않은 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Remapped : 해당 객체의 재배치 여부를 판단하는 포인터이며, 이 bit 의 값이 1이라면 최신 참조 상태임을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Marked 0, Marked 1 : 해당 객체가 Live 상태인지 확인하는 여부이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;왜 색상(colored)으로 비유할까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터의 추가된 정보가 객체의 상태를 나타내며 이 상태를 색상으로 비유해서 설명하는 것이 직관적이기 때문이다. 각 상태를 특정 색상으로 구분된다고 상상하면 GC 가 객체의 상태를 빠르게 식별하고 적절한 작업 (마킹, 재배치) 등을 수행할 수 있다는 점에서 유래했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;어떤 특징으로 인해서 지연시간을 단축하는걸까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- bit 의 구분을 통해 빠르게 객체 상태를 즉시 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 별도의 연산 없이 포인터의 특정 비트를 읽어 상태를 확인하기 때문에 상태 확인 속도가 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 애플리케이션 스레드가 동시에 객체를 참조하고 상태를 변경하더라도 충돌없이 동작할 수 있다. (스레드의 중단없이 수행한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 객체상태를 확인하는데 드는 비용을 줄임으로써, GC 작업이 더 빠르게 완료되므로 STW 시간을 줄이고 작업의 성능을 향상시킨다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[Load Barrier]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Load Barrier 는 애플리케이션이 객체 참조를 읽으려고 할 때 GC 가 개입하여 특정 작업을 수행하는 매커니즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 참조를 읽을 때 객체가 이동되었는지 확인하기 위해 참조 포인터의 상태 (Colored Pointer) 를 검사한다. 그리고 만약 객체가 이동된 상태라면 최신 위치로 참조를 업데이트하고 애플리케이션 스레드가 항상 올바른 객체를 참조하도록 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;동작 순서를 알아보자 (예시)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 객체 참조 읽기 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 애플리케이션 스래드가 특정 객체를 참조하려고 할 때 Load Barrier 가 트리거 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 객체 상태 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- colored pointer 의 비트를 확인하여 객체가 이동되었는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 참조 갱신 또는 무시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 객체가 이동된 경우 새로운 위치로 참조를 갱신한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 객체가 이동되지 않은 경우 추가 작업 없이 기존 참조를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 애플리케이션으로 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 최신 참조를 애플리케이션에게 제공해서 작업을 계속 진행한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정리 (사용하는 이유)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;지연 시간의 단축&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- GC 작업 중 객체를 이동하거나 마킹하는 동안에도 애플리케이션이 객체를 안전하게 참조할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 이를 통해 STW 시간을 최소화하고 초 저지연을 실현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;Concurrent GC 지원&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- GC 작업 (마킹, 재배치 등) 이 애플리케이션 스레드와 병렬로 실행될 수 있도록 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 객체 참조가 실시간으로 업데이트 되므로, 객체 이동으로 인한 충돌이나 오류를 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;대규모 힙 메모리 지원&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- ZGC 는 테라바이트 규모의 힙 메모리를 지원하며 Load Barrier 를 통해 객체 이동 비용을 최소화하여 대규모 메모리에서도 효율적으로 동작한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;셰넌도어와 ZGC 의 공통점과 차이점은 무엇일까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 저지연 가비지 컬렉터인 셰넌도어와 ZGC 에 대해서 알아보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰넌도어와 ZGC 는 둘다 JVM 에서 동작하며 애플리케이션의 지연시간을 최소화 하기위해서 설계되었다. 두 GC 는 concurrent GC 방식을 채택하여 대부분의 작업을 애플리케이션 스레드와 병렬로 수행한다는 공통점을 가지고 있다. 하지만 내부 동작 방식과 설계 철학에서 중요한 차이점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;공통점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. concurrentGC 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 저지연 설계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Region 기반 메모리 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 단편화를 줄이고 메모리 사용을 최적화하는데 도움을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 대규모 힙 메모리 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 참조 갱신 개념&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 두 GC 는 참조 갱신을 위해 Barrier 기술을 사용하며 객체 재배치(Relocation)와 관련된 작업을 지원한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;차이점&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 203px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.8915%; height: 19px; text-align: center;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%; height: 19px; text-align: center;&quot;&gt;셰넌도어&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 19px; text-align: center;&quot;&gt;ZGC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 58px;&quot;&gt;
&lt;td style=&quot;width: 20.8915%; height: 58px; text-align: left;&quot;&gt;주요 목표&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%; height: 58px;&quot;&gt;&lt;b&gt;Low Latency + compact 힙:&lt;/b&gt;&lt;br /&gt;낮은 지연시간을 유지하면서 메모리 단편화도 줄이는 것이 목적&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 58px;&quot;&gt;Ultra Low Latency:&lt;br /&gt;초 저지연을 달성하는데 집중하며, 메모리 단편화 해결은 덜 강조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.8915%; height: 18px; text-align: left;&quot;&gt;Relocation 방식&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%; height: 18px;&quot;&gt;살아있는 객체를 동일 Region 내에서 압축해서 단편화를 줄임&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 18px;&quot;&gt;객체를 다른 Region 으로 이동하여 메모리를 정리한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.8915%; height: 18px; text-align: left;&quot;&gt;Barrier 종류&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%; height: 18px;&quot;&gt;Load-Reference-Barrier :&amp;nbsp;&lt;br /&gt;&lt;b&gt;write 시점&lt;/b&gt;에 참조를 업데이트 하는 Barrier 를 사용&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 18px;&quot;&gt;Load Barrier (Read Barrier) :&amp;nbsp;&lt;br /&gt;Read 시점에 참조 상태를 확인하고 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.8915%; height: 18px; text-align: left;&quot;&gt;Colored Pointer&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%; height: 18px;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 18px;&quot;&gt;객체 상태를 컬러 포인터로 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.8915%; height: 18px; text-align: left;&quot;&gt;메모리 단편화 관리&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%; height: 18px;&quot;&gt;&lt;b&gt;높은 우선순위&lt;/b&gt;: 힙 내 단편화를 줄이기 위해 compaction 을 적극적으로 수행&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 18px;&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: 20.8915%; height: 18px; text-align: left;&quot;&gt;STW 시간&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%; height: 18px;&quot;&gt;수 밀리초(ms) 단위로 매우 짧음&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 18px;&quot;&gt;수 밀리초(ms) 로 매우 짧으나 셰넌도어보다 더 짧은 경우가 많음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.8915%; height: 18px; text-align: left;&quot;&gt;Heap 크기 지원&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%; height: 18px;&quot;&gt;수십 GB 에서 수백 GB 힙 메모리에 최적화&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 18px;&quot;&gt;테라바이트(TB) 단위의 대규모 힙 메모리를 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20.8915%; height: 18px; text-align: left;&quot;&gt;GC 알고리즘&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%; height: 18px;&quot;&gt;&lt;b&gt;Incremental Compaction&lt;/b&gt; :&amp;nbsp;&lt;br /&gt;힙 내부 단편화를 줄이며 일부 작업은 점진적으로 수행&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 18px;&quot;&gt;&lt;b&gt;Region Relocation&lt;/b&gt;: 객체를 이동시켜 단편화를 해결하며 대부분의 작업을 concurrent 로 수행함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.8915%; text-align: left;&quot;&gt;적합한 애프리케이션&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%;&quot;&gt;단편화가 문제될 가능성이 있는 중간 크기의 애플리케이션&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%;&quot;&gt;초저지연이 필요한 초대형 애플리케이션&amp;nbsp;&lt;br /&gt;(ex. 금융시스템, 실시간 대규모 애플리케이션)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.8915%; text-align: left;&quot;&gt;JVM 버전&lt;/td&gt;
&lt;td style=&quot;width: 37.8681%;&quot;&gt;JDK 12 에 도입&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%;&quot;&gt;JDK 11 에 실험적으로 도입 후 JDK 15 에서 정식으로 제공&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 JVM 에서 지연시간의 단축이 중요한 이유와 저지연 가비지 컬렉터, 그리고 종류를 알아보았다.&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;셰넌도어와 ZGC 는 JVM 의 발전과 함께 애플리케이션 성능 최적화를 위한 혁신적인 접근 방식을 보여준다. 두 GC 모두 저지연이라는 목표는 동일하지만 이를 달성하기 위한 철학과 세부 구현에서 뚜렷한 차이를 보인다.&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;특히 ZGC 는 테라바이트 단위의 힙 메모리를 지원하고도 극도로 낮은 STW 시간을 유지하는 점에서 매우 놀랍다. 아직까지 실무에서는(내 서비스 ㅎㅎ) G1 GC 로도 충분히 관리되고 있는데 ZGC 의 장점을 부각시킬 대규모 시스템도 운영하고 적용해보고 싶다.&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;기술의 발전이 GC 같은 복잡한 분야에서도 눈에 띄게 계속해서 진행되고 있다는 점이 매우 흥미로웠다. 세부 구현에 대해서 완전히 이해하기에는 아직 겉만 공부한 수준이지만 각 GC 의 주요 특징에서 객체를 효율적으로 관리하기 위한 방법들도 재미있었다.&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>Kotlin &amp;amp; Java</category>
      <category>colored pointer</category>
      <category>GC</category>
      <category>jvm gc</category>
      <category>load barrier</category>
      <category>ZGC</category>
      <category>가비지 컬렉터</category>
      <category>셰넌도어</category>
      <category>저지연 가비지 컬렉터</category>
      <category>지연시간</category>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/182</guid>
      <comments>https://bin-repository.tistory.com/182#entry182comment</comments>
      <pubDate>Sat, 21 Dec 2024 23:55:15 +0900</pubDate>
    </item>
    <item>
      <title>매개변수를 통해 JVM 메모리 할당과 회수 전략에 대해서 알아보자!</title>
      <link>https://bin-repository.tistory.com/175</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 대상&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;- JVM (자바가상머신) 에서는 어떻게 메모리를 할당하고 회수하는지 직접 눈으로 확인하고 싶은 사람&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가상머신 매개변수를 사용해서 테스트 해보고 싶은 사람&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본적인 GC 관련된 키워드는 알고있는 사람 (ex. Old Generation, &amp;hellip;.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 미래의 나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 개요&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;&amp;nbsp;최근 회사에서 싱글코어로 배포되어 있던 서버의 코어수를 증량하면서 SerialGC -&amp;gt; G1GC 를 사용하게 되었다. 그에 따라 메모리 회수가 잘 이루어지는지 모니터링을 하고 있는데 각자의 공간에 메모리가 어떻게 할당되고 회수를 하는지 직접 확인하고 싶어졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;마침 최근 읽고 있는 &lt;a title=&quot;JVM 밑바닥까지 파헤치기&quot; href=&quot;https://product.kyobobook.co.kr/detail/S000213057051&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JVM 밑바닥 까지 파헤치기&lt;/a&gt; 라는 책에서 다루고 있어 정리하고 복기할겸 글로 남기는 것이 의미있을 것 같았다. 따라서 이번 글에서는 가장 기본적인 메모리 할당 정책을 설명하고 코드를 이용해 검증해볼 것이다. 결과를 쉽게 해석하고 이해할 수 있도록 메모리 할당과 회수는 시리얼 컬렉터의 전략을 따라서 진행해보려고 한다. 운영환경에서는 일반적이지 않지만 목표는 '&lt;b&gt;분석 방법 익히기&lt;/b&gt;' 이므로 가장 단순한 SerialGC 를 예로 살펴보겠다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 검증&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. 객체는 먼저 에덴에 할당된다.&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대부분의 경우 객체는 신세대의 에덴에 할당되고 에덴의 공간이 부족해지면 가상머신은 마이너 GC 를 시작한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핫스팟 가상머신의 &lt;/span&gt;&lt;b&gt;-Xlog:gc* &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매개변수는 가비지 컬렉션시 메모리 회수 로그를 출력하고, 프로세스 종료시 메모리 각 영역의 할당 내용을 출력한다. 실전에서는 컬렉터 로그를 파일로 출력하여 전문 분석 도구로 분석하기도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 &lt;b&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-Xlog:gc*&lt;/span&gt; &lt;/span&gt;&lt;/b&gt;매개변수를 이용해서 어떻게 메모리를 할당하는지 로그를 통해 눈으로 보자!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 소스코드의 &lt;b&gt;testAllocation()&lt;/b&gt; 메서드는 크기가 2MB 인 객체 3개와 4MB 인 객체 1개를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730205973551&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class JvmTest {
  private static final int _1MB = 1024 * 1024;

  public static void testAllocation() {
    byte[] alloc1, alloc2, alloc3, alloc4;
    alloc1 = new byte[2 * _1MB];
    alloc2 = new byte[2 * _1MB];
    alloc3 = new byte[2 * _1MB];
    alloc4 = new byte[4 * _1MB]; // 마이너 GC 발생
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 컴파일 한 뒤 가상 머신 매개변수를 붙여 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730206131847&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;➜  jvm javac JvmTest.java 
➜  jvm java -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -Xlog:&quot;gc*&quot; JvmTest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 java21 버전에서&lt;b&gt; -Xlog:&quot;gc*&quot;&lt;/b&gt; 와 같이 값에 &lt;b&gt;&quot;&quot;&lt;/b&gt; 를 붙여줘야 실행이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lD9Uf/btsKoP0vl9d/tFKvfKbEEdDPohQaI7sAlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lD9Uf/btsKoP0vl9d/tFKvfKbEEdDPohQaI7sAlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lD9Uf/btsKoP0vl9d/tFKvfKbEEdDPohQaI7sAlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlD9Uf%2FbtsKoP0vl9d%2FtFKvfKbEEdDPohQaI7sAlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;323&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;-XX:+UseSerialGC&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;-Xms20M, -Xmx20M, -Xmn10M&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 런타임 힙 크기를 20MB 로 제한한다. 확장은 불가하고 10MB 는 신세대에, 나머지 10MB 는 구세대에 배정된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;-XX:SurvivorRatio=8&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;신세데의 에덴과 생존자 공간 비율을 8:1 로 설정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과는 다음과 같다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) alloc4 할당 직전의 힙 상황&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jvm1.png&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFZZ9L/btsKT53okQN/cXIczgVD4KuDuCOshDqfw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFZZ9L/btsKT53okQN/cXIczgVD4KuDuCOshDqfw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFZZ9L/btsKT53okQN/cXIczgVD4KuDuCOshDqfw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFZZ9L%2FbtsKT53okQN%2FcXIczgVD4KuDuCOshDqfw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;816&quot; height=&quot;307&quot; data-filename=&quot;jvm1.png&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732445591708&quot; class=&quot;markdown&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;[0.036s][info][gc,heap,exit]   eden space 8192K,  51% used [0x00000007fe800000, 0x00000007fec14830, 0x00000007ff000000)
[0.036s][info][gc,heap,exit]   from space 1024K,   6% used [0x00000007ff100000, 0x00000007ff110b68, 0x00000007ff200000)
[0.036s][info][gc,heap,exit]   to   space 1024K,   0% used [0x00000007ff000000, 0x00000007ff000000, 0x00000007ff100000)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리는 위에서 신세대의 에덴과 생존자 공간 비율을 8:1 로 설정했다. 따라서 실행 결과 중간에서 설정된 결과를 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 신세대에서 사용할 수 있는 총 공간은 9216KB 이다. (에덴의 용량 + 생존자 공간 1 개의 용량)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 testAllocation() 에서 alloc4 객체를 생성하려고 할 때 마이너 GC 가 한차례 수행될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;alloc4 용 메모리를 할당하려고 보니 에덴에 6MB 이상이 이미 차있어서 4MB 크기인 alloc4 에 내어줄 공간이 부족하기 때문이다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) 마이너 GC 직후 힙 상황&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jvm2.png&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/miDtC/btsKTwUPRVg/KYaoMp3aqGklwmxQDUQ1g0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/miDtC/btsKTwUPRVg/KYaoMp3aqGklwmxQDUQ1g0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/miDtC/btsKTwUPRVg/KYaoMp3aqGklwmxQDUQ1g0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmiDtC%2FbtsKTwUPRVg%2FKYaoMp3aqGklwmxQDUQ1g0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;801&quot; height=&quot;307&quot; data-filename=&quot;jvm2.png&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생존자 공간의 크기는 겨우 1MB 이기 때문에 가비지 컬렉션 과정에서 가상머신은 에덴에 있던 2MB 객체 3개를 생존자 공간으로 옮길 수 없음을 깨닫고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;할당 보증 매커니즘&lt;/b&gt;을 발동해 곧바로 구세대로 옮긴다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;* 할당 보증 매커니즘&lt;br /&gt;할당 보증(Allocation Guarantee)은 Young Generation에 객체를 할당할 수 없을 경우, JVM이 Old Generation에 객체를 직접 할당하는 메커니즘이다. 다음과 같은 조건에서 작동한다.&lt;br /&gt;1. Eden과 Survivor 영역에 충분한 공간이 없을 때.&lt;br /&gt;2.Young Generation에서 GC를 실행해도 대상 객체를 수용할 공간이 없을 때.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과 중 마이너 GC 결과와 비교해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1732445781640&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[0.036s][info][gc,heap     ] GC(0) DefNew: 6635K(9216K)-&amp;gt;66K(9216K) 
								   Eden: 6635K(8192K)-&amp;gt;0K(8192K) 
								   From: 0K(1024K)-&amp;gt;66K(1024K)
[0.036s][info][gc,heap     ] GC(0) Tenured: 1036K(14336K)-&amp;gt;7180K(14336K)&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;b&gt;Eden 영역을 보면 0K&lt;/b&gt; 로 깨끗하게 비워진 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;From 에서 66K&lt;/b&gt; 는 에덴의 기타 객체들 중 생존한 객체를 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tenured 의 7180K&lt;/b&gt; 는 구세대 영역을 뜻하며 에덴에서 바로 넘어온 alloc1, alloc2, alloc3 를 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 때, 출력 로그에서 Old Generation 을 Tenured 라고 표현한다. &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;마이너 GC 의 결과로 신세대 사용량은 66K 로 줄었지만 줄어든 용량 대부분이 구세대로 이동하여 총 사용량은 크게 줄지 않았다. alloc1, alloc2, alloc3 객체가 여전히 살아있어 가상 머신이 회수할 수 있는 객체가 많지 않았기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. alloc4 까지 할당한 후의 힙 상황&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jvm5.drawio.png&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6ZEdC/btsKUF4mEV4/fa841a2CYBSOKTi3Be2vOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6ZEdC/btsKUF4mEV4/fa841a2CYBSOKTi3Be2vOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6ZEdC/btsKUF4mEV4/fa841a2CYBSOKTi3Be2vOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6ZEdC%2FbtsKUF4mEV4%2Ffa841a2CYBSOKTi3Be2vOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;801&quot; height=&quot;307&quot; data-filename=&quot;jvm5.drawio.png&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732446296473&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[0.036s][info][gc,heap,exit] Heap
[0.036s][info][gc,heap,exit]  def new generation   total 9216K, used 4244K [0x00000007fe800000, 0x00000007ff200000, 0x00000007ff200000)
[0.036s][info][gc,heap,exit]   eden space 8192K,  51% used [0x00000007fe800000, 0x00000007fec14830, 0x00000007ff000000)
[0.036s][info][gc,heap,exit]   from space 1024K,   6% used [0x00000007ff100000, 0x00000007ff110b68, 0x00000007ff200000)
[0.036s][info][gc,heap,exit]   to   space 1024K,   0% used [0x00000007ff000000, 0x00000007ff000000, 0x00000007ff100000)
[0.036s][info][gc,heap,exit]  tenured generation   total 14336K, used 7180K [0x00000007ff200000, 0x0000000800000000, 0x0000000800000000)
[0.036s][info][gc,heap,exit]    the space 14336K,  50% used [0x00000007ff200000, 0x00000007ff9033f0, 0x00000007ff903400, 0x0000000800000000)&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;최종적으로 에덴은 총 8MB 중 51% 가 차있고, 생존자 공간에는 기타 작은 객체들이 들어있다. 구세대는 6MB 정도가 차있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 큰 객체는 곧바로 구세대에 할당된다.&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jvm7.jpg&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H6QCH/btsKUEEod5R/u7cE5GBlDlrNI4pgWKPtgK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H6QCH/btsKUEEod5R/u7cE5GBlDlrNI4pgWKPtgK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H6QCH/btsKUEEod5R/u7cE5GBlDlrNI4pgWKPtgK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH6QCH%2FbtsKUEEod5R%2Fu7cE5GBlDlrNI4pgWKPtgK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;467&quot; data-filename=&quot;jvm7.jpg&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;큰 객체란 커다란&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;연속된&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메모리 공간을 필요로 하는 자바 객체를 말한다. 매우 긴 문자열이나 원소가 매우 많은 배열이 대표적인 예다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;메모리를 할당해야하는 가상 머신에 큰 객체의 등장은 타협이 불가능한 나쁜 소식이다. 큰 객체의 등장보다 더 나쁜 소식은 &lt;b&gt;곧 사라질&lt;/b&gt; 큰 객체들을 떼로 만나는 것이다. 그래서 프로그램을 작성할 때 이런 코드는 피하는 것이 좋다. &lt;b&gt;큰 객체들을 담기 위한 연속된 공간을 확보하기 위해 수많은 다른 객체들을 옮겨야 하므로 심각한 메모리 복사 오버헤드를 동반한다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 &lt;b&gt;-XX:PretenureSizeThreshold&lt;/b&gt; 매개 변수를 설정하면 설정값보다 큰 객체를 곧바로 구세대에 할당한다. 이 매개 변수의 목적은 &lt;b&gt;에덴과 두 생존자 공간 사이의 대규모 복사를 줄이는데&lt;/b&gt; 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 통해 확인해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1732447477731&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PretenureSizeThresholdTest {
  private static final int _1MB = 1024 * 1024;

  public static void main(String[] args) {
    byte[] alloc;
    alloc = new byte[4*_1MB];

  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOhVzH/btsKUCfn8L1/7kCBBcPxmizhiJ2BUkFF40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOhVzH/btsKUCfn8L1/7kCBBcPxmizhiJ2BUkFF40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOhVzH/btsKUCfn8L1/7kCBBcPxmizhiJ2BUkFF40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOhVzH%2FbtsKUCfn8L1%2F7kCBBcPxmizhiJ2BUkFF40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;313&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1732447553552&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[0.032s][info][gc,heap,exit] Heap
[0.032s][info][gc,heap,exit]  def new generation   total 9216K, used 655K [0x00000007fe800000, 0x00000007ff200000, 0x00000007ff200000)
[0.032s][info][gc,heap,exit]   eden space 8192K,   8% used [0x00000007fe800000, 0x00000007fe8a3e60, 0x00000007ff000000)
[0.032s][info][gc,heap,exit]   from space 1024K,   0% used [0x00000007ff000000, 0x00000007ff000000, 0x00000007ff100000)
[0.032s][info][gc,heap,exit]   to   space 1024K,   0% used [0x00000007ff100000, 0x00000007ff100000, 0x00000007ff200000)
[0.032s][info][gc,heap,exit]  tenured generation   total 14336K, used 5132K [0x00000007ff200000, 0x0000000800000000, 0x0000000800000000)
[0.032s][info][gc,heap,exit]    the space 14336K,  35% used [0x00000007ff200000, 0x00000007ff7033d0, 0x00000007ff703400, 0x0000000800000000)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-XX:PretenureSizeThreshold=3M &lt;/b&gt;로 설정해준 뒤 실행해보면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에덴은 거의 쓰이지 않고 10MB 짜리 구세대는&amp;nbsp; 5132K 가 사용됐음을 확인할 수 있다. 3MB 보다 큰 4MB 크기의 alloc 객체가 곧바로 구세대에 할당되었기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;하지만 이 매개변수는 시리얼과 파뉴 신세대 컬렉터에만 적용된다. PS 와 같은 다른 신세대 컬렉터들은 이 매개변수를 지원하지 않는다. 꼭 이 매개변수를 이용해 튜닝하고자 한다면 파뉴 + CMS 조합을 고려해보자.&lt;br /&gt;(JDK 10 까지는 -XX:PretenureSizeThreshold 매개변수의 값이 3M 형태를 인식하지 못하기 때문에&amp;nbsp; 3145728로 지정해야한다. )&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 나이가 차면 구세대로 옮겨진다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핫스팟 가상 머신의 컬렉터 대부분은 힙 메모리 관리에 &lt;b&gt;세대 단위 컬렉션&lt;/b&gt;을 활용한다. 그래서 메모리를 청소할 때 어떤 생존 객체를 신세대에 남겨 두고 어떤 생존 객체를 구세대로 옮길지 정해야한다. 이를 위해 가상 머신은 &lt;b&gt;각 객체의 객체 헤더에 세대 나이 카운터&lt;/b&gt;를 두도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체는 주로 에덴에서 태어나고 태어났을 때의 나이는 0이다. 첫번 째 마이너 GC 에서 살아남은 객체는, 생존자 공간이 충분하면 생존자 공간으로 옮겨지면서 나이가 1 증가한다. 그리고 생존자 공간에서 마이너 GC 를 한번 겪을 때마다 다시 1씩 증가한다. 그리고 특정 나이가 되면 구세대로 승격된다. 구세대로 승격되는 나이는 &lt;b&gt;-XX:MaxTenuringThreshold&lt;/b&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;MaxTenuringThreshold&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;를 바꿔 가면서 테스트 해보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) -XX:MaxTenuringThreshold=1&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1732448564134&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GC(0) Pause Young (Allocation Failure) // 첫번 째 GC 가 일어남
GC(0) Desired survivor size 524288 bytes, new threshold 1 (max threshold 1) // (1)
GC(0) Age table with threshold 1 (max threshold 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) -&amp;gt; 최대 나이는 1로 잘 설정 되어있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732448703059&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GC(0) DefNew: 4715K(9216K)-&amp;gt;194K(9216K)
      Eden: 4715K(8192K)-&amp;gt;0K(8192K)
      From: 0K(1024K)-&amp;gt;194K(1024K)  // (2)
GC(0) Tenured: 1036K(14336K)-&amp;gt;5132K(14336K)&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;(2) -&amp;gt; alloc1 이 아직 생존자 공간에 남아있다.&lt;/p&gt;
&lt;pre id=&quot;code_1732448857089&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;GC(1) Pause Young (Allocation Failure) // 2번째 GC
GC(1) Desired survivor size 524288 bytes, new threshold 1 (max threshold 1)
GC(1) Age table with threshold 1 (max threshold 1)
GC(1) DefNew: 4290K(9216K)-&amp;gt;0K(9216K) 
      Eden: 4096K(8192K)-&amp;gt;0K(8192K) 
      From: 194K(1024K)-&amp;gt;0K(1024K) // (3) 나이가 차서 구세대로 이동
GC(1) Tenured: 5132K(14336K)-&amp;gt;5327K(14336K) // (4) alloc2 + alloc1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;(3) -&amp;gt; 두번째 GC 때 생존자 공간에서 사라져 (4) -&amp;gt; 구세대로 이동했다. (1살이 되었기 때문이다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) -XX:MaxTenuringThreshold=15 -XX:TargetSurvivorRatio=80&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1732449059022&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GC(0) Pause Young (Allocation Failure) // 1번째 GC
GC(0) Desired survivor size 838856 bytes, new threshold 15 (max threshold 15) // (1)
GC(0) Age table with threshold 15 (max threshold 15) 
GC(0) - age   1:     199480 bytes,     199480 total
GC(0) DefNew: 4715K(9216K)-&amp;gt;194K(9216K) 
      Eden: 4715K(8192K)-&amp;gt;0K(8192K) 
      From: 0K(1024K)-&amp;gt;194K(1024K) // alloc1
GC(0) Tenured: 1036K(14336K)-&amp;gt;5132K(14336K) // alloc2
...
GC(1) Pause Young (Allocation Failure) // 2번째 GC
GC(1) Desired survivor size 838856 bytes, new threshold 15 (max threshold 15)
GC(1) Age table with threshold 15 (max threshold 15)
GC(1) - age   2:     199480 bytes,     199480 total
GC(1) DefNew: 4290K(9216K)-&amp;gt;194K(9216K) 
      Eden: 4096K(8192K)-&amp;gt;0K(8192K) 
      From: 194K(1024K)-&amp;gt;194K(1024K) // (2) alloc1 아직도 존재하고 있다.
GC(1) Tenured: 5132K(14336K)-&amp;gt;5132K(14336K) // alloc2
...
Heap
def new generation   total 9216K, used 4372K ... 
eden space 8192K,  51% used ... // alloc3
from space 1024K,  19% used ... // (3) alloc1
to   space 1024K,   0% used ...&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;보다시피 (1) 최대 나이도 잘 설정되었고 (2) alloc1 객체는 두 번째 GC 후에도 여전히 생존자 공간에 남아있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그런데 이번에는 -XX:TargetSurvivorRatio 매개 변수도 함께 지정했는데 이 매개변수는 생존자 공간이 지정한 비율 이상 차면 나이를 신경쓰지 않고 살아남은 객체들을 구세대로 승격시킨다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시에서는 80으로 설정했는데 (1) 의 838856바이트가 생존자 공간크기의 80%에 해당한다. 그리고 (3) 에서 생존자 공간이 19% 정도 채워졌는데 목표 비율을 이보다 더 작게 설정했다면 나이가 차지 않았음에도 바로 구세대로 옮겨졌을 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것으로 여기서 구세대로 옮기는 조건이 객체 크기와 나이뿐이 아님을 알 수 있다! 아래서 이어서 더 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 공간이 비좁으면 강제로 승격시킨다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 프로그램의 메모리 사용 패턴에 더 정밀하게 대응하기 위해 핫스팟 가상 머신은 나이가 -XX:MaxTenuringThreshold 보다 적으도 구세대로 승격시키기도 한다. 앞에서 바로 이야기한 &lt;b&gt;생존자 공간 점유율&lt;/b&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;기본값은 50%라서 생존 객체 전체의 크기 총합이 생존자 공간의 절반을 넘어서면 모든 객체를 구세대로 옮긴다. -XX:MaxTenuringThreshold 로 정한 나이는 무시된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 alloc_new 라는 객체를 추가해 80%를 넘기는 상황을 만들어보자.&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;pre id=&quot;code_1732449928258&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -Xlog:gc* 
-Xlog:gc+age=trace -XX:MaxTenuringThreshold=15 -XX:TargetSurvivorRatio=80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 실행 로그&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732450371051&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GC(0) Pause Young (Allocation Failure) // 첫번 째 GC
GC(0) Desired survivor size 838856 bytes, new threshold 15 (max threshold 15)
GC(0) Age table with threshold 15 (max threshold 15)
GC(0) - age   1:     885400 bytes,     885400 total
GC(0) DefNew: 5436K(9216K)-&amp;gt;864K(9216K) 
      Eden: 5436K(8192K)-&amp;gt;0K(8192K) 
      From: 0K(1024K)-&amp;gt;864K(1024K) // (1) alloc1, alloc_new 생존자 공간에 존재
GC(0) Tenured: 0K(14336K)-&amp;gt;4096K(14336K) // alloc2
...
GC(1) Pause Young (Allocation Failure) // 두 번째 GC
GC(1) Desired survivor size 838856 bytes, new threshold 15 (max threshold 15)
GC(1) Age table with threshold 15 (max threshold 15)
GC(1) - age   2:     885400 bytes,     885400 total
GC(1) DefNew: 4960K(9216K)-&amp;gt;0K(9216K) 
      Eden: 4096K(8192K)-&amp;gt;0K(8192K) 
      From: 864K(1024K)-&amp;gt;0K(1024K) // (2) 80% 초과, 구세대로 이동
GC(1) Tenured: 4096K(10240K)-&amp;gt;4960K(10240K) // (3) alloc2 + alloc1, alloc_new
...
Heap
def new generation   total 9216K, used 4436K ...
eden space 8192K,  51% used ...
from space 1024K,  0% used ...
to   space 1024K,   0% used ...
tenured generation   total 14336K, used 5132K // alloc1, alloc_new, alloc2&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;샐행결과 첫번 째 마이너 GC 때도 (1) alloc1 객체는 똑같이 생존자 공간에 존재했다. 그런데 두 번째 GC 때는 (2) 생존자 공간 목표 비율인 80% 를 훌쩍 넘어서 (3) 나이가 차지 않았음에도 구세대로 이동되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 마치며&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가비지 컬렉터는 많은 경우에 시스템의 일시 정지 시간과 처리량에서 중요한 요인으로 작용한다.&amp;nbsp;&lt;/b&gt;사용자가 실제 애플리케이션의 요구 조건과 구현 방식에 가장 적합한 컬렉션 방식을 선택할 수 있도록 가상 머신은 다양한 컬렉터와 많은 조율 매개 변수를 제공한다. 모든 상황에서 단 하나의 컬렉터나&amp;nbsp; 매개 변수 조합이란 없다. 따라서 가상 머신 메모리의 대한 지식을 쌓고 최적화 노하우를 얻고 싶다면, 각 컬렉터의 동작 방식과 장단점 그리고 매개변수들을 이해해야한다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;느낀점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서 다뤄본 매개변수는 겨우 일부에 불과한데 이렇게 많은 매개변수들이 존재하고 있는지 몰랐다. 로그를 통해 직접 메모리들이 어떻게 이동하고 회수되는지를 눈으로 보니 좀 더 잘 이해하고 다른 시선으로도 바라볼 수 있게 되었다. 실무에서 이런 매개변수들을 사용해본 경험이 전무한데 앞으로 필요한 상황이 생기면 이런 매개변수들을 활용해서 최적화를 해볼 수 도 있을 것 같다!&lt;/p&gt;</description>
      <category>Kotlin &amp;amp; Java</category>
      <category>GC</category>
      <category>gc 메모리 회수 전략</category>
      <category>JVM</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/175</guid>
      <comments>https://bin-repository.tistory.com/175#entry175comment</comments>
      <pubDate>Sun, 24 Nov 2024 21:26:05 +0900</pubDate>
    </item>
    <item>
      <title>2024 WOOWACON 후기 (백엔드 및 멘토링 세션 참여)</title>
      <link>https://bin-repository.tistory.com/178</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3030&quot; data-origin-height=&quot;2060&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byuSKI/btsKCxM7gsm/U2JvPAbPKw28cQW2Xn7g5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byuSKI/btsKCxM7gsm/U2JvPAbPKw28cQW2Xn7g5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byuSKI/btsKCxM7gsm/U2JvPAbPKw28cQW2Xn7g5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyuSKI%2FbtsKCxM7gsm%2FU2JvPAbPKw28cQW2Xn7g5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;585&quot; data-origin-width=&quot;3030&quot; data-origin-height=&quot;2060&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 우아콘을 참여하게된 계기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소 우아한 테크 메일을 구독하고 있는데 2024년에도 어김없이 우아콘을 개최한다는 소식을 받게되었다. 인프콘이나 유스콘 등 이런 기술 컨퍼런스에 관심이 많은 편이라 이번에도 신청을 하게 되었다. 지인에게 표를 받아 간 인프콘 외에 이런 랜덤 추첨식 컨퍼런스는 항상 떨어졌기 때문에 큰 기대를 하지 않고 있었는데 이번에는 운 좋게 참가자로 선정되어서 갈 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2474&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3fDfC/btsKC2ZVGOz/9ahPcBCLRz6Ajz5XOH7UGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3fDfC/btsKC2ZVGOz/9ahPcBCLRz6Ajz5XOH7UGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3fDfC/btsKC2ZVGOz/9ahPcBCLRz6Ajz5XOH7UGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3fDfC%2FbtsKC2ZVGOz%2F9ahPcBCLRz6Ajz5XOH7UGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;81&quot; data-origin-width=&quot;2474&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 우아콘에서는 전과 달리 참여형 세션이 생겼는데 참여형의 경우 따로 구글폼으로 따로 신청을 받았다. 그중 소수의 멘티로 진행하는 집중형 멘토링을 신청했고 신청폼에는 신청사유와 질문목록 등을 작성하도록 되어있어 &quot;오 이거 잘쓰면 붙겠는데?&quot; 하는 생각에 정성들여 썼다. (이 전략이 통한 것 같다 ㅎㅎㅎ)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2874&quot; data-origin-height=&quot;1452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YWVAV/btsKCXkiobH/KOazCAK8OF8vSNrN40pL11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YWVAV/btsKCXkiobH/KOazCAK8OF8vSNrN40pL11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YWVAV/btsKCXkiobH/KOazCAK8OF8vSNrN40pL11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYWVAV%2FbtsKCXkiobH%2FKOazCAK8OF8vSNrN40pL11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;404&quot; data-origin-width=&quot;2874&quot; data-origin-height=&quot;1452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 컨퍼런스를 참여하면서 세운 나의 가장 큰 목표는 &lt;b&gt;메타인지 높이기&lt;/b&gt; 였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연차가 쌓일수록 회사에서 오래 일할 수록 사람들은 현재 하는일에 매우 익숙해진다. 계속해서 개선하고 발전시키는 사람이 있는 반면 기술 부채에 대해서 점점 무뎌지고 익숙해지는 사람도 있다. 현재의 나는 후자가 되어가는 것 같아 이런 행사에 참여하는 것을 매우 뜻 깊게 생각하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 주요 세션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 우아콘에서는 BE, FE, AI(데이터사이언스), PM 분야로 트랙이 나뉘어있었다. 개인적으로 AI, PM 쪽도 궁금했는데 BE 트랙들도 못지않게 궁금한 것들이 많아서 (kafka 내용을 다룬 세션들이 많았다.) 백엔드 트랙 (A) 에서 죽치고 들었다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 오프닝 세션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오프닝 세션에서는 글로벌 환경에서 우아한형제들이 현재 어떤 것들을 하고 있고 어떤 방향으로 나아갈 것인지를 소개했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-15-09.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mbvqv/btsKCM39E5t/xVV815UcLyJ5mZdnpbdma1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mbvqv/btsKCM39E5t/xVV815UcLyJ5mZdnpbdma1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mbvqv/btsKCM39E5t/xVV815UcLyJ5mZdnpbdma1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmbvqv%2FbtsKCM39E5t%2FxVV815UcLyJ5mZdnpbdma1%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;800&quot; height=&quot;450&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-15-09.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&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;1226&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkvUmA/btsKD3wQsmn/x5igsKz4JfL3n0V2jwlJnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkvUmA/btsKD3wQsmn/x5igsKz4JfL3n0V2jwlJnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkvUmA/btsKD3wQsmn/x5igsKz4JfL3n0V2jwlJnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkvUmA%2FbtsKD3wQsmn%2Fx5igsKz4JfL3n0V2jwlJnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;392&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 배달의 민족 API Gateway&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-18-34.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsReYl/btsKCToWCLw/AJvUPTePwxPMI5ug38qlM0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsReYl/btsKCToWCLw/AJvUPTePwxPMI5ug38qlM0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsReYl/btsKCToWCLw/AJvUPTePwxPMI5ug38qlM0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsReYl%2FbtsKCToWCLw%2FAJvUPTePwxPMI5ug38qlM0%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;800&quot; height=&quot;450&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-18-34.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 다니고 있는 회사에서 완벽한 MSA 는 아니지만 어느정도 클라이언트에서 부르는 서버들이 여러개라서 API Gateway 사용의 대한 고민들이 있었다. 그래서 첫 세션부터 기대했는데 짧은 시간에 API Gateway 와 API Gateway Pattern 의 차이, 그리고 API Gateway 를 적용해야할 시점, 장단점 등을 알 수 있는 시간이어서 매우 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도메인 분할 때문에 API Gateway 가 필요하다고 생각하면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프론트 서버에서 횡단 관심사 문제가 커질 때 API Gateway 가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- API Gateway 를 잘 적용하려면 API Gateway pattern 을 잘 알아야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) Kafka 를 이용한 메세지 플랫폼에서 장애를 겪으며 아키텍쳐를 개선한 이야기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-28-20.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm4nL0/btsKCNhEWiJ/6p5YkJ6VzyFPVCjK5gs0K1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm4nL0/btsKCNhEWiJ/6p5YkJ6VzyFPVCjK5gs0K1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm4nL0/btsKCNhEWiJ/6p5YkJ6VzyFPVCjK5gs0K1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm4nL0%2FbtsKCNhEWiJ%2F6p5YkJ6VzyFPVCjK5gs0K1%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;800&quot; height=&quot;450&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-28-20.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 카프카 렉 100만건이 쌓여 장애로 이루어졌던 일이 있었다. 또한 FCM 을 사용하는데 믿고 쓰기엔 꽤나 500 대 에러가 자주 발생한다. 그런데 우형도 똑같이 FCM 을 사용하고 있고 그런 장애를 겪고 있어 retry 하는 횟수가 많다고 하니 동질감이 들었다.. ㅎㅎ&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 장애 주요 원인은 Kafka Exactly once 전략을 사용하고 있어 지나치게 멱등성을 강제하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 720 개의 파티션을 줄이기 위한 도전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 병목이 되는 아웃라이어 제거한다. (FCM 경우 특정시간마다 레이턴시가 있어 계속해서 리밸런싱이 발생했다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 동시 처리량을 증가시킨다. (동기 -&amp;gt; 비동기, 스레드풀 최적화, 데드레터 방식 사용)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) Kafka 기반 대규모 동시성 최적화: Request-Reply 패턴 활용 사례&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-38-32.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVAj1F/btsKDialmyd/s5WWRoC0w1mCXIpSqg2FLK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVAj1F/btsKDialmyd/s5WWRoC0w1mCXIpSqg2FLK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVAj1F/btsKDialmyd/s5WWRoC0w1mCXIpSqg2FLK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVAj1F%2FbtsKDialmyd%2Fs5WWRoC0w1mCXIpSqg2FLK%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;800&quot; height=&quot;450&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-38-32.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세션 또한 되게 흥미로웠다. Request-Reply 패턴을 사용하게 된 사례로 라이더 배달 반경을 예로 들었는데 관련 도메인 입장에서 흥미롭게 들었다. 주된 이야기는 카프카를 통해 발행된 이벤트들의 순서보장을 강제하기 위한 방법이었다. 꽤나 실무에 바로 적용해볼 법한 주제였다고 생각한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 분산락과 Request-Reply 를 비교했을 때 Request-Reply 가 구현은 조금 더 어렵지면 더 높은 확장성을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 요청과 응답메세지의 처리를 통해 순서보장을 강제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 단일 Reply Topic 구성시 Requester 마다 유일한 컨슈머 그룹 아이디를 할당하여 응답 메세지를 수신한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) DDD 그렇게 하는거 아닌데&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-47-07.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddRHkH/btsKDYibisT/eD3qA4LWlnR7eqLywRuGP1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddRHkH/btsKDYibisT/eD3qA4LWlnR7eqLywRuGP1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddRHkH/btsKDYibisT/eD3qA4LWlnR7eqLywRuGP1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddRHkH%2FbtsKDYibisT%2FeD3qA4LWlnR7eqLywRuGP1%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;800&quot; height=&quot;450&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-47-07.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시나브로 옷젖듯 팀에 DDD 를 전파를 가장 빠르게 실천할 수 있는 이야기들을 들을 수 있었다. 개인적으로 넥스트스텝에서 제공하는 강의들을 들으면서 익숙하게 들어왔던 이야기들이라 편하게 들을 수 있었다!&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6) 우아한 데이터 허브. 일 200억건 데이터 안전하게 처리하는 대용량 시스템 구축하기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-47-14.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJX3Fe/btsKDryppAo/vdkbrSEpkFgmB09OrgvBo1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJX3Fe/btsKDryppAo/vdkbrSEpkFgmB09OrgvBo1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJX3Fe/btsKDryppAo/vdkbrSEpkFgmB09OrgvBo1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJX3Fe%2FbtsKDryppAo%2FvdkbrSEpkFgmB09OrgvBo1%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;800&quot; height=&quot;450&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-47-14.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혜택, 회원, 커머스, 푸드가게 등등&amp;hellip; 여러 서비스들에서 모아지는 데이터들이 분당 약 700만개라고 한다. 그리고 하루에 200억개의 데이터를 다루는 것의 대한 이야기를 했는데 정말 재밌는 (^^&amp;hellip;) 환경이라고 생각했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-52-51.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QH8fi/btsKCREALOh/LA1lAAkkgJkxSEtheOckGK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QH8fi/btsKCREALOh/LA1lAAkkgJkxSEtheOckGK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QH8fi/btsKCREALOh/LA1lAAkkgJkxSEtheOckGK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQH8fi%2FbtsKCREALOh%2FLA1lAAkkgJkxSEtheOckGK%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;800&quot; height=&quot;450&quot; data-filename=&quot;KakaoTalk_Photo_2024-11-09-20-52-51.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;2268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트를 통한 데이터 통함 -&amp;gt; 데이터 처리 -&amp;gt; 데이터 저장의 관련된 전반적인 내용을 다뤘다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 저장 부분에서 샤딩과 핫/콜드 파티션, SPOF 등의 문제점 등을 말해줬는데 이런 것들을 다뤄본 경험이 없다보니 이해하기가 좀 어려웠다. 이 세션은 나중에 영상이 공개되면 다시 한번 봐야겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 멘토링 세션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 기대했던 세션이 멘토링 세션이었다. 소수의 멘티로 구성된다고 했는데 어느정도가 소수인지도 궁금했다. 행사 진행전에 오픈단톡방을 알려줘서 초대됐는데 멘티들이 엄청 많아서 소수여도 10명 이상이겠구나? 하고 예상했다. 준비한 질문들을 다 할 수 있을지도 걱정이 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집중형 멘토링은 6명 정도 앉을 수 있는 책상이 조그맣게 분리된 공간에서 1(멘토) : 5(멘티) 로 진행되었다. 진행 전에 쿠키와 차도 먹을 수 있도록 되어있어서 약간의 긴장(?)이 풀렸다. 시작전에 간단하게 각자 소개들을 했는데 나와 비슷한 연차분들과 10년차(!!) 분들도 있었다! 도대체 어떤 고민들이 있으시길래 멘토링 신청을 하셨는지 궁금했다.&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;권용근 멘토님과의 분위기는 매우 편안했다. 인상도 굉장히 좋으셨다. 멘토링은 40분으로 매우 짧은 시간동안 진행되어 가장 궁금한 1가지씩만 질답하니 시간이 다 되어서 조금 급하게 마무리 된 감이 있었다.&amp;nbsp;여러가지 질문들이 오갔지만 그 중 가장 인상깊었던 내용이 있다. 이건 나도 똑같이 궁금했던 건데&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;백엔드 n년차면 어느정도를 해야할까요?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 질문이었다. 거기서 멘토님께서는 &lt;b&gt;한계를 두지 말고 자신이 생각하는 대로 성장해&lt;/b&gt;가라고 했다. 조급한 마음에 남들과 비교하며 이정도면 괜찮겠지? 라고 생각했던 날들이 조금 부끄러워졌다. 다시 한번 주위 신경쓰지 않고 딥다이브하고 싶다는 욕구가 불타올랐다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러명의 다양한 고민들을 들으니 이런생각도하는구나, 다들 똑같은 생각이구나 하는 마음에 새롭게 자극이 되기도 안심이 되기도 했다. 다양한 도메인과 연차를 가진 사람들의 고민을 듣는것도 매우 유익한 시간이었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_KakaoTalk_Photo_2024-11-09-21-12-21.jpeg&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1687&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z4Q8c/btsKCLxqtst/AWzrrw8I7jwZ4GxfQf2a0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z4Q8c/btsKCLxqtst/AWzrrw8I7jwZ4GxfQf2a0k/img.png&quot; data-alt=&quot;멘토님이 사비로 준비해주셨다고 한다 ㅎㅎ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z4Q8c/btsKCLxqtst/AWzrrw8I7jwZ4GxfQf2a0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz4Q8c%2FbtsKCLxqtst%2FAWzrrw8I7jwZ4GxfQf2a0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;450&quot; data-filename=&quot;edited_KakaoTalk_Photo_2024-11-09-21-12-21.jpeg&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1687&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;멘토님이 사비로 준비해주셨다고 한다 ㅎㅎ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 행사에 가면 항상 활기찬 분위기와 배움의 대한 열정들을 느낄 수 있어서 너무 좋다. 각자의 경험과 고민을 공유하며 &lt;b&gt;나와 다른 관점을 배울 수 있어 시야가 넓어진다&lt;/b&gt;는 점도 매우 좋다. 또 내가 저 연사자 자리에 서면 어떨까 하는 생각도 해보았다. (언젠가&amp;hellip;)&lt;/p&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;amp; 네트워킹을 통해 다시 한번 내가 현재 위치한 자리를 알고 앞으로 무엇을 해나가야할지를 다짐할 수 있었다. 다음번엔 토스, 카카오, 네이버 컨퍼런스도 참여할 수 있었으면 좋겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;(이번엔 귀여운 스티커들이 없어서 좀 아쉬웠다.)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>나의 이야기 &amp;amp; 회고</category>
      <category>2024우아콘</category>
      <category>개발자</category>
      <category>개발자컨퍼런스</category>
      <category>배달의민족</category>
      <category>오블완</category>
      <category>우아콘</category>
      <category>우아한형제들</category>
      <category>티스토리챌린지</category>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/178</guid>
      <comments>https://bin-repository.tistory.com/178#entry178comment</comments>
      <pubDate>Sat, 9 Nov 2024 21:27:57 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 1461 도서관 (java)</title>
      <link>https://bin-repository.tistory.com/177</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1461&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1461&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;1178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JvUN0/btsKAtpVi1s/8okuOlIi3eOZk0SHSkMlKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JvUN0/btsKAtpVi1s/8okuOlIi3eOZk0SHSkMlKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JvUN0/btsKAtpVi1s/8okuOlIi3eOZk0SHSkMlKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJvUN0%2FbtsKAtpVi1s%2F8okuOlIi3eOZk0SHSkMlKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;433&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;1178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 풀이&lt;/h2&gt;
&lt;p data-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;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: 14.2857%; text-align: center;&quot;&gt;-37&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;-6&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;-39&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;-29&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;11&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;-28&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;여기서 세준이의 위치와 책들의 시작위치는 0 이기 때문의 0의 위치도 추가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 거리를 구하기 위해서는 양수와 음수를 차례대로 정렬해보아야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 17px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 17px;&quot;&gt;-39&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 17px;&quot;&gt;-37&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 17px;&quot;&gt;-29&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 17px;&quot;&gt;-28&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 17px;&quot;&gt;-6&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 17px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;0&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 17px;&quot;&gt;11&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;0 의 위치에서부터 M 만큼의 책을 들고 책을 두러 이동할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음수쪽에서 이동하게 되면 예제 1 (최대 들고 갈 수 있는 책의 수:2) 을 예로 들면 아래 처럼 묶어서 가는 것 보다는&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgao6T/btsKzxmhE7R/WNLgJo5E24z0z6SgkKItN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgao6T/btsKzxmhE7R/WNLgJo5E24z0z6SgkKItN1/img.png&quot; data-alt=&quot;최소 걸음수 : 169&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgao6T/btsKzxmhE7R/WNLgJo5E24z0z6SgkKItN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdgao6T%2FbtsKzxmhE7R%2FWNLgJo5E24z0z6SgkKItN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;85&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최소 걸음수 : 169&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 큰수를 최대한 묶음으로 가는 것이 최소 걸음 수가 나오게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1836&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DMu8E/btsKzpohTOL/JY14IqnZ8uagzRbLFG3eUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DMu8E/btsKzpohTOL/JY14IqnZ8uagzRbLFG3eUk/img.png&quot; data-alt=&quot;최소 걸음수 : 109&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DMu8E/btsKzpohTOL/JY14IqnZ8uagzRbLFG3eUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDMu8E%2FbtsKzpohTOL%2FJY14IqnZ8uagzRbLFG3eUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;79&quot; data-origin-width=&quot;1836&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최소 걸음수 : 109&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 &lt;b&gt;제일 마지막으로 도착한 위치에서는 다시 0 의 위치로 돌아오지 않고 끝내도 된다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 바로 위의 표를 식으로 만들면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(6 * 2) + (29 * 2) + 39 (마지막) = 109&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 음수 방향에서의 최소 걸음수를 구할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주의!&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 코드를 작성할 때 우선순위큐를 이용해서 풀 수 있는데 음수인 경우 절대값으로 큐에 집어넣으면 원하는대로 정렬할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 한가지 주의할 점은 계속해서 원하는대로 정렬이 되지 않아서 chatGPT 에게 물어봤는데 &lt;b&gt;우선순위큐의 경우 힙 구조로 작동하기 때문에 큐에서 값을 꺼낼 때 원하는 정렬 순서를 보장한다고 한다.&amp;nbsp; (주의!)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baoVks/btsKBPyAI2m/ZaCOmVN4V5YnAqL00S2Jp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baoVks/btsKBPyAI2m/ZaCOmVN4V5YnAqL00S2Jp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baoVks/btsKBPyAI2m/ZaCOmVN4V5YnAqL00S2Jp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaoVks%2FbtsKBPyAI2m%2FZaCOmVN4V5YnAqL00S2Jp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;377&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 소스코드&lt;/h2&gt;
&lt;pre id=&quot;code_1730983025133&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

/**
 * https://www.acmicpc.net/problem/1461 걸을 수 있는 최소 걸음 수 구하
 */
public class BG_도서관 {

  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    StringTokenizer st = new StringTokenizer(br.readLine());

    int N = Integer.parseInt(st.nextToken()); // 책의 개수
    int M = Integer.parseInt(st.nextToken());// 들 수 있는 책의 수

    // 내림차순 정렬
    PriorityQueue&amp;lt;Integer&amp;gt; positiveQ = new PriorityQueue&amp;lt;&amp;gt;((p1, p2) -&amp;gt; p2 - p1);
    PriorityQueue&amp;lt;Integer&amp;gt; negativeQ = new PriorityQueue&amp;lt;&amp;gt;((p1, p2) -&amp;gt; p2 - p1);

    st = new StringTokenizer(br.readLine());
    for (int i = 0; i &amp;lt; N; i++) {
      int temp = Integer.parseInt(st.nextToken());

      if (temp &amp;gt; 0) { // 양수라면
        positiveQ.offer(temp);
      } else { // 음수라면
        negativeQ.offer(Math.abs(temp));
      }
    }

    int maxPosition = 0; // 가장 멀리있는 책의 위치를 저장
    if (positiveQ.isEmpty()) {
      maxPosition = negativeQ.peek();
    } else if (negativeQ.isEmpty()) {
      maxPosition = positiveQ.peek();
    } else {
      maxPosition = Math.max(positiveQ.peek(), negativeQ.peek()); // 두개의 최상위 원소중 가장 큰 값을 지정
    }

    int result = 0;

    while (!positiveQ.isEmpty()) { // 우선순위큐에 값이 없을 때까지
      int temp = positiveQ.poll();
      for (int i = 0; i &amp;lt; M-1; i++) {
        positiveQ.poll();

        if (positiveQ.isEmpty()) {
          break;
        }
      }

      result += temp * 2;
    }

    // 39 37 29 28 6
    while (!negativeQ.isEmpty()) {
      int temp = negativeQ.poll();
      for (int i = 0; i &amp;lt; M-1; i++) {
        negativeQ.poll();

        if (negativeQ.isEmpty()) {
          break;
        }
      }

      result += temp * 2;
    }

    result -= maxPosition; // 마지막에 제일 큰 위치의 수를 뺀다.
    bw.write(result + &quot;\n&quot;);
    bw.flush();
    bw.close();
    br.close();
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/177</guid>
      <comments>https://bin-repository.tistory.com/177#entry177comment</comments>
      <pubDate>Thu, 7 Nov 2024 21:38:14 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] RestClient 에서 Okhttp3 라이브러리를 사용할 때 주의할 점</title>
      <link>https://bin-repository.tistory.com/172</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 글은 실무에서 겪은 이슈의 원인과 해결, 문제 분석을 회고 형식으로 작성한 글입니다. 결론만 보아도 무방합니다!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;1. 개요 (문제 상황)&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내에서 스프링 부트 버전을 3.2.3 으로 업그레이드를 하면서 HTTP 클라이언트로&amp;nbsp;&lt;a title=&quot;RestClient&quot; href=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RestClient&lt;/a&gt; 를 도입하기로 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestClient 는 스프링 부트 3.2.x 버전부터 사용할 수 있으며 RestTemplate 을 더이상 유지보수 하지 않게 되면서 스프링 진영에서 권장하고 있는 HTTP 클라이언트 입니다. WebClient 도 있긴 하지만 따로 spring-cloud 의존성을 추가해야 하기 때문에 후보에서 제외되었습니다.&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;잠깐 RestClient 의 장점에 대해서 설명하자면 fluent 한 API 를 제공하고 체이닝 메서드를 제공하기 때문에 가독성이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 팀에서 선택하기에 따라서 OpenFeign 처럼 인터페이스에서 &lt;a title=&quot;선언형&quot; href=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;선언형&lt;/a&gt;으로 사용할 수도 있고 서비스 로직에서 체이닝 메서드를 통해 HTTP 통신을 할 수도 있습니다! (저희는 fluent 하게 체이닝 메서드를 사용하는 방식으로 개발했습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 &lt;a title=&quot;Retrofit&quot; href=&quot;https://square.github.io/retrofit/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Retrofit&lt;/a&gt; 을 사용하고 있었으나 안드로이드 진영에서 많이 사용되는 라이브러리고 별도의 의존성 추가 없이도 사용할 수 있는 RestClient 를 사용하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레거시를 청산할 수 있는 새로운 라이브러리의 적용이라니 너무 신이났습니다. ^^!!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 신남도 잠시 어떤 순서로 문제의 코드를 마주하게 되었는지 차근차근 설명해 보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1) RestClient 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에서도 매우 잘 나와있다시피 RestClient 빈을 선언하고 체이닝 메서드로 HTTP 요청 요소들을 세팅한 뒤 호출하면 끝입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 defaultHeader, defaultStatusHandler 등 조금의 세팅을 더 했지만 여기선 생략하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728568204583&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RestClient customClient = RestClient.builder()
  .baseUrl(&quot;https://example.com&quot;)
  .requestInterceptor(myCustomInterceptor)
  .build();&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1728568088050&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String result = restClient.get()
  .uri(&quot;https://example.com&quot;)
  .retrieve()
  .body(String.class);

System.out.println(result);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2) 커스텀 로깅 인터셉터&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략하게 스켈레톤 코드로 잘 호출되는 것을 확인한 뒤 request, response 를 로깅할 수 있는 커스텀 인터셉터를 추가하기로 합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 참고: &lt;a href=&quot;https://www.baeldung.com/spring-resttemplate-logging&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.baeldung.com/spring-resttemplate-logging&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728568674445&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LoggingInterceptor implements ClientHttpRequestInterceptor {

    static Logger LOGGER = LoggerFactory.getLogger(LoggingInterceptor.class);

    @Override
    public ClientHttpResponse intercept(
      HttpRequest req, byte[] reqBody, ClientHttpRequestExecution ex) throws IOException {
        LOGGER.debug(&quot;Request body: {}&quot;, new String(reqBody, StandardCharsets.UTF_8));
        ClientHttpResponse response = ex.execute(req, reqBody);
        InputStreamReader isr = new InputStreamReader(
          response.getBody(), StandardCharsets.UTF_8);
        String body = new BufferedReader(isr).lines()
            .collect(Collectors.joining(&quot;\n&quot;));
        LOGGER.debug(&quot;Response body: {}&quot;, body);
        return response;
    }
}&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;이대로 실행하게 되면 인터셉터가 응답 스트림을 이미 소비하여 API 를 호출했을 때 빈 응답값이 오게 됩니다. 따라서&amp;nbsp;&lt;span style=&quot;background-color: #fafafa; color: #63b175; text-align: start;&quot;&gt;BufferingClientHttpRequestFactory&lt;/span&gt;&lt;span style=&quot;color: #63b175; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 객체를 사용하여 요청과 응답을 메모리에 버퍼링하여 문제를 해결 할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #63b175; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(하지만 이 방법은 최악의 경우 OOM 으로 이어질 수 있다고 하는데 해당 글에서는 따로 다루지 않도록 하겠습니다. )&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728568912532&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ClientHttpRequestFactory factory = 
        new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
        RestTemplate restTemplate = new RestTemplate(factory);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 그러면 다시 한번 코드를 실행해 볼까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3) 문제 발생&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1728568969074&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String result = restClient.get()
  .uri(&quot;https://example.com&quot;)
  .retrieve()
  .body(String.class);

System.out.println(result);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행하게 되면 에러를 만나게 됩니다... 로깅 인터셉터를 추가하기전까지만 해도 잘되던 코드였는데요?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;java.lang.IllegalArgumentException: method GET must not have a request body.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJwkHp/btsJ08TZov3/Vn7rMrK72MEkJ7qCpCtHaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJwkHp/btsJ08TZov3/Vn7rMrK72MEkJ7qCpCtHaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJwkHp/btsJ08TZov3/Vn7rMrK72MEkJ7qCpCtHaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJwkHp%2FbtsJ08TZov3%2FVn7rMrK72MEkJ7qCpCtHaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;256&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제가 왜 발생했을까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 저수준으로 내려가서 HTTP 클라이언트 라이브러리를 무엇을 사용하고 있는지를 알아봐야했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;2. 분석 (문제의 원인을 찾아서...)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1) 내가 사용하고 있는 저수준 HTTP 클라이언트 라이브러리는 무엇일까요?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수준의 HTTP 클라이언트는 RestClient 를 사용하고 있는데 그렇다면 저수준의 HTTP 클라이언트 라이브러리는 무엇을 사용하고 있었을까요? 위에 명시한 간단한 코드들을 보면 어떤 라이브러리를 사용하고 있는지 설정해 준적이 없습니다.&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;ClientHttpRequestFactorySettings.DEFAULTS&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 를 사용했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728569954057&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
  public RestClient restClient() {
    var settings = ClientHttpRequestFactorySettings.DEFAULTS
        .withConnectTimeout(Duration.ofSeconds(5L))
        .withReadTimeout(Duration.ofSeconds(5L));

    return RestClient.builder()
        .baseUrl(&quot;&quot;)
        .requestFactory(new BufferingClientHttpRequestFactory(ClientHttpRequestFactories.get(settings)))
        .requestInterceptor(new RestClientLoggingInterceptor())
        .build();
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/reference/io/rest-client.html#io.rest-client.restclient&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;참고: https://docs.spring.io/spring-boot/reference/io/rest-client.html#io.rest-client.restclient&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728569995567&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Calling REST Services :: Spring Boot&quot; data-og-description=&quot;Spring Framework&amp;rsquo;s RestTemplate class predates RestClient and is the classic way that many applications use to call remote REST services. You might choose to use RestTemplate when you have existing code that you don&amp;rsquo;t want to migrate to RestClient, or &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/reference/io/rest-client.html#io.rest-client.restclient&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/reference/io/rest-client.html#io.rest-client.restclient&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/reference/io/rest-client.html#io.rest-client.restclient&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/reference/io/rest-client.html#io.rest-client.restclient&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Calling REST Services :: Spring Boot&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Framework&amp;rsquo;s RestTemplate class predates RestClient and is the classic way that many applications use to call remote REST services. You might choose to use RestTemplate when you have existing code that you don&amp;rsquo;t want to migrate to RestClient, or&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, requestFactory 를 세팅해주는 부분을 보면 &lt;b&gt;ClientHttpRequestFactories.get(settings)&lt;/b&gt;&amp;nbsp;를 해주고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당하는 메서드 로직을 보게되면 순서대로 &lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;APACHE_HTTP_CLIENT&lt;/span&gt;, &lt;span style=&quot;color: #1a5490;&quot;&gt;JETTY_CLIENT&lt;/span&gt;, &lt;span style=&quot;color: #1a5490;&quot;&gt;OKHTTP_CLIENT&lt;/span&gt;, &lt;span style=&quot;color: #1a5490;&quot;&gt;SIMPLE&lt;/span&gt;&lt;/b&gt; 을 지원하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpxnsM/btsJ2wM6dfC/A3PreyvLlzD9rofOdrEd00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpxnsM/btsJ2wM6dfC/A3PreyvLlzD9rofOdrEd00/img.png&quot; data-alt=&quot;( https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java )&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpxnsM/btsJ2wM6dfC/A3PreyvLlzD9rofOdrEd00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpxnsM%2FbtsJ2wM6dfC%2FA3PreyvLlzD9rofOdrEd00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;268&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;( https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java )&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위 상수들은 ClassUtils 이라는 클래스에서 isPresent 메서드로 클래스 path 를 통해 의존성 존재여부를 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/trdT0/btsJ0vbkFMq/bvQ01GJtNlgTQaOW1oGqJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/trdT0/btsJ0vbkFMq/bvQ01GJtNlgTQaOW1oGqJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/trdT0/btsJ0vbkFMq/bvQ01GJtNlgTQaOW1oGqJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtrdT0%2FbtsJ0vbkFMq%2FbvQ01GJtNlgTQaOW1oGqJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;265&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, Retrofit 을 사용해본적이 있다면 알 수 있을텐데 아래와 같은 조건에서 사용하게 되는 저수준 HTTP 클라이언트 라이브러리는 무엇일까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 별도의 저수준 라이브러리 의존성을 추가한 적 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. retrofit 사용중&lt;/p&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;hellip; &lt;b&gt;Okhttp3&lt;/b&gt; 입니다. Retrofit 과 Okhttp 는 squareup 이라는 같은 회사에서 만들었고 Retrofit 은 Okhttp 라이브러리에 의존하고 있습니다. HTTP 클라이언트를 교체한다고 해서 기존 레거시 코드를 빅뱅으로 지울 수는 없었기 때문에 해당하는 의존성이 존재했고 &lt;b&gt;ClientHttpRequestFactories.get(settings) &lt;/b&gt;를 호출했을 때 &quot;okhttp3.OkHttpClient&quot; 클래스가 존재했기 때문에 ClientHttpRequestFactory 는 OkHttp3ClientHttpRequestFactory 로 리턴되어 해당 저수준 라이브러리를 사용하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2) Okhttp3 는 왜 GET 요청일 때 본문(body) 가 있다면&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt; 에러를 발생시키는걸까요?&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;관련된 내용을 &lt;a title=&quot;squareup/okhttp 레포&quot; href=&quot;https://github.com/square/okhttp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;squareup/okhttp 레포&lt;/a&gt; 이슈 탭에서 찾을 수 있었습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;2017년에 누군가 해당 에러를 받고 요청본문을 보낼 수 있도록 허용해달라는 이슈였는데 코멘트 중에서 이유를 찾을 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;a href=&quot;https://github.com/square/okhttp/issues/3154&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/square/okhttp/issues/3154&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728573728400&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;java.lang.IllegalArgumentException: method GET must not have a request body. &amp;middot; Issue #3154 &amp;middot; square/okhttp&quot; data-og-description=&quot;What kind of issue is this? Question. This issue tracker is not the place for questions. If you want to ask how to do something, or to understand why something isn't working the way you expect it t...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/square/okhttp/issues/3154&quot; data-og-url=&quot;https://github.com/square/okhttp/issues/3154&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PHYtZ/hyXhXRfQqh/b4JJOlalwuBjl85yhHI9r1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/YIMXD/hyXhPFGsDV/hOugNHAskry4xAu5qhftzk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/square/okhttp/issues/3154&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/square/okhttp/issues/3154&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PHYtZ/hyXhXRfQqh/b4JJOlalwuBjl85yhHI9r1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/YIMXD/hyXhPFGsDV/hOugNHAskry4xAu5qhftzk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;java.lang.IllegalArgumentException: method GET must not have a request body. &amp;middot; Issue #3154 &amp;middot; square/okhttp&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What kind of issue is this? Question. This issue tracker is not the place for questions. If you want to ask how to do something, or to understand why something isn't working the way you expect it t...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rDZ0i/btsJZTXZ3ka/EiH3gLO8tg4KHxmDqu08E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rDZ0i/btsJZTXZ3ka/EiH3gLO8tg4KHxmDqu08E1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rDZ0i/btsJZTXZ3ka/EiH3gLO8tg4KHxmDqu08E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrDZ0i%2FbtsJZTXZ3ka%2FEiH3gLO8tg4KHxmDqu08E1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;368&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코멘트를 해석하면 아래와 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;GET 요청이 의미 없는 요청 본문을 가질 수 있도록 허용하는 것은 상당한 비용을 초래합니다.&lt;br /&gt;&lt;br /&gt;- 캐싱이 예측할 수 없게 작동함: 요청 본문이 포함된 GET 요청은 캐시 동작에 부정적인 영향을 미칠 수 있습니다. HTTP 캐시는 GET 요청의 응답을 저장하고 다시 사용할 수 있도록 설계되었지만, 본문이 있으면 캐시의 일관성을 해칠 수 있습니다.&lt;br /&gt;- 인터셉터가 충돌하거나 리소스를 누수할 수 있음: HTTP 요청의 본문을 다루는 과정에서 인터셉터가 예상치 못한 동작을 하게 되어, 시스템의 안정성이 저하될 수 있습니다.요청 본문이 &lt;br /&gt;- HTTP/1 연결 풀에 영향을 미쳐 요청 조작 공격을 허용할 수 있음: HTTP/1에서는 요청과 응답이 명확히 구분되어야 합니다. 요청 본문이 포함되면, 이 구분이 흐려져 보안 취약점이 발생할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;코멘트를 조금 더 읽어보면 여러 개발자들이 RFC 에서도 제약하고 있지 않은데 너무하다고들 하고 있습니다 ㅠㅠ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;개인적으로 아래 코멘트가 너무 웃펐습니다. &quot;해줘.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V2v5M/btsJ2bJblg6/zRDNlcT33XY0ZxMLpCAQdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V2v5M/btsJ2bJblg6/zRDNlcT33XY0ZxMLpCAQdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V2v5M/btsJ2bJblg6/zRDNlcT33XY0ZxMLpCAQdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV2v5M%2FbtsJ2bJblg6%2FzRDNlcT33XY0ZxMLpCAQdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;144&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 현재까지도 계속해서 GET 요청시 요청 본문을 허용하지 않고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 RestClient 뿐만아니라 OpenFeign 이나 다른 고수준 HTTP 클라이언트를 사용해도 동일할 것입니다.&lt;/p&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 GET 요청에 요청 본문을 포함시킨적이 없습니다. 그러면 어디서 포함된걸까요??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3) 도대체 어디서 요청 body 가 포함된걸까요?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한번 제가 개발했던 순서를 따라가면서 소거하다보면 마지막으로 의심가는 부분이 한가지 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 로깅 인터셉터를 추가하면서 추가한 &lt;span style=&quot;background-color: #fafafa; color: #63b175; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #63b175; text-align: start;&quot;&gt;BufferingClientHttpRequestFactory&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #63b175; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;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;span style=&quot;text-align: start;&quot;&gt;위에서 간단하게 언급했다시피 &lt;span style=&quot;background-color: #fafafa; color: #63b175; text-align: start;&quot;&gt;BufferingClientHttpRequestFactory&lt;/span&gt;&lt;span style=&quot;color: #63b175; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt; 는 RestClient에서 HTTP 요청을 수행할 때 사용하는 클래스로, &lt;b&gt;요청 본문을 메모리에 버퍼링하여 전체 요청 본문을 읽고 전송할 수 있도록 지원합니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 기능&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;버퍼링&lt;/b&gt;: 요청 본문을 한 번에 메모리에 읽어서 전송하므로, 다른 인터셉터나 요청 후킹 로직에서 본문을 필요할 때 다시 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능&lt;/b&gt;: 버퍼링을 통해 요청 본문을 여러 번 읽을 수 있지만, 메모리 사용량이 증가할 수 있으므로 대용량 데이터 전송 시 주의가 필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인터셉터와의 호환성&lt;/b&gt;: 요청 본문을 버퍼링하면, 여러 인터셉터가 순차적으로 실행될 때 동일한 본문에 접근할 수 있게 되어, 더 유연한 요청 처리 로직을 구현할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앗. 그러면 이 때 의심해볼 수 있습니다. &lt;span style=&quot;background-color: #fafafa; color: #63b175; text-align: start;&quot;&gt;BufferingClientHttpRequestFactory &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;너가 요청보낼 때 body 를 보내고 있는거구나?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;AS.png&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/otWte/btsJ1f6EhJU/e5RhNBlDlN89aiVjJeWva0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/otWte/btsJ1f6EhJU/e5RhNBlDlN89aiVjJeWva0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/otWte/btsJ1f6EhJU/e5RhNBlDlN89aiVjJeWva0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FotWte%2FbtsJ1f6EhJU%2Fe5RhNBlDlN89aiVjJeWva0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;420&quot; data-filename=&quot;AS.png&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도대체 어디에서 body 를 만들어서 보내고 있는건지 디버깅을 통해 코드를 추적했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;BufferingClientHttpRequestWrapper&quot; href=&quot;https://github.com/spring-projects/spring-framework/blob/v6.1.4/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BufferingClientHttpRequestWrapper&lt;/a&gt; 클래스 (스프링 6.1.4 버전입니다.) 에 들어가보면 &lt;b&gt;&lt;u&gt;executeInternal&lt;/u&gt;&lt;/b&gt; 이라는 메서드가 있는데 이곳에서 어떤 스트리밍 Http 메세지든 &lt;u&gt;&lt;b&gt;setBody&lt;/b&gt;&lt;/u&gt; 를 하고 있는 현장을 목격할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cj8l36/btsJ0h5h99M/oswrOvKHALp9AkGKbV3dk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cj8l36/btsJ0h5h99M/oswrOvKHALp9AkGKbV3dk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cj8l36/btsJ0h5h99M/oswrOvKHALp9AkGKbV3dk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcj8l36%2FbtsJ0h5h99M%2FoswrOvKHALp9AkGKbV3dk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;499&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;1066&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring 컨트리뷰터의 꿈을 안고 해당 이슈가 고쳐졌는지 확인해 보았습니다만 당연히 이미 아주 전에 고쳐져 있었습니다. ^.ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4월에 누군가 &lt;a title=&quot;이슈레이징&quot; href=&quot;https://github.com/spring-projects/spring-framework/issues/32612&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이슈레이징&lt;/a&gt;을 했고 바로 다음 패치 버전으로 수정(&lt;a title=&quot;수정된 commit&quot; href=&quot;https://github.com/spring-projects/spring-framework/issues/32612&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;수정된 commit&lt;/a&gt;)되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1946&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JRup4/btsJ06PGA2Q/HdRZ9ufLQYfihHNlKcJbFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JRup4/btsJ06PGA2Q/HdRZ9ufLQYfihHNlKcJbFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JRup4/btsJ06PGA2Q/HdRZ9ufLQYfihHNlKcJbFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJRup4%2FbtsJ06PGA2Q%2FHdRZ9ufLQYfihHNlKcJbFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;875&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1946&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정된 코드는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bufferedOutput 의 길이가 0 이상일 때만 &lt;u&gt;&lt;b&gt;setBody&lt;/b&gt;&lt;/u&gt; 를 호출하도록 조건이 추가되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1664&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5YLWI/btsJ1aRWxou/NCyVe6mRIwpsjVxqY6FuFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5YLWI/btsJ1aRWxou/NCyVe6mRIwpsjVxqY6FuFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5YLWI/btsJ1aRWxou/NCyVe6mRIwpsjVxqY6FuFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5YLWI%2FbtsJ1aRWxou%2FNCyVe6mRIwpsjVxqY6FuFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;551&quot; data-origin-width=&quot;1664&quot; data-origin-height=&quot;1066&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;4) 해결&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 코드가 수정되었으니 해결방법은 간단했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링 부트 버전을 올리면 됩니다&amp;hellip; 3.2.3 에서 GA 인 3.2.10 으로 버전업&lt;/b&gt;을 하니 해결되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(운영중인 서버의 버전을 올리는게 쉬운일은 아니지만 이미 3.2.3 으로 올리면서 많은 시행착오를 겪었기 때문에 패치 버전을 올리는 건 문제가 되지 않았습니다&amp;hellip;)&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;3. 추가적인 문제&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- Spring Boot 3.2.0 버전에서 Okhttp3 이 deprecated 됨&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ClientHttpRequestFactories 클래스에서 OkHttp 클래스를 확인하면 @Deprecated 어노테이션 마크가 붙어있는 것을 볼 수 있습니다. 따라서 RestClient 뿐만아니라 RestTemplate, WebClient 도 동일할 것으로 보입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kQpNg/btsJ4oVQ3Qy/YMRWKacvBvGOErnaIrIMVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kQpNg/btsJ4oVQ3Qy/YMRWKacvBvGOErnaIrIMVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kQpNg/btsJ4oVQ3Qy/YMRWKacvBvGOErnaIrIMVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkQpNg%2FbtsJ4oVQ3Qy%2FYMRWKacvBvGOErnaIrIMVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;391&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;816&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring-boot 3.2.0 버전 부터 deprecated 된 것을 볼 수 있는데 왜 deprecated 시켰을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 또한 이슈탭에서 찾아볼 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf56I8/btsJ2K0Tq3w/maqaI96ZR8sXMkCcWnEQAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf56I8/btsJ2K0Tq3w/maqaI96ZR8sXMkCcWnEQAK/img.png&quot; data-alt=&quot;https://github.com/spring-projects/spring-framework/issues/30919&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf56I8/btsJ2K0Tq3w/maqaI96ZR8sXMkCcWnEQAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf56I8%2FbtsJ2K0Tq3w%2FmaqaI96ZR8sXMkCcWnEQAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;427&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/spring-projects/spring-framework/issues/30919&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리해보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Okhttp4 가 출시되었고 이는 Okhttp3 과 호환은 가능하지만 Kotlin 런타임이 필요함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 런타임에 Kotlin 을 요구하기 때문에 Java 사용에는 덜 적합하다는 판단&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- (Okhttp4 는 Kotlin 으로 개발됨 (안드로이드 진영에 완전히 맞춰가고 있는 것으로 보임))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Okhttp5 는 현재 진행중이지만 이전 버전과 호환되지 않을 예정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Jetty, Netty 등 좋은 대안이 있으니 사용하기 바람&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 spring 에서 Okhttp5 를 오피셜하게 지원하지 않는 이상 다른 저수준 http client 라이브러리를 사용하는 것을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SimpleClientHttpRequestFactory 의 경우 운영에서 사용하기에 적합하지 않다는 의견들이 많아 다른 라이브러리를 사용하는 것이 좋을 것 같습니다.&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;(저의 경우, 다른 대안을 사용하더라도 Retrofit 를 아직 제거할 수 없기 때문에 방향성에 대해서 조금 더 생각해보기로 했습니다.)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;4. 마무리&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 주저리 주저리 작성하면서 알게된 것을 요약하면 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. ClientHttpRequestFactories.get() 을 사용하게되면 지원하는 클라이언트에 한해서 프로젝트에 주입되어있는 저수준 http client 라이브러리를 자동으로 사용한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Okhttp 에서는 GET 요청에서 body 를 포함하는 것을 허용하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 요청/응답을 로깅하기위한 BufferingClientHttpRequestFactory 에서 body 가 없어도 포함시키는 로직은 스프링 6.1.6 버전에서 포함시키지 않도록 수정되었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Spring 에서는 Okhttp3 클라이언트를 코틀린 호환 문제로(대표하면) deprecated 시켰다.&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;사실 제목에는 RestClient 를 포함했지만 BufferingClientHttpRequestFactory 를 사용하는 RestTemplate 에서도 특정 버전 이하에서 동일한 현상이 발생할 것이기 때문에 주의해서 사용할 필요가 있습니다. 만약 버전업이 가능하지 않은 환경의 경우 다른 방법을 찾아본다거나 사용하는 라이브러리를 교체한다든지의 작업이 필요할 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;느낀점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우 버전을 올릴 수 있는 환경이었기 때문에 쉽게 이슈를 해결할 수 있었지만 만일 그렇지 못한 상황이었다면 조금 머리가 아팠을 것 같습니다&amp;hellip;.. (RestClient 는 도입했고 도입한 기능의 데드라인이 쪼여오고&amp;hellip;.)&lt;/p&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>Spring/Spring</category>
      <category>okhttp3</category>
      <category>okhttp3 deprecated</category>
      <category>RESTCLIENT</category>
      <category>springboot</category>
      <category>springboot restclient</category>
      <category>스프링부트</category>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/172</guid>
      <comments>https://bin-repository.tistory.com/172#entry172comment</comments>
      <pubDate>Fri, 11 Oct 2024 00:59:26 +0900</pubDate>
    </item>
    <item>
      <title>삶의 지도</title>
      <link>https://bin-repository.tistory.com/171</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;해당 글은 글또 10기를 지원하면서 작성한 삶의 지도입니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;딱히 하고싶은게 없어서 정보통신과를 왔어요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;학창시절의 저는 딱히 하고싶은게 없고 친구들과 놀기를 좋아하는 어른들앞에선 조용한 그런아이였습니다. 부모님이 성적에 크게 관심을 가지지도 않았고 그런저런 성적에 맞춰서 전문대 정보통신과에 진학했습니다. 간단히 말하면 소프트웨어 / 전기공학 반반을 다루는 과였어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;과정이 끝난 후 같이 교육받았던 친구들은 관련업계로 취업하거나 다른 쪽으로 취업했는데 저는 뭔가 아쉬운 생각이 들었습니다. 그러다 문 득 고등학교 3학년 때 담임선생님이 진로상담에서 해주셨던 말씀이 생각났어요. &lt;b&gt;&amp;ldquo;ㅇㅇ아, 하고싶은게 없어도 너무 조급해 하지 않아도돼. 사람마다 다 때가있고, 쌤이 아시는 분도 뒤늦게 하고싶은게 생겨서 대학원가고 잘지내고있으니 너도 그 때 해도 늦지않아&amp;rdquo;&lt;/b&gt; 그때는 단순하게 당장 고민하지 않아도 됐던게 좋았는데 지금 생각해보니 엄청나게 힘이되는 말이더라구요. 그래서 결심합니다. 대학을 다시 가기로.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;편입생의 혼란스러운 졸업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하고싶은게 있으면 또 하는 성격이라 (그거만 하는게 문제지만&amp;hellip;) 열심히 공부해서 인서울 4년제 대학 컴퓨터공학과로 편입했습니다. 그때가 23살이었고 제가 다시 대학을 옴으로써 얻고자 했던 것은 크게 두가지였어요. 첫번 째, 전공지식. 두번 째, 다양한 경험을 가진 사람들과의 교류. 하지만, 편입을 하게되면 3학년 수업부터 듣게되는데 알고는 있었지만 모든 과정을 건너띄고 전공수업부터 듣게되니 &amp;lsquo;어? 이거 뭔가 잘못됐다.&amp;rsquo; 라는 생각이 들었습니다.&lt;/p&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;생각만큼 순탄하지 않은 생활을 보내던 중 동아리를 들었습니다. 게임 동아리였는데 학교 수업을 떠나서 진짜 자기가 하고싶은 것들을 하는 사람들이 모인 곳이더라구요. 이미 실제로 서비스를 운영하고 있는 친구들도 있었고 진짜 딥하고 로우한 사이드 프로젝트를 하는 친구들도 있었습니다. 여기서 또 2차 좌절을 했습니다. &amp;lsquo;내가 이렇게 까지 밤낮없이 개발할 정도로 개발을 좋아하는사람인가?&amp;rsquo; 계속 나에게 묻고 결국 전 동아리를 나오게됐습니다.&lt;/p&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;뭔가 절차대로 (쉬움 &amp;rarr; 중간 &amp;rarr; 어려움) 공부할 수 없는 상황, 우선순위를 어떻게 정해야할 지 모르는 상황속에서 방황만하다가 겨우 졸업을 하긴 했습니다. (그냥 가만히 있으면 졸업시켜주는줄 알았는데 그 때 인생에서 첫번 째로 쓴맛을 보았네요  )&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스타트업 부터 시작해보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;한참 취업하기 위해서 이력서를 쓰던 때가 개발자 붐이 일어났을 때였습니다. 여러 대기업에도 지원을 했는데 면접 공부하면서 GSAT 나 NCS 를 공부하기가 쉽지는 않았습니다. 구르면서 배워보자는 취지로 여러 스타트업들에도 지원을 했고 그 중 블록체인 스타트업에서 백엔드 개발자로 일하게 되었습니다. (그 때 처음 go 라는 언어를 사용했는데 아직까지 관심이 있을 정도로 정말 매력적인 언어인 것 같아요.) 3개월 근무 후 아쉽게도 정규직 전환에는 실패했습니다. 그리고 또 다시 취준생활을 하던 중, 첫 회사에서 같이 근무했었던 사수분이 다른 회사로 이직하신 후 저를 불러주셔서 두번 째 스타트업 회사에 취업을 하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그곳에서는 약 2년반 정도 근무를 했는데 근무하면서 파이썬 장고, 노드 nestjs, 코프링 등 CTO 가 바뀔 때마다 언어와 프레임워크도 같이 바뀌었고 다양한 경험을 했습니다. poc 프로젝트도 많이 진행했고 기획자와 함께 기획 리뷰 하면서 런칭도 해보았습니다. 이 과정에서 학교 다닐 때보다 많은 흥미를 느꼈고 역시 실무 개발이 재밌다라고 느꼈던것 같습니다. 기술적인 것을 논의할 사수가 없다보니 1년 정도 됐을 때 &lt;b&gt;우매함의 봉우리&lt;/b&gt; 끝에 있었고 뭐든지 할 수 있을 것만 같았어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-17 오후 6.10.47.png&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;1130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTWb1g/btsJETCjO52/AXzQKeU30GnPZtku634GFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTWb1g/btsJETCjO52/AXzQKeU30GnPZtku634GFK/img.png&quot; data-alt=&quot;주니어개발자 그래프로 많이 쓰이는 더닝-크루거 효과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTWb1g/btsJETCjO52/AXzQKeU30GnPZtku634GFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTWb1g%2FbtsJETCjO52%2FAXzQKeU30GnPZtku634GFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;410&quot; data-filename=&quot;스크린샷 2024-09-17 오후 6.10.47.png&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;1130&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;주니어개발자 그래프로 많이 쓰이는 더닝-크루거 효과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사가 사업을 조금 확장함에 따라 미들급 개발자들을 증원하면서 저는 &lt;b&gt;절망의 계곡&lt;/b&gt;에 빠지게 됩니다. 이 때 코틀린, 스프링 부트로 기술스택을 변경했는데 저에게 러닝커브도 너무 높았고 쏟아지는 지식의 바다에서 길을 어떻게 나아가야할지 많은 고민을 했던 것 같습니다. 하지만 다행히 최고의 복지는 동료라고 좋은 동료들이 길잡이 역할을 잘 해줬던 것 같아요. 그 때 DDD 나 헥사고날 아키텍쳐, TDD 등 아키텍쳐에 대한 관심이 많이 생겼던 것 같습니다.&lt;/p&gt;
&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;h2 data-ke-size=&quot;size26&quot;&gt;그렇게 현재는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇게 이직한 현재 회사에서는 트래픽도 적지않고 카프카에 레디스, 웹소켓도 운영하고 있어 정말 다양한 경험을 했습니다. 주도적으로 한 피쳐를 맡아서 배포하기도 하고 사용해보지 않은 라이브러리를 사용한다던지, 동시성 이슈나 이벤트 처리 등 대용량 트래픽환경의 대한 설계도 진행했습니다. 이렇게 마음에 드는 환경에 이직하게 되었는데도 처음엔 낯선환경과 쏟아지는 업무사이에서 어떤일부터 시작하고 어떤 것부터 공부해야할지 몰라 또 이직을 고민했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그 때 넥스트스텝이라는 교육사이트에서 &lt;b&gt;커리어 NEXTSTEP&lt;/b&gt; 이라는 과정을 수강하면서 현재 업무를 잘 정리하고 내가 성장할 수 있는 방향대로 이끌어가는 방법을 배우게되었습니다. 일을 하기전에 항상 문서로 전체 프로세스를 정리하고 그 과정에서 우선순위를 정할 수 있었어요. 그 과정에서, 다양한 도메인의 개발자들과 이야기도 하게되면서 시야도 많이 트이고 인사이트도 얻게되었습니다. 그러다보니 일이 익숙해진 지금도 계속해서 배울 수 있는 점을 찾아내고 정리하면서 성장해 나가고 있어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 삶의 지도를 작성하다보니 2023년 하반기, 2024년 상반기는 깨달음이 많았네요. 확실히 절망의 계곡에서는 빠져나온것 같은데 깨달음의 오르막을 오르는 것이 굉장이 힘이들다는 생각이 듭니다.&lt;/p&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;a href=&quot;https://jojoldu.tistory.com/743&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;재능있는척하지않기&lt;/a&gt;&amp;nbsp;라는 글을 보게 되었는데 특히 &lt;b&gt;노력하지 않는 척 할 수록 나는 진짜 재능 없는 못하는 사람이 되었다&lt;/b&gt;. 라는 대목이 너무 와닿았습니다. 평소에 저는 정말 모르는 것 외에는 동료들에게 질문하지않았고 평일저녁과 주말에 어떠한 노력을 하는지 아무에게도 알리지 않았습니다. (물론 tmi 라고 생각해서 알리지 않은 것도 있었지만 ㅎㅎ) 내가 공부하는 주제에 대해서 더 높은 기대치를 바라고 잘못된 답을 할까봐 그게 두려워서 였던 것 같습니다.&lt;/p&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;b&gt;커리어 NEXTSTEP&lt;/b&gt; 에서 만나신 분이 알려주셨는데요. 저에게 꼭 10기를 지원해보라고 하셔서 계속 기다렸습니다. 혼자서 글쓰기는 이제 조금 심심한 것 같아서요. 생각을 글로 계속 정리하는 습관을 기르고 다양한 사람들을 만날 수 있는 환경이 기대가 됩니다!&lt;/p&gt;</description>
      <category>나의 이야기 &amp;amp; 회고</category>
      <category>글또</category>
      <category>글또10기</category>
      <category>백엔드개발자</category>
      <category>삶의지도</category>
      <category>주니어개발자</category>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/171</guid>
      <comments>https://bin-repository.tistory.com/171#entry171comment</comments>
      <pubDate>Tue, 17 Sep 2024 19:11:46 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] @Async 애노테이션에서 커스텀 스레드풀 이름을 설정해줬을 때 따로 빈으로 등록하지않아도 스프링컨테이너가 구동된다구??</title>
      <link>https://bin-repository.tistory.com/170</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 발단&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 커스텀 스레드풀을 빈으로 등록해주지 않고 &lt;code&gt;@Async(&quot;customThreadPoolExecutor&quot;)&lt;/code&gt; 를 메서드에 붙여주고 스프링 컨테이너를 실행했을 때 당연히 빈을 못찾아서 실행부터 오류가 날거라고 생각했다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Service
public class MyService {

    @Async(&quot;customTaskExecutor&quot;)
    public void performAsyncTask() {
        // 비즈니스 로직
        System.out.println(&quot;비동기 작업 실행: &quot; + Thread.currentThread().getName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 스프링 컨테이너는 잘 실행되었고 해당 기능을 호출하기 전까지 아무런 문제없이 구동되고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3032&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O02n3/btsJClUjUYp/h296HztkgKXn5MjhceHKG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O02n3/btsJClUjUYp/h296HztkgKXn5MjhceHKG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O02n3/btsJClUjUYp/h296HztkgKXn5MjhceHKG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO02n3%2FbtsJClUjUYp%2Fh296HztkgKXn5MjhceHKG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;198&quot; data-origin-width=&quot;3032&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 기능이 호출되었을 때에서야 에러가 발생되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3418&quot; data-origin-height=&quot;1332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dU6FuM/btsJDIAiwCN/f2ZtZVm4fPPpYVsDaL3B7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dU6FuM/btsJDIAiwCN/f2ZtZVm4fPPpYVsDaL3B7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dU6FuM/btsJDIAiwCN/f2ZtZVm4fPPpYVsDaL3B7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdU6FuM%2FbtsJDIAiwCN%2Ff2ZtZVm4fPPpYVsDaL3B7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;281&quot; data-origin-width=&quot;3418&quot; data-origin-height=&quot;1332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. @Async 의 동작과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 &lt;code&gt;@Async&lt;/code&gt; 의 동작과정을 간단히 요약해보면 다음과 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 과정 요약&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;@Async&lt;/code&gt; 어노테이션이 붙은 메서드가 호출되면, 스프링 AOP가 메서드 호출을 프록시로 가로챈다.&lt;/li&gt;
&lt;li&gt;프록시는 따로 정의한 &lt;code&gt;Executor&lt;/code&gt;(커스텀 스레드풀)이나 없을 경우 SimpleThreadPoolExecutor 를 참조한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Executor&lt;/code&gt;는 해당 메서드를 비동기 작업으로 스레드풀에 제출한다.&lt;/li&gt;
&lt;li&gt;스레드풀의 스레드 중 하나가 메서드를 실행하여 비동기 처리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 발단에서의 현상을 살펴보면 2번 과정에서 정의한 커스텀 스레드풀 (bean) 이 없기때문에 &lt;code&gt;NoSuchBeanDefinitionException&lt;/code&gt; 이 발생했음을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 커스텀 스레드풀 빈은 어떻게 등록되는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Async 어노테이션에서 커스텀 스레드풀을 등록하고 싶다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 아래와 같이 커스텀 Configuration 클래스에서 따로 스레드풀 설정을 진행한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    @Bean(name = &quot;customTaskExecutor&quot;)
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix(&quot;Custom-Executor-&quot;);
        executor.initialize();
        return executor;
    }
}&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;스프링에서는 처음 구동될 때 AnnotationConfigApplicationContext 와 GenericApplicationContext 클래스에서 빈을 등록하고 관리하는 과정을 담당한다. 이 때, &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ConfigurationClassPostProcessor.html&quot;&gt;ConfigurationClassPostProcessor&lt;/a&gt; 에서 &lt;code&gt;@Configuration&lt;/code&gt;, &lt;code&gt;@Bean&lt;/code&gt;과 같은 스프링의 설정 관련 어노테이션을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 설명하자면, @Bean 어노테이션이 붙은 메서드 빈같은 경우 리턴타입이 빈의 타입이 되고 해당 빈을 주입받게되면 메서드를 invoke 하여 실행되는 방식이다. 더 자세한 설명은 &lt;code&gt;ConfigurationClassPostProcessor&lt;/code&gt; 소스코드를 살펴보면 좋다. (org.springframework.context.annotation.ConfigurationClassPostProcessor 패키지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Config 클래스에서 따로 설정해주게 되면 빈으로 등록되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 그렇다면 @Async 은 어떻게 등록되고 관리되는데?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한번 정리해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Async&lt;/code&gt;는 스프링에서 &lt;b&gt;비동기 메서드 실행&lt;/b&gt;을 관리하는 어노테이션으로, 스프링이 제공하는 &lt;b&gt;AOP(Aspect-Oriented Programming)&lt;/b&gt;를 통해 동작한다. 이 어노테이션의 작동 방식과 스레드풀 관리는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 작동방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 컨테이너가 구동될 때, &lt;code&gt;@EnableAsync&lt;/code&gt; 어노테이션을 통해 &lt;b&gt;비동기 처리를 위한 설정&lt;/b&gt;이 활성화된다. 이때 스프링은 &lt;code&gt;@Async&lt;/code&gt;를 처리하기 위한 &lt;b&gt;프록시 객체&lt;/b&gt;를 생성한다. 즉, &lt;code&gt;@Async&lt;/code&gt;가 붙은 메서드는 비동기 실행을 위해 AOP 프록시로 감싸지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이 구동되면서 &lt;code&gt;@Async&lt;/code&gt;를 처리할 수 있도록 다음과 같은 과정을 거친다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;@EnableAsync&lt;/code&gt;&lt;/b&gt;: &lt;code&gt;@EnableAsync&lt;/code&gt;는 스프링의 비동기 기능을 활성화한다. 이 과정에서 &lt;code&gt;AsyncAnnotationBeanPostProcessor&lt;/code&gt;를 등록하고 비동기 처리에 필요한 설정을 진행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AOP 프록시 생성&lt;/b&gt;: 스프링 컨테이너는 &lt;code&gt;@Async&lt;/code&gt;가 붙은 빈을 감싸는 AOP 프록시를 생성합니다. 이 프록시는 메서드 호출을 가로채고, 비동기 스레드풀을 통해 메서드를 비동기로 실행합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 커스텀 스레드풀 이름을 정의하지 않은 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Async&lt;/code&gt; 어노테이션에 특정한 스레드풀 이름을 지정하지 않으면, 스프링은 기본적으로 &lt;b&gt;전역적으로 설정된 기본 스레드풀&lt;/b&gt;을 사용한다. 이때 기본적으로 사용되는 스레드풀은 &lt;b&gt;&lt;code&gt;SimpleAsyncTaskExecutor&lt;/code&gt;&lt;/b&gt;이다. 이 스레드풀은 특별한 스레드풀 관리를 하지 않고, 호출 시마다 새로운 스레드를 생성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;커스텀 스레드풀을 정의하지 않거나 잘못된 스레드풀 이름을 사용한 경우&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 &lt;code&gt;@Async(&quot;customThreadPoolName&quot;)&lt;/code&gt;에서 &lt;code&gt;&quot;customThreadPoolName&quot;&lt;/code&gt;이라는 이름의 빈이 스프링 컨텍스트에 존재하지 않으면, *&lt;i&gt;&lt;code&gt;NoSuchBeanDefinitionException&lt;/code&gt;&lt;/i&gt;이 발생한다. 스프링은 스레드풀 이름으로 해당 빈을 찾기 때문에, 정의하지 않은 이름을 사용할 경우 스프링 컨테이너가 구동 중에 이를 인식하지 못하고 예외가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음 그렇다면 스프링컨테이너 구동시 정의된 커스텀 스레드풀 빈이 없다면 에러가 발생해야하는 것이 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 언급했던 &lt;code&gt;AsyncAnnotationBeanPostProcessor&lt;/code&gt; 에 대해서 조금 더 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그전에 ProxyAsyncConfiguration 에 대해서 짚고 넘어갈 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. ProxyAsyncConfiguration&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;package org.springframework.scheduling.annotation&lt;/code&gt; 에 존재하는 ProxyAsyncConfiguration 클래스를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(spring6.1.12 버전이다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;1152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cstefv/btsJBNjxtYs/TGpGu3L0ak7vOVLZgg0GuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cstefv/btsJBNjxtYs/TGpGu3L0ak7vOVLZgg0GuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cstefv/btsJBNjxtYs/TGpGu3L0ak7vOVLZgg0GuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcstefv%2FbtsJBNjxtYs%2FTGpGu3L0ak7vOVLZgg0GuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;453&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;1152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;asyncAdvisor() 메서드에서는 아래와 같은 동작과정을 진행한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;빈 정의&lt;/b&gt;: &lt;code&gt;ProxyAsyncConfiguration&lt;/code&gt; 클래스는 &lt;code&gt;AsyncAnnotationBeanPostProcessor&lt;/code&gt; 를 통해 빈을 정의한다. 이 빈은 비동기 메서드 호출을 처리하기 위한 AOP 프록시를 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설정 적용&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;AsyncAnnotationBeanPostProcessor&lt;/code&gt;는 비동기 작업을 처리하기 위해 스레드풀과 예외 처리기를 설정한다.&lt;/li&gt;
&lt;li&gt;사용자 정의 비동기 어노테이션 타입이 지정된 경우, 해당 어노테이션 타입을 설정한다.&lt;/li&gt;
&lt;li&gt;프록시 생성 시 클래스 기반 프록시를 사용할지, 프록시의 순서를 설정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프록시 생성&lt;/b&gt;: &lt;code&gt;AsyncAnnotationBeanPostProcessor&lt;/code&gt;는 스프링 컨테이너가 초기화될 때 &lt;code&gt;@Async&lt;/code&gt; 어노테이션이 붙은 메서드를 감싸는 프록시를 생성한다. 이 프록시는 비동기 메서드 호출을 처리하여 비동기적으로 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. AsyncAnnotationBeanPostProcessor&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AsyncAnnotationBeanPostProcesor&lt;/code&gt; 에서는 setBeanFactory를 통해 실제 Adivosr(PointCut, Advice)와 @Annotation 을 등록한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zktWP/btsJB88Klyf/rubbMuKo7hrmMWJ7tJkDp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zktWP/btsJB88Klyf/rubbMuKo7hrmMWJ7tJkDp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zktWP/btsJB88Klyf/rubbMuKo7hrmMWJ7tJkDp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzktWP%2FbtsJB88Klyf%2FrubbMuKo7hrmMWJ7tJkDp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;179&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;442&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;2078&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlrvcz/btsJDzpT30M/uJfB7pVAs3IPX3CuIgBPgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlrvcz/btsJDzpT30M/uJfB7pVAs3IPX3CuIgBPgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlrvcz/btsJDzpT30M/uJfB7pVAs3IPX3CuIgBPgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdlrvcz%2FbtsJDzpT30M%2FuJfB7pVAs3IPX3CuIgBPgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;90&quot; data-origin-width=&quot;2078&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런뒤에 AbstractAdvisingBeanPostProcessor 클래스의 postProcessAfterInitialization 메서드에서 advisor 가 등록된다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;  public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (this.advisor != null &amp;amp;&amp;amp; !(bean instanceof AopInfrastructureBean)) {
      if (bean instanceof Advised) {
        Advised advised = (Advised)bean;
        if (!advised.isFrozen() &amp;amp;&amp;amp; this.isEligible(AopUtils.getTargetClass(bean))) {
          if (this.beforeExistingAdvisors) {
            advised.addAdvisor(0, this.advisor);
          } else {
            if (advised.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE &amp;amp;&amp;amp; advised.getAdvisorCount() &amp;gt; 0) {
              advised.addAdvisor(advised.getAdvisorCount() - 1, this.advisor);
              return bean;
            }

            advised.addAdvisor(this.advisor);
          }

          return bean;
        }
      }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2320&quot; data-origin-height=&quot;1378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfacCw/btsJDFwN8xU/UBlXeeGLAHraROfTZKGAe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfacCw/btsJDFwN8xU/UBlXeeGLAHraROfTZKGAe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfacCw/btsJDFwN8xU/UBlXeeGLAHraROfTZKGAe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfacCw%2FbtsJDFwN8xU%2FUBlXeeGLAHraROfTZKGAe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;428&quot; data-origin-width=&quot;2320&quot; data-origin-height=&quot;1378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 비동기 어노테이션의 초기화 설정 관련된 부분이다. (단계가 많이 스킵되었다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 실제 @Async 어노테이션을 붙인 메서드의 호출은 어떤식으로 이루어질까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 비동기 호출 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정에서 AsyncAnnotationAdvisor 의 buildAdvisor() 메서드에서 AnnotationAsyncExecutionInterceptor 객체를 생성하고 configure 해주는 코드를 볼 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. AnnotationAsyncExecutionInterceptor&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AnnotationAsyncExecutionInterceptor 는 AsyncExecutionInterceptor 를 상속받고 AsyncExecutionInterceptor 는 비동기 메서드 호출을 위한 &lt;b&gt;기본 AOP 인터셉터 역할&lt;/b&gt;을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 AnnotationAsyncExecutionInterceptor 는 위에서 등록된 advisor 로 proxy 객체에 접근할 때 해당하는 advisor 가 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;1164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZSZit/btsJCJ1jBaG/TVfcD983K5FpMJ8yCdERP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZSZit/btsJCJ1jBaG/TVfcD983K5FpMJ8yCdERP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZSZit/btsJCJ1jBaG/TVfcD983K5FpMJ8yCdERP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZSZit%2FbtsJCJ1jBaG%2FTVfcD983K5FpMJ8yCdERP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;461&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;1164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;invoke메서드는 determineAsyncExectuor 에 의해 Async 전략을 정한뒤 해당 Thread를 실행시켜 Callback메서드를 받아온다. 기본적으로 Async는 Bean이 등록된 상태로 Advice를 진행하게 된다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);
Pointcut mpc = new AnnotationMatchingPointcut(null, asyncAnnotationType, true);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같이 클래스, 메서드 대상으로 pointCut 을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 이제 거의 다왔다. 바로 determineAsyncExectuor 메서드 구현을 확인함으로써 발단이 되었던 궁금증을 해결할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. determineAsyncExectuor()&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;determineAsyncExecutor(Method method)&lt;/code&gt; 메서드는 스프링의 비동기 메서드(&lt;code&gt;@Async&lt;/code&gt; 어노테이션이 붙은 메서드)를 실행할 때 사용할 적절한 &lt;code&gt;AsyncTaskExecutor&lt;/code&gt;를 결정하는 메서드이다. 이 메서드는 메서드에 지정된 스레드풀을 찾고, 적절한 스레드풀을 캐싱하여 반복적인 요청에 효율적으로 대응한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;1458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBx7xP/btsJCw2myS9/yQUsWfGVOVMPEXKwWpJjqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBx7xP/btsJCw2myS9/yQUsWfGVOVMPEXKwWpJjqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBx7xP/btsJCw2myS9/yQUsWfGVOVMPEXKwWpJjqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBx7xP%2FbtsJCw2myS9%2FyQUsWfGVOVMPEXKwWpJjqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;570&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;1458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세부동작을 조금 더 알아보자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 캐시된 &lt;code&gt;AsyncTaskExecutor&lt;/code&gt; 확인&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저, 메서드별로 캐시된 &lt;code&gt;AsyncTaskExecutor&lt;/code&gt; 가 있는지 &lt;code&gt;executors&lt;/code&gt; 맵에서 확인한다. 캐시된 &lt;code&gt;executor&lt;/code&gt;가 있으면 이를 반환하고, 없으면 다음 단계로 넘어간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;AsyncTaskExecutor executor = (AsyncTaskExecutor)this.executors.get(method);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 스레드풀 이름(qualifier) 가져오기&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;String qualifier = this.getExecutorQualifier(method);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@Async&lt;/code&gt; 어노테이션에 지정된 스레드풀 이름(qualifier)을 가져온다. 커스텀 스레드풀을 만들었을 경우 메서드나 클래스에 명시된 &lt;code&gt;@Async&lt;/code&gt; 어노테이션에서 &lt;code&gt;value()&lt;/code&gt; 속성으로 스레드풀 이름이 정의되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 스레드풀 결정&lt;/b&gt;: ⭐⭐⭐⭐⭐&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드풀 이름이 지정되어 있으면(&lt;code&gt;qualifier&lt;/code&gt; 값이 존재하는 경우):
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;findQualifiedExecutor&lt;/code&gt; 메서드를 통해, &lt;code&gt;beanFactory&lt;/code&gt;에서 지정된 이름에 해당하는 &lt;code&gt;Executor&lt;/code&gt; 빈을 찾는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;targetExecutor = this.findQualifiedExecutor(this.beanFactory, qualifier);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드풀 이름이 지정되어 있지 않으면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 설정된 &lt;code&gt;Executor&lt;/code&gt;(defaultExecutor)를 가져온다. 이는 &lt;code&gt;@Async&lt;/code&gt; 어노테이션에 스레드풀 이름이 명시되지 않은 경우 사용할 기본 스레드풀이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;targetExecutor = (Executor)this.defaultExecutor.get();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, &lt;code&gt;qualifier&lt;/code&gt; 값이 존재하는경우 즉 스레드풀 이름이 지정되어있을 때 &lt;code&gt;Executor&lt;/code&gt; 빈을 찾지 못하면 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 스프링 컨테이너 초기화 때는 @Async 의 value 로 지정한 executor 빈 이름까지 확인하지 않고 실제 호출시(invoke() 메서드)에 확인해서 그때 그때 Executor 를 조회해오고 있음을 알 수 있다. 그렇기 때문에 당연히 커스텀 스레드풀 빈을 정의하지 않아도 구동시에는 에러가 발생하지 않았던 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@EnableAsync 어노테이션을 적용한 뒤 스프링 컨테이너를 실행시키면, 초기화 과정에서&lt;code&gt;AsyncAnnotationBeanPostProcessor&lt;/code&gt; 에서 &lt;code&gt;@Async&lt;/code&gt; 어노테이션이 붙은 메서드를 감싸는 프록시를 생성하지만 value 로 정의된 Executor 빈을 찾는 과정은 실제 비동기 메서드 호출시에 수행된다.&lt;/li&gt;
&lt;li&gt;따라서, 스레드풀 빈을 정의하지 않고 @Async 어노테이션에 value 를 지정해도 스프링 컨테이너는 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러가 발생했을 때 trace 를 확인해보면 에러가 발생하는 지점을 잘 알수가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2852&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXQzcV/btsJCJmTjtd/mduyFOn7yiQb6XHxK4wEtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXQzcV/btsJCJmTjtd/mduyFOn7yiQb6XHxK4wEtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXQzcV/btsJCJmTjtd/mduyFOn7yiQb6XHxK4wEtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXQzcV%2FbtsJCJmTjtd%2FmduyFOn7yiQb6XHxK4wEtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;170&quot; data-origin-width=&quot;2852&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 호출하는 시점에 스레드풀을 찾으려고 하기 때문에 런타임에 에러가 발생하는게 아닐까라고 짐작은 하고있었지만 그렇다면 스프링 컨테이너 초기화 과정에서는 어디까지를 진행하는거지? 의 대한 궁금증으로 해당 포스팅을 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 소스코드를 분석하면서 다시한번 정말 많은 BeanPostProcessor 가 있구나 알게되었고, 비동기 어노테이션은 어떻게 동작하고 관리하는지에 대해 정리할 수 있어서 좋은 시간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드 분석은 &lt;a href=&quot;https://velog.io/@jeongyunsung/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B4%EB%B6%80%ED%95%99-Async-EnableAsync-AsyncAnnotationBeanPostProcessor&quot;&gt;https://velog.io/@jeongyunsung/스프링부트-해부학-Async-EnableAsync-AsyncAnnotationBeanPostProcessor&lt;/a&gt; 요 블로그를 많이 참고했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 해당 블로그 마지막에 쓰여있는 Quiz 부분도 읽어보면 매우 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 보너스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Async 어노테이션 인터페이스 소스코드를 보면 @Reflective 어노테이션을 발견할 수 있다. 이 어노테이션이 하는일은 뭘까?&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package org.springframework.scheduling.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective
public @interface Async {
  String value() default &quot;&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Reflective&lt;/code&gt; 애노테이션은 &lt;b&gt;Spring Framework&lt;/b&gt;에서 제공하는 애노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히는 &lt;b&gt;Spring AOT&lt;/b&gt;(Ahead of Time) 힌트 시스템의 일부로 사용된다고한다. Spring AOT는 주로 &lt;b&gt;Native Image&lt;/b&gt;를 만들기 위한 &lt;b&gt;GraalVM&lt;/b&gt;과의 호환성 작업에서 사용되며, 런타임에 필요한 리플렉션, 프록시, 리소스 등을 미리 분석하여 빌드 타임에 힌트를 제공함으로써 실행 성능을 최적화하려는 목적으로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 3.x 이전에는 &lt;b&gt;GraalVM&lt;/b&gt;과 관련된 지원이 없었고, &lt;b&gt;Native Image&lt;/b&gt; 같은 개념도 존재하지 않았다고 한다. 대신, 전통적인 &lt;b&gt;JVM&lt;/b&gt; 환경에서 애플리케이션이 실행되었으며, Spring은 JVM의 동적 기능을 활용한 방식으로 동작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 3.x 이후 부터 &lt;b&gt;GraalVM&lt;/b&gt; 지원이 시작되면서, GraalVM을 사용해 &lt;b&gt;네이티브 이미지&lt;/b&gt;를 생성할 수 있게 되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GraalVM 및 Native Image&lt;/b&gt;: GraalVM은 &lt;b&gt;Ahead-of-Time(AOT)&lt;/b&gt; 컴파일러로, Java 애플리케이션을 네이티브 바이너리로 컴파일할 수 있다. 이를 통해 실행 성능과 메모리 사용량이 크게 최적화되며, 특히 컨테이너 환경이나 서버리스 환경에서 빠른 시작 속도가 필요할 때 유리하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spring Native 및 AOT 지원&lt;/b&gt;: Spring은 GraalVM을 사용하여 네이티브 이미지를 만들 때 동적 리플렉션, 프록시 생성, 리소스 로딩 등과 같은 기존 JVM 기능을 사용할 수 없다는 제약이 있었다. 이를 해결하기 위해 Spring AOT 지원이 도입되었고, 리플렉션 및 동적 기능에 대한 힌트를 제공하는 방식으로 네이티브 이미지에서도 동작할 수 있도록 변경되었습니다. 이때 &lt;code&gt;@Reflective&lt;/code&gt; 같은 애노테이션이 등장하여 AOT 컴파일에서 필요한 힌트를 제공하게 되었다고한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring</category>
      <category>@Async</category>
      <category>비동기호출</category>
      <category>스프링</category>
      <category>스프링부트</category>
      <category>스프링부트 비동기</category>
      <category>스프링비동기</category>
      <author>피곤핑</author>
      <guid isPermaLink="true">https://bin-repository.tistory.com/170</guid>
      <comments>https://bin-repository.tistory.com/170#entry170comment</comments>
      <pubDate>Fri, 13 Sep 2024 17:08:49 +0900</pubDate>
    </item>
  </channel>
</rss>