https://eeeasycode.devGatsbyJSTue, 25 Nov 2025 14:33:52 GMThttps://eeeasycode.dev/mysql-index-comparison/https://eeeasycode.dev/mysql-index-comparison/Sun, 22 Jun 2025 00:00:00 GMT<p>느려터진 쿼리, 도대체 왜일까요?</p> <p>많은 경우, 적절하지 않은 인덱스 구조가 원인입니다.<br> 특히 InnoDB를 사용하는 MySQL에서는 <strong>Clustered Index와 Non-Clustered Index</strong>의 차이를 제대로 이해하지 않으면, 성능을 개선하기 어렵습니다.</p> <p>이번 글에서는 두 인덱스의 구조적 차이, 장단점, 그리고 실제 쿼리 설계 시 어떤 기준으로 선택해야 하는지를 정리해보겠습니다.</p> <hr> <h2 id="Clustered-Index란" style="position:relative;"><a href="#Clustered-Index%EB%9E%80" aria-label="Clustered Index란 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Clustered Index란?</h2> <p>Clustered Index는 <strong>데이터가 인덱스 자체에 포함되어 물리적으로 정렬</strong>되는 방식입니다.<br> InnoDB에서는 테이블당 하나만 가질 수 있으며, 보통 <strong>기본 키(PK)</strong> 가 해당 역할을 합니다.</p> <h3 id="특징" style="position:relative;"><a href="#%ED%8A%B9%EC%A7%95" aria-label="특징 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>특징</h3> <ul> <li><strong>하나만 생성 가능</strong>: 데이터 자체가 인덱스의 일부이기 때문에 테이블당 1개만 설정 가능</li> <li><strong>데이터 정렬 포함</strong>: 인덱스의 순서 = 데이터 정렬 순서</li> <li><strong>빠른 범위 쿼리</strong>에 유리: 연속된 값을 조회하는 쿼리에 매우 빠름</li> </ul> <h3 id="예시" style="position:relative;"><a href="#%EC%98%88%EC%8B%9C" aria-label="예시 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>예시</h3> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token keyword">user</span> <span class="token punctuation">(</span> id <span class="token keyword">BIGINT</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token comment">-- id가 자동으로 Clustered Index가 됨</span> name <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">,</span> created_at <span class="token keyword">DATETIME</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>이 경우, <code class="language-text">id</code> 기준으로 데이터가 디스크에 정렬되어 저장됩니다.</p> <hr> <h2 id="Non-Clustered-Index란" style="position:relative;"><a href="#Non-Clustered-Index%EB%9E%80" aria-label="Non Clustered Index란 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Non-Clustered Index란?</h2> <p>Non-Clustered Index는 <strong>인덱스와 데이터가 분리되어 저장</strong>됩니다.<br> 보조 인덱스로서 사용되며, <strong>데이터 위치를 가리키는 포인터를 포함</strong>합니다.</p> <h3 id="특징-1" style="position:relative;"><a href="#%ED%8A%B9%EC%A7%95-1" aria-label="특징 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>특징</h3> <ul> <li><strong>여러 개 생성 가능</strong>: 다양한 쿼리 조건에 맞게 인덱스 생성 가능</li> <li><strong>데이터 참조 필요</strong>: 인덱스만으로 값을 조회할 수 없으면 테이블을 한 번 더 읽어야 함</li> <li><strong>정렬 없음</strong>: 물리적 정렬이 없으므로 삽입/삭제가 빠름</li> </ul> <h3 id="예시-1" style="position:relative;"><a href="#%EC%98%88%EC%8B%9C-1" aria-label="예시 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>예시</h3> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">INDEX</span> idx_user_name <span class="token keyword">ON</span> <span class="token keyword">user</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>이 인덱스는 <code class="language-text">name</code>을 기준으로 정렬된 인덱스 트리를 만들지만, 실제 데이터는 Clustered Index에 위치해 있으므로 <strong>데이터를 추가로 참조해야</strong> 합니다.</p> <hr> <h2 id="EXPLAIN으로-보는-차이" style="position:relative;"><a href="#EXPLAIN%EC%9C%BC%EB%A1%9C-%EB%B3%B4%EB%8A%94-%EC%B0%A8%EC%9D%B4" aria-label="EXPLAIN으로 보는 차이 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>EXPLAIN으로 보는 차이</h2> <p>아래 쿼리를 <code class="language-text">EXPLAIN</code>으로 실행해보면 차이를 확인할 수 있습니다:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token comment">-- Clustered Index를 사용하는 쿼리</span> <span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> <span class="token keyword">user</span> <span class="token keyword">WHERE</span> id <span class="token operator">BETWEEN</span> <span class="token number">1</span> <span class="token operator">AND</span> <span class="token number">100</span><span class="token punctuation">;</span> <span class="token comment">-- Non-Clustered Index를 사용하는 쿼리</span> <span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> <span class="token keyword">user</span> <span class="token keyword">WHERE</span> name <span class="token operator">=</span> <span class="token string">'Alice'</span><span class="token punctuation">;</span></code></pre></div> <ul> <li>첫 번째 쿼리는 데이터 자체가 <code class="language-text">id</code>로 정렬되어 있어 빠른 접근이 가능합니다.</li> <li>두 번째 쿼리는 <code class="language-text">name</code> 인덱스를 따라가서 <code class="language-text">id</code>를 찾아야 하므로, <strong>인덱스 → 테이블</strong> 두 번 접근합니다.</li> </ul> <hr> <h2 id="비교표-Clustered-vs-Non-Clustered" style="position:relative;"><a href="#%EB%B9%84%EA%B5%90%ED%91%9C-Clustered-vs-Non-Clustered" aria-label="비교표 Clustered vs Non Clustered permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>비교표: Clustered vs Non-Clustered</h2> <table> <thead> <tr> <th>항목</th> <th>Clustered Index</th> <th>Non-Clustered Index</th> </tr> </thead> <tbody> <tr> <td>정렬 방식</td> <td>데이터가 인덱스 기준으로 정렬됨</td> <td>정렬된 인덱스 구조, 데이터는 별도</td> </tr> <tr> <td>생성 가능 개수</td> <td>1개만 생성 가능</td> <td>여러 개 생성 가능</td> </tr> <tr> <td>저장 공간</td> <td>추가 공간 거의 없음</td> <td>인덱스 공간 외에도 데이터 참조 필요</td> </tr> <tr> <td>조회 속도</td> <td>빠름 (범위 조회에 특히 유리)</td> <td>조건에 따라 느릴 수 있음</td> </tr> <tr> <td>삽입/삭제 성능</td> <td>느릴 수 있음 (정렬 유지 필요)</td> <td>빠름 (정렬 없음)</td> </tr> </tbody> </table> <hr> <h2 id="언제-어떤-인덱스를-써야-할까" style="position:relative;"><a href="#%EC%96%B8%EC%A0%9C-%EC%96%B4%EB%96%A4-%EC%9D%B8%EB%8D%B1%EC%8A%A4%EB%A5%BC-%EC%8D%A8%EC%95%BC-%ED%95%A0%EA%B9%8C" aria-label="언제 어떤 인덱스를 써야 할까 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>언제 어떤 인덱스를 써야 할까?</h2> <ul> <li> <p><strong>Clustered Index 추천</strong></p> <ul> <li>PK 기반으로 데이터 조회가 대부분일 때</li> <li>범위 쿼리(<code class="language-text">BETWEEN</code>, <code class="language-text">ORDER BY</code>)가 자주 사용될 때</li> <li>정렬된 데이터 저장이 이점이 될 때</li> </ul> </li> <li> <p><strong>Non-Clustered Index 추천</strong></p> <ul> <li>다양한 검색 조건을 대응해야 할 때</li> <li>여러 필드에 대해 인덱스를 걸어야 할 때</li> <li>삽입/삭제가 자주 일어나는 테이블일 때</li> </ul> </li> </ul> <hr> <h2 id="결론" style="position:relative;"><a href="#%EA%B2%B0%EB%A1%A0" aria-label="결론 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>결론</h2> <p>Clustered Index와 Non-Clustered Index는 단순히 "있으면 좋은" 것이 아니라, <strong>성능에 직결되는 중요한 설계 요소</strong>입니다.</p> <p>쿼리 성능이 고민이라면, 지금 사용하는 인덱스 구조가 쿼리 목적에 맞는지 꼭 점검해보세요.</p> <blockquote> <p>여러분은 어떤 기준으로 인덱스를 설계하시나요?</p> </blockquote>https://eeeasycode.dev/resolve-concurrency-issue/https://eeeasycode.dev/resolve-concurrency-issue/Sun, 22 Jun 2025 00:00:00 GMT<p>실제 운영 중인 서비스에서 가장 당황스러운 순간 중 하나는 <strong>"어? 중복 결제 처리된 것 같은데요?"</strong> 라는 제보를 받을 때입니다.</p> <p>분명히 로직상으로는 문제가 없어 보이는데, 동시에 여러 요청이 들어오면서 발생하는 <strong>동시성 이슈</strong>는 예상치 못한 곳에서 터져 나오곤 합니다.</p> <p>저 역시 과거에 서비스 개발 중 이런 동시성 문제를 마주쳤고, <strong>Redis를 활용한 분산 락</strong>으로 해결한 경험을 공유해보려고 합니다. 문제 상황부터 단계별 해결 과정에 대한 내용들을 담았습니다.</p> <p>먼저 Redis의 특징부터 간단히 살펴보겠습니다.</p> <h2 id="Redis-특징-살펴보기" style="position:relative;"><a href="#Redis-%ED%8A%B9%EC%A7%95-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0" aria-label="Redis 특징 살펴보기 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Redis 특징 살펴보기</h2> <p>Redis는 다음과 같은 특징을 가지고 있습니다.</p> <ul> <li><strong>인메모리 NoSQL 데이터베이스</strong>로 빠른 응답 속도 제공</li> <li>초당 약 <strong>10만회의 명령 실행</strong> (CPU에 따라 5만 ~ 25만회)</li> <li>기본적으로 <strong>Key-Value 저장 구조</strong>이며, List/Set/Hash 등 다양한 자료구조 지원</li> <li><strong>싱글 스레드</strong> 기반으로 한번에 하나의 명령어만 실행</li> </ul> <p>이러한 특징들 덕분에 DB Layer의 부하 분산과 빠른 응답을 위한 <strong>Cache Layer</strong>로 널리 사용되고 있습니다.</p> <h2 id="Redis-Strings-명령어" style="position:relative;"><a href="#Redis-Strings-%EB%AA%85%EB%A0%B9%EC%96%B4" aria-label="Redis Strings 명령어 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Redis Strings 명령어</h2> <p>Redis는 기본적으로 Key-Value 구조입니다. Value에 사용되는 자료구조에 따라 다양한 기능을 제공하며, 모든 데이터에 <strong>유효기간(TTL)을 설정</strong>할 수 있어 효율적인 메모리 관리가 가능합니다.</p> <p>동시성 이슈 해결에 핵심이 되는 <strong>String 명령어</strong>들을 살펴보겠습니다.</p> <h3 id="SET--GET--DEL-명령어" style="position:relative;"><a href="#SET--GET--DEL-%EB%AA%85%EB%A0%B9%EC%96%B4" aria-label="SET GET DEL 명령어 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>SET / GET / DEL 명령어</h3> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token comment"># key-value 구조로 string 정보 저장</span> SET key value <span class="token punctuation">[</span>NX <span class="token operator">|</span> XX<span class="token punctuation">]</span> <span class="token punctuation">[</span>GET<span class="token punctuation">]</span> <span class="token punctuation">[</span>EX seconds <span class="token operator">|</span> PX milliseconds <span class="token operator">|</span> EXAT unix-time-seconds <span class="token operator">|</span> PXAT unix-time-milliseconds <span class="token operator">|</span> KEEPTTL<span class="token punctuation">]</span> <span class="token comment"># 옵션 설명</span> <span class="token comment"># NX: 이전에 저장된 내용이 없는 경우에만 저장</span> <span class="token comment"># XX: 이전에 저장된 경우에만 저장</span> <span class="token comment"># GET: 해당 key에 대한 이전 value를 반환</span> <span class="token comment"># EX: 해당 정보가 유지되는 시간 (second 단위)</span> <span class="token comment"># PX: 해당 정보가 유지되는 시간 (millisecond 단위)</span></code></pre></div> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token comment"># 저장한 value값 가져오기</span> GET key</code></pre></div> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token comment"># 저장한 정보 삭제하기</span> DEL key <span class="token punctuation">[</span>key <span class="token punctuation">..</span>.<span class="token punctuation">]</span></code></pre></div> <h3 id="활용-예시" style="position:relative;"><a href="#%ED%99%9C%EC%9A%A9-%EC%98%88%EC%8B%9C" aria-label="활용 예시 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>활용 예시</h3> <ul> <li>SET으로 단순 값 저장 후 GET으로 불러오기</li> <li>기존에 존재하지 않는 key인지 확인 후, 존재하지 않으면 value 저장</li> <li>기존에 존재하는 key인지 확인 후, 존재하면 value 저장</li> <li>color라는 key의 red라는 value를 3초간 유지하도록 저장</li> </ul> <p>위 명령어들을 이해했다면, 이제 본격적으로 <strong>Redis를 통한 동시성 이슈 해결</strong>에 도전해보겠습니다.</p> <h2 id="동시성-이슈-상황-재현" style="position:relative;"><a href="#%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%EC%83%81%ED%99%A9-%EC%9E%AC%ED%98%84" aria-label="동시성 이슈 상황 재현 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>동시성 이슈 상황 재현</h2> <p>예시 시나리오로 가상의 <strong>온라인 쇼핑몰 재고 관리 시스템</strong>을 활용해 설명해보겠습니다.</p> <h3 id="시나리오" style="position:relative;"><a href="#%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4" aria-label="시나리오 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>시나리오</h3> <ul> <li>한정판 스니커즈의 현재 재고: <strong>10개</strong></li> <li>고객이 주문하려는 수량: <strong>1개</strong></li> <li>예상 결과: 주문 완료 후 재고 <strong>9개</strong></li> </ul> <h3 id="서버-로직-흐름" style="position:relative;"><a href="#%EC%84%9C%EB%B2%84-%EB%A1%9C%EC%A7%81-%ED%9D%90%EB%A6%84" aria-label="서버 로직 흐름 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>서버 로직 흐름</h3> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">1. DB에서 상품의 현재 재고 조회 (10개 확인) 2. 현재 재고 >= 주문 수량이면 주문 테이블에 주문 정보 insert 3. 재고 테이블에서 주문 수량만큼 차감 (-1개) 4. 나머지 주문 처리 로직 진행</code></pre></div> <h3 id="문제-상황-코드" style="position:relative;"><a href="#%EB%AC%B8%EC%A0%9C-%EC%83%81%ED%99%A9-%EC%BD%94%EB%93%9C" aria-label="문제 상황 코드 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>문제 상황 코드</h3> <p>DB Select와 Insert는 <code class="language-text">setTimeout()</code>을 통한 더미 로직으로 구현했습니다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> productInventory <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment">// DB의 재고 정보</span> stock<span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">wait</span> <span class="token operator">=</span> timeToDelay <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name"><span class="token builtin">Promise</span></span><span class="token punctuation">(</span>resolve <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> timeToDelay<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token function-variable function">getCurrentStock</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span> <span class="token comment">// select에 0.2초 소요된다고 가정</span> <span class="token keyword">return</span> productInventory<span class="token punctuation">.</span>stock <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">decreaseStock</span> <span class="token operator">=</span> <span class="token keyword">async</span> quantity <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token number">300</span><span class="token punctuation">)</span> <span class="token comment">// update에 0.3초 소요된다고 가정</span> productInventory<span class="token punctuation">.</span>stock <span class="token operator">-=</span> quantity <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">order</span><span class="token punctuation">(</span>quantity<span class="token punctuation">,</span> req<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Starting... Req"</span><span class="token punctuation">,</span> req<span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">getCurrentStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">>=</span> quantity<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">decreaseStock</span><span class="token punctuation">(</span>quantity<span class="token punctuation">)</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Req </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>req<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, currentStock: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">await</span> <span class="token function">getCurrentStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Req </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>req<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, failed - insufficient stock</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"There was an error:"</span><span class="token punctuation">,</span> err<span class="token punctuation">.</span>message<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// 중복 Request 시뮬레이션</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">2</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">order</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="문제-발생" style="position:relative;"><a href="#%EB%AC%B8%EC%A0%9C-%EB%B0%9C%EC%83%9D" aria-label="문제 발생 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>문제 발생!</h3> <p>앱에서 구매 버튼 클릭 시 <strong>중복 요청</strong>이 발생하면서 다음과 같은 상황이 벌어졌습니다.</p> <p><img src="" alt=""></p> <p><img src="" alt=""></p> <p><strong>결과</strong>: 한정판 스니커즈 재고가 10개였는데 동시 주문으로 인해 재고가 <strong>8개</strong>가 되어버렸습니다! 즉, 실제로는 2개의 주문이 처리된 상황입니다.</p> <p>이런 <strong>재고 부족 상황</strong>을 어떻게 해결할 수 있을까요?</p> <h2 id="해결-방법-1---NX-옵션-활용" style="position:relative;"><a href="#%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-1---NX-%EC%98%B5%EC%85%98-%ED%99%9C%EC%9A%A9" aria-label="해결 방법 1 NX 옵션 활용 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>해결 방법 1 - NX 옵션 활용</h2> <p>Redis의 <strong>싱글 스레드</strong> 특성과 SET 명령어의 <strong>NX 옵션</strong>을 활용해 간단한 락을 구현할 수 있습니다.</p> <h3 id="기본-아이디어" style="position:relative;"><a href="#%EA%B8%B0%EB%B3%B8-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4" aria-label="기본 아이디어 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>기본 아이디어</h3> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token comment"># key는 상품 ID와 주문 프로세스 조합</span> <span class="token comment"># value는 더미값 "lock", 유효기간은 1초</span> SET product:sneakers:order-lock <span class="token string">"lock"</span> NX EX <span class="token number">1</span></code></pre></div> <p><strong>NX 옵션</strong>: 해당 key가 존재하지 않을 때만 값을 저장합니다.</p> <h3 id="구현-코드" style="position:relative;"><a href="#%EA%B5%AC%ED%98%84-%EC%BD%94%EB%93%9C" aria-label="구현 코드 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>구현 코드</h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> Redis <span class="token operator">=</span> <span class="token keyword">require</span><span class="token punctuation">(</span><span class="token string">"ioredis"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// version: 4.27.9</span> <span class="token keyword">const</span> redis <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Redis</span><span class="token punctuation">(</span><span class="token punctuation">{</span> port<span class="token operator">:</span> <span class="token number">16.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">,</span> host<span class="token operator">:</span> <span class="token string">"redis-..."</span><span class="token punctuation">,</span> family<span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token comment">// ipv4</span> password<span class="token operator">:</span> <span class="token string">"83z..."</span><span class="token punctuation">,</span> db<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productInventory <span class="token operator">=</span> <span class="token punctuation">{</span> stock<span class="token operator">:</span> <span class="token number">10</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">wait</span> <span class="token operator">=</span> <span class="token punctuation">(</span>timeToDelay<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name"><span class="token builtin">Promise</span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>resolve<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> timeToDelay<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">getCurrentStock</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> productInventory<span class="token punctuation">.</span>stock<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">decreaseStock</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>quantity<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token number">300</span><span class="token punctuation">)</span><span class="token punctuation">;</span> productInventory<span class="token punctuation">.</span>stock <span class="token operator">-=</span> quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">order</span><span class="token punctuation">(</span>quantity<span class="token punctuation">,</span> req<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Starting... Req"</span><span class="token punctuation">,</span> req<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> redis<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'product:sneakers:order-lock'</span><span class="token punctuation">,</span> <span class="token string">'lock'</span><span class="token punctuation">,</span> <span class="token string">'NX'</span><span class="token punctuation">,</span> <span class="token string">'EX'</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result <span class="token operator">===</span> <span class="token string">'OK'</span> <span class="token operator">&amp;&amp;</span> <span class="token keyword">await</span> <span class="token function">getCurrentStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">>=</span> quantity<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">decreaseStock</span><span class="token punctuation">(</span>quantity<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Req </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>req<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, currentStock: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">await</span> <span class="token function">getCurrentStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Req </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>req<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, failed - insufficient stock</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"There was an error:"</span><span class="token punctuation">,</span> err<span class="token punctuation">.</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> redis<span class="token punctuation">.</span><span class="token function">disconnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">2</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">order</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="결과" style="position:relative;"><a href="#%EA%B2%B0%EA%B3%BC" aria-label="결과 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>결과</h3> <p><img src="" alt=""></p> <p><img src="" alt=""></p> <p>두 번의 동일한 요청에 대해 <strong>NX 옵션을 통한 락</strong>이 정상적으로 동작함을 확인할 수 있습니다!</p> <h3 id="하지만-문제가-있습니다" style="position:relative;"><a href="#%ED%95%98%EC%A7%80%EB%A7%8C-%EB%AC%B8%EC%A0%9C%EA%B0%80-%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4" aria-label="하지만 문제가 있습니다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>하지만 문제가 있습니다...</h3> <p>안타깝게도 위 방식은 <strong>불완전한 해결책</strong>입니다. 중복 요청이 너무 많이 발생하여 <strong>락의 유효기간 이후</strong>에 들어오는 요청들은 막을 수 없기 때문입니다.</p> <p><img src="" alt=""></p> <h2 id="해결-방법-2---Redlock-적용" style="position:relative;"><a href="#%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-2---Redlock-%EC%A0%81%EC%9A%A9" aria-label="해결 방법 2 Redlock 적용 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>해결 방법 2 - Redlock 적용</h2> <p>Redis에서는 <strong>Expire 기능</strong>을 활용하여 안정성이 보장된 **분산 락 프로토콜(Distributed Locking Protocol)**인 <strong>Redlock</strong>을 제공합니다.</p> <h3 id="Redlock의-특징" style="position:relative;"><a href="#Redlock%EC%9D%98-%ED%8A%B9%EC%A7%95" aria-label="Redlock의 특징 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Redlock의 특징</h3> <ul> <li><strong>SPOF(Single Point of Failure) 방지</strong>를 위해 최소 3개 이상의 독립적인 Redis 인스턴스 구성 권장</li> <li>더 안정적이고 신뢰할 수 있는 분산 락 구현</li> </ul> <h3 id="Redlock-알고리즘" style="position:relative;"><a href="#Redlock-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98" aria-label="Redlock 알고리즘 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Redlock 알고리즘</h3> <ol> <li><strong>현재 시간</strong>을 밀리초 단위로 가져옵니다</li> <li><strong>모든 Redis 인스턴스</strong>에서 동일한 key와 난수값으로 순차적으로 락을 획득합니다</li> <li><strong>락 획득 소요 시간</strong>을 계산하여, 대부분의 인스턴스에서 락을 획득하고 소요 시간이 유효시간보다 짧은 경우에만 락 획득으로 간주합니다</li> <li><strong>락 유효시간</strong>은 초기 설정 시간에서 경과 시간을 뺀 값으로 계산합니다</li> <li>락 획득에 실패하면 <strong>모든 Redis 인스턴스</strong>에서 락을 해제합니다</li> </ol> <h3 id="구현-코드-1" style="position:relative;"><a href="#%EA%B5%AC%ED%98%84-%EC%BD%94%EB%93%9C-1" aria-label="구현 코드 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>구현 코드</h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> Redis <span class="token operator">=</span> <span class="token keyword">require</span><span class="token punctuation">(</span><span class="token string">"ioredis"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// version: 4.27.9</span> <span class="token keyword">const</span> Redlock <span class="token operator">=</span> <span class="token keyword">require</span><span class="token punctuation">(</span><span class="token string">"redlock"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// version: 4.2.0</span> <span class="token keyword">const</span> redis1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Redis</span><span class="token punctuation">(</span><span class="token punctuation">{</span> port<span class="token operator">:</span> <span class="token number">16.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">,</span> host<span class="token operator">:</span> <span class="token string">"redis-..."</span><span class="token punctuation">,</span> family<span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token comment">// ipv4</span> password<span class="token operator">:</span> <span class="token string">"83z..."</span><span class="token punctuation">,</span> db<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> redis2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Redis</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> redis3 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Redis</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> redlock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Redlock</span><span class="token punctuation">(</span> <span class="token punctuation">[</span>redis1<span class="token punctuation">,</span> redis2<span class="token punctuation">,</span> redis3<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> driftFactor<span class="token operator">:</span> <span class="token number">0.01</span><span class="token punctuation">,</span> <span class="token comment">// clock drift 보상을 위한 driftTime 계산 요소</span> retryCount<span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token comment">// 에러 전까지 재시도 최대 횟수</span> retryDelay<span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span> <span class="token comment">// 각 시도 간 간격(ms)</span> retryJitter<span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span> <span class="token comment">// 재시도 시 추가되는 최대 시간(ms)</span> automaticExtensionThreshold<span class="token operator">:</span> <span class="token number">500</span><span class="token punctuation">,</span> <span class="token comment">// 락 연장 전 최소 남아야 할 시간(ms)</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productInventory <span class="token operator">=</span> <span class="token punctuation">{</span> stock<span class="token operator">:</span> <span class="token number">10</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">wait</span> <span class="token operator">=</span> <span class="token punctuation">(</span>timeToDelay<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name"><span class="token builtin">Promise</span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>resolve<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> timeToDelay<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">getCurrentStock</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> productInventory<span class="token punctuation">.</span>stock<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">decreaseStock</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>quantity<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token number">300</span><span class="token punctuation">)</span><span class="token punctuation">;</span> productInventory<span class="token punctuation">.</span>stock <span class="token operator">-=</span> quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">order</span><span class="token punctuation">(</span>quantity<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"redlock Starting..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> lock <span class="token operator">=</span> <span class="token keyword">await</span> redlock<span class="token punctuation">.</span><span class="token function">acquire</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"product-order-lock"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">getCurrentStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">>=</span> quantity<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">decreaseStock</span><span class="token punctuation">(</span>quantity<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'currentStock: '</span><span class="token punctuation">,</span> <span class="token keyword">await</span> <span class="token function">getCurrentStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">await</span> lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"There was an error:"</span><span class="token punctuation">,</span> err<span class="token punctuation">.</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> redis1<span class="token punctuation">.</span><span class="token function">disconnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> redis2<span class="token punctuation">.</span><span class="token function">disconnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> redis3<span class="token punctuation">.</span><span class="token function">disconnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">order</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="결과-1" style="position:relative;"><a href="#%EA%B2%B0%EA%B3%BC-1" aria-label="결과 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>결과</h3> <p><img src="" alt=""></p> <p>성공적으로 동시성 이슈를 해결할 수 있게 되었습니다. 10번의 중복 요청에도 불구하고 한 번만 실행되는 것을 확인할 수 있습니다.</p> <h2 id="적용-가이드" style="position:relative;"><a href="#%EC%A0%81%EC%9A%A9-%EA%B0%80%EC%9D%B4%EB%93%9C" aria-label="적용 가이드 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>적용 가이드</h2> <h3 id="언제-어떤-방법을-선택할까" style="position:relative;"><a href="#%EC%96%B8%EC%A0%9C-%EC%96%B4%EB%96%A4-%EB%B0%A9%EB%B2%95%EC%9D%84-%EC%84%A0%ED%83%9D%ED%95%A0%EA%B9%8C" aria-label="언제 어떤 방법을 선택할까 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>언제 어떤 방법을 선택할까?</h3> <table> <thead> <tr> <th><strong>상황</strong></th> <th><strong>추천 방법</strong></th> <th><strong>이유</strong></th> </tr> </thead> <tbody> <tr> <td><strong>간단한 중복 방지</strong></td> <td>NX 옵션</td> <td>구현이 단순하고 성능이 좋음</td> </tr> <tr> <td><strong>중요한 비즈니스 로직</strong></td> <td>Redlock</td> <td>높은 안정성과 신뢰성 보장</td> </tr> <tr> <td><strong>고가용성이 필요한 서비스</strong></td> <td>Redlock</td> <td>SPOF 방지 및 장애 대응 가능</td> </tr> </tbody> </table> <h3 id="적용-시-주의사항" style="position:relative;"><a href="#%EC%A0%81%EC%9A%A9-%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD" aria-label="적용 시 주의사항 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>적용 시 주의사항</h3> <h4 id="1-락-타임아웃-설정" style="position:relative;"><a href="#1-%EB%9D%BD-%ED%83%80%EC%9E%84%EC%95%84%EC%9B%83-%EC%84%A4%EC%A0%95" aria-label="1 락 타임아웃 설정 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>1. <strong>락 타임아웃 설정</strong></h4> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token comment">// 너무 짧으면 - 정상 처리 중에도 락이 해제될 수 있음</span> <span class="token comment">// 너무 길면 - 장애 시 복구가 늦어짐</span> <span class="token keyword">const</span> <span class="token constant">LOCK_TIMEOUT</span> <span class="token operator">=</span> <span class="token number">5000</span> <span class="token comment">// 5초 정도가 적절</span></code></pre></div> <h4 id="2-락-키-설계" style="position:relative;"><a href="#2-%EB%9D%BD-%ED%82%A4-%EC%84%A4%EA%B3%84" aria-label="2 락 키 설계 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>2. <strong>락 키 설계</strong></h4> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token comment">// 사용자별, 액션별로 세분화하여 락의 범위를 최소화</span> <span class="token keyword">const</span> lockKey <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">lock:user:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>userId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:action:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>actionType<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>resourceId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span></code></pre></div> <h4 id="3-예외-처리" style="position:relative;"><a href="#3-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC" aria-label="3 예외 처리 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>3. <strong>예외 처리</strong></h4> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">businessLogic</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> lock <span class="token keyword">try</span> <span class="token punctuation">{</span> lock <span class="token operator">=</span> <span class="token keyword">await</span> redlock<span class="token punctuation">.</span><span class="token function">acquire</span><span class="token punctuation">(</span><span class="token punctuation">[</span>lockKey<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token constant">LOCK_TIMEOUT</span><span class="token punctuation">)</span> <span class="token comment">// 비즈니스 로직 실행</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">.</span>name <span class="token operator">===</span> <span class="token string">"LockError"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"이미 처리 중인 요청입니다."</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">throw</span> error <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>lock<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token builtin">console</span><span class="token punctuation">.</span>error<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="성능-고려사항" style="position:relative;"><a href="#%EC%84%B1%EB%8A%A5-%EA%B3%A0%EB%A0%A4%EC%82%AC%ED%95%AD" aria-label="성능 고려사항 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>성능 고려사항</h3> <h4 id="Redis-인스턴스-개수" style="position:relative;"><a href="#Redis-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EA%B0%9C%EC%88%98" aria-label="Redis 인스턴스 개수 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Redis 인스턴스 개수</h4> <ul> <li><strong>단일 인스턴스</strong>: 빠르지만 SPOF 존재</li> <li><strong>3개 인스턴스</strong>: 안정성과 성능의 균형점</li> <li><strong>5개 이상</strong>: 과도한 네트워크 오버헤드 발생 가능</li> </ul> <h4 id="네트워크-지연" style="position:relative;"><a href="#%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%A7%80%EC%97%B0" aria-label="네트워크 지연 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>네트워크 지연</h4> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token comment">// 각 Redis 인스턴스별 타임아웃 설정</span> <span class="token keyword">const</span> redis <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Redis</span><span class="token punctuation">(</span><span class="token punctuation">{</span> host<span class="token operator">:</span> <span class="token string">"redis-host"</span><span class="token punctuation">,</span> connectTimeout<span class="token operator">:</span> <span class="token number">1000</span><span class="token punctuation">,</span> <span class="token comment">// 연결 타임아웃</span> lazyConnect<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 지연 연결</span> maxRetriesPerRequest<span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token comment">// 재시도 횟수</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div> <h2 id="마무리하며" style="position:relative;"><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC%ED%95%98%EB%A9%B0" aria-label="마무리하며 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>마무리하며</h2> <p>이번 글에서는 실제 서비스에서 발생할 수 있는 <strong>동시성 이슈</strong>를 <strong>Redis를 활용한 분산 락</strong>으로 해결하는 과정을 살펴봤습니다.</p> <blockquote> <p><strong>핵심 포인트</strong> <br/></p> <ul> <li>단순한 NX 옵션도 많은 경우에 충분히 효과적</li> <li>중요한 비즈니스 로직에서는 Redlock을 활용한 안정적인 분산 락 구현</li> <li>락 타임아웃과 키 설계, 예외 처리까지 고려한 완전한 구현이 중요</li> </ul> </blockquote> <p>동시성 이슈는 서비스가 성장하면서 필연적으로 마주치게 되는 문제입니다. 미리 대비하고 적절한 해결책을 준비해둔다면, 사용자 경험을 해치지 않으면서도 안정적인 서비스를 제공할 수 있을 것입니다.</p> <p>특히 <strong>포인트, 재고, 쿠폰</strong> 등 정확성이 중요한 도메인에서는 이런 분산 락 패턴이 매우 유용하니, 실무에서 적극 활용해볼 필요가 있습니다.</p> <h2 id="참고-자료" style="position:relative;"><a href="#%EC%B0%B8%EA%B3%A0-%EC%9E%90%EB%A3%8C" aria-label="참고 자료 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>참고 자료</h2> <ul> <li><a href="https://redis.io/commands/set/">Redis 공식 문서 - SET 명령어</a></li> <li><a href="https://redis.io/docs/manual/patterns/distributed-locks/">Redlock 공식 문서</a></li> <li><a href="https://github.com/luin/ioredis">ioredis GitHub</a></li> <li><a href="https://github.com/mike-marcacci/node-redlock">node_redis Redlock</a></li> </ul>https://eeeasycode.dev/dont-use-keys-in-redis/https://eeeasycode.dev/dont-use-keys-in-redis/Sat, 24 May 2025 00:00:00 GMT<h2 id="들어가며" style="position:relative;"><a href="#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0" aria-label="들어가며 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>들어가며</h2> <p>Redis는 빠른 속도와 단순한 구조 덕분에 캐시, 세션 저장소, 실시간 데이터 처리 등 다양한 분야에서 널리 사용되는 인메모리 데이터베이스입니다.</p> <p>하지만 Redis는 <strong>싱글 스레드(single-threaded)</strong> 구조이기 때문에, 하나의 <strong>무거운 명령어</strong>가 실행 중이면 다른 요청도 <strong>모두 대기</strong>하게 됩니다.</p> <p>이러한 구조적 특징은 잘못된 명령어 사용 시, 전체 서비스의 응답성을 크게 저하시킬 수 있습니다.</p> <p>그 대표적인 명령어가 바로 <strong>KEYS</strong>입니다.</p> <p>이번 글에서는 <strong>KEYS 명령어</strong>가 얼마나 위험한지, 실제 코드 실험을 통해 그 문제를 직접 확인해보려고 합니다.</p> <h2 id="실험-설계" style="position:relative;"><a href="#%EC%8B%A4%ED%97%98-%EC%84%A4%EA%B3%84" aria-label="실험 설계 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>실험 설계</h2> <hr> <h3 id="목적" style="position:relative;"><a href="#%EB%AA%A9%EC%A0%81" aria-label="목적 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>목적</h3> <blockquote> <p>Redis에서 KEYS 명령어가 실행될 때, 동시에 요청되는 <strong>단순한 GET 명령어도 지연되는가?</strong> </br> 즉, 싱글 스레드 구조에서 모든 명령어가 <strong>순차적으로 처리되는지</strong>를 확인합니다.</p> </blockquote> <p>이를 위해 Redis에 약 <strong>1,000만 개의 key-value 데이터를 미리 세팅</strong>한 후, KEYS와 GET을 <strong>동시에 요청</strong>해 처리 시간을 측정했습니다.</p> <h3 id="테스트-데이터-생성-코드" style="position:relative;"><a href="#%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%83%9D%EC%84%B1-%EC%BD%94%EB%93%9C" aria-label="테스트 데이터 생성 코드 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>테스트 데이터 생성 코드</h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">fillTestData</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> batchSize <span class="token operator">=</span> <span class="token number">10000</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> count<span class="token punctuation">;</span> i <span class="token operator">+=</span> batchSize<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> pipeline <span class="token operator">=</span> redis<span class="token punctuation">.</span><span class="token function">pipeline</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> j <span class="token operator">=</span> i<span class="token punctuation">;</span> j <span class="token operator">&lt;</span> i <span class="token operator">+</span> batchSize <span class="token operator">&amp;&amp;</span> j <span class="token operator">&lt;</span> count<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> pipeline<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">key:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>j<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">value:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>j<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">await</span> pipeline<span class="token punctuation">.</span><span class="token function">exec</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="실험-코드-Nodejs--ioredis" style="position:relative;"><a href="#%EC%8B%A4%ED%97%98-%EC%BD%94%EB%93%9C-Nodejs--ioredis" aria-label="실험 코드 Nodejs ioredis permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>실험 코드 (Node.js + ioredis)</h3> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">concurrentRequests</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> slowCommandPromise <span class="token operator">=</span> <span class="token function">benchmark</span><span class="token punctuation">(</span>slowCommand<span class="token punctuation">)</span> <span class="token comment">// KEYS</span> <span class="token keyword">const</span> fastCommandPromise <span class="token operator">=</span> <span class="token function">benchmark</span><span class="token punctuation">(</span>fastCommand<span class="token punctuation">)</span> <span class="token comment">// GET</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>slowCommandTime<span class="token punctuation">,</span> fastCommandTime<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span> slowCommandPromise<span class="token punctuation">,</span> fastCommandTime<span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">}</span></code></pre></div> <ul> <li>slowCommand: <code class="language-text">KEYS key:*</code> 실행</li> <li>fastCommand: <code class="language-text">GET key:1</code> 실행</li> <li>두 명령어를 동시에 실행한 후 소요 시간을 비교합니다.</li> </ul> <h2 id="실험-결과" style="position:relative;"><a href="#%EC%8B%A4%ED%97%98-%EA%B2%B0%EA%B3%BC" aria-label="실험 결과 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>실험 결과</h2> <hr> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">slowCommand 시작 fastCommand 시작 slowCommand 완료 fastCommand 완료 slowCommand 소요 시간: <span class="token number">6690</span>.167ms fastCommand 소요 시간: <span class="token number">6689</span>.897ms</code></pre></div> <blockquote> <p>단순한 GET 명령어 하나가 <strong>무려 6.6초 동안 지연됨</strong> </br> 즉, KEYS 명령이 실행 중일 때, 아무리 단순한 명령어라도 모두 블로킹됨</p> </blockquote> <h3 id="왜-이런-현상이-발생할까" style="position:relative;"><a href="#%EC%99%9C-%EC%9D%B4%EB%9F%B0-%ED%98%84%EC%83%81%EC%9D%B4-%EB%B0%9C%EC%83%9D%ED%95%A0%EA%B9%8C" aria-label="왜 이런 현상이 발생할까 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>왜 이런 현상이 발생할까?</h3> <p>Redis는 기본적으로 싱글 스레드 기반으로 동작합니다.</p> <p>즉, 한 번에 하나의 명령어만 처리할 수 있으며, 나머지 명령어는 큐에서 대기하게 됩니다.</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Client A: KEYS key:* Client B: GET key:1 → Client B는 A가 끝날 때까지 대기</code></pre></div> <blockquote> <p>아무리 다른 클라이언트라도, Redis는 명령어를 병렬 처리하지 않습니다.</p> </blockquote> <h3 id="SLOWLOG-확인" style="position:relative;"><a href="#SLOWLOG-%ED%99%95%EC%9D%B8" aria-label="SLOWLOG 확인 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a><strong>SLOWLOG 확인</strong></h3> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token punctuation">[</span> <span class="token punctuation">[</span> <span class="token number">211</span>, <span class="token number">1747990094</span>, <span class="token number">3170315</span>, <span class="token punctuation">[</span> <span class="token string">'keys'</span>, <span class="token string">'key:*'</span> <span class="token punctuation">]</span>, <span class="token string">'172.18.0.1:65392'</span>, <span class="token string">''</span> <span class="token punctuation">]</span> <span class="token punctuation">]</span></code></pre></div> <ul> <li>KEYS 명령어가 3초 이상 걸린 기록이 남아 있음</li> <li>키의 개수가 많아질수록, 블로킹 시간은 더 늘어날 수 있음</li> </ul> <h2 id="KEYS-명령어의-위험성" style="position:relative;"><a href="#KEYS-%EB%AA%85%EB%A0%B9%EC%96%B4%EC%9D%98-%EC%9C%84%ED%97%98%EC%84%B1" aria-label="KEYS 명령어의 위험성 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>KEYS 명령어의 위험성</h2> <hr> <h3 id="위험한-이유" style="position:relative;"><a href="#%EC%9C%84%ED%97%98%ED%95%9C-%EC%9D%B4%EC%9C%A0" aria-label="위험한 이유 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>위험한 이유</h3> <table> <thead> <tr> <th><strong>항목</strong></th> <th><strong>설명</strong></th> </tr> </thead> <tbody> <tr> <td>전체 키 탐색</td> <td>모든 키를 순회하므로 <strong>시간 복잡도 O(N)</strong></td> </tr> <tr> <td>블로킹 발생</td> <td><strong>단일 스레드 구조</strong>라 다른 요청도 모두 대기</td> </tr> <tr> <td>슬로우 명령어</td> <td>SLOWLOG에 기록될 만큼 성능 저하 유발</td> </tr> <tr> <td>실시간 서비스 중단</td> <td>실제 서비스에서는 <strong>전체 시스템 지연</strong>으로 이어질 가능성 있음</td> </tr> </tbody> </table> <h3 id="실무에서-대안은" style="position:relative;"><a href="#%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EB%8C%80%EC%95%88%EC%9D%80" aria-label="실무에서 대안은 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a><strong>실무에서 대안은?</strong></h3> <table> <thead> <tr> <th><strong>잘못된 방식</strong></th> <th><strong>대안</strong></th> </tr> </thead> <tbody> <tr> <td>KEYS user:*</td> <td>사용 금지</td> </tr> <tr> <td>SCAN 명령어 사용</td> <td>커서 기반 탐색</td> </tr> <tr> <td>위험 명령어 실행 제한 (운영 환경)</td> <td>rename-command KEYS "" 등을 통해 아예 사용 금지</td> </tr> </tbody> </table> <h2 id="마무리하며" style="position:relative;"><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC%ED%95%98%EB%A9%B0" aria-label="마무리하며 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a><strong>마무리하며</strong></h2> <hr> <p>이번 실험을 통해 Redis의 싱글 스레드 구조에서 KEYS 명령어 같은 <strong>O(N)</strong> 명령어가 실시간 서비스에 얼마나 큰 영향을 줄 수 있는지 <strong>직접 눈으로 확인</strong>할 수 있었습니다.</p> <blockquote> <p>하나의 단순하고 편리한 명령어가 전체 시스템을 블로킹시킬 수 있다는 점 </br> 운영 환경에서 Redis 명령어를 실행할 때, 단순함과 편리함 뒤의 위험성도 반드시 고려해야 합니다.</p> </blockquote> <h2 id="-참고" style="position:relative;"><a href="#-%EC%B0%B8%EA%B3%A0" aria-label=" 참고 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a><strong>📁 참고</strong></h2> <ul> <li>Redis 공식 문서: <a href="https://redis.io/commands/keys/">https://redis.io/commands/keys/</a></li> </ul>https://eeeasycode.dev/nestjs-validate-transform/https://eeeasycode.dev/nestjs-validate-transform/Thu, 22 May 2025 00:00:00 GMT<p>NestJS를 사용하다 보면 DTO를 통해 API 요청 데이터를 검증하거나 변환할 일이 많습니다. 이때 자주 사용하는 두 가지 핵심 라이브러리가 바로 <code class="language-text">class-validator</code>와 <code class="language-text">class-transformer</code>입니다. 이 글에서는 이 두 도구가 <strong>어떤 순서로</strong>, <strong>어떻게 동작하는지</strong>, 그리고 제가 자주 헷갈렸던 <code class="language-text">@ValidateIf</code>와 <code class="language-text">@IsOptional</code>의 처리 방식까지 함께 정리해보겠습니다.</p> <h2 id="1-class-validator와-class-transformer란" style="position:relative;"><a href="#1-class-validator%EC%99%80-class-transformer%EB%9E%80" aria-label="1 class validator와 class transformer란 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>1. class-validator와 class-transformer란?</h2> <h3 id="-class-validator" style="position:relative;"><a href="#-class-validator" aria-label=" class validator permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>📌 class-validator</h3> <p><code class="language-text">class-validator</code>는 클래스 기반의 유효성 검증 도구입니다. <code class="language-text">@IsEmail</code>, <code class="language-text">@IsNotEmpty</code>, <code class="language-text">@MinLength</code> 등의 데코레이터를 통해 클래스 프로퍼티에 유효성 조건을 정의할 수 있습니다. NestJS의 <code class="language-text">ValidationPipe</code>와 함께 사용되며, 유효하지 않은 요청은 자동으로 예외를 발생시킵니다.</p> <h3 id="-class-transformer" style="position:relative;"><a href="#-class-transformer" aria-label=" class transformer permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>🔁 class-transformer</h3> <p><code class="language-text">class-transformer</code>는 평범한 JavaScript 객체를 지정한 클래스 인스턴스로 변환하거나 그 반대를 수행합니다. 예를 들어 <code class="language-text">plainToInstance()</code>를 통해 API 요청 본문 데이터를 DTO 클래스로 변환해줄 수 있습니다.</p> <h2 id="2-실행-순서-Transform--Validate" style="position:relative;"><a href="#2-%EC%8B%A4%ED%96%89-%EC%88%9C%EC%84%9C-Transform--Validate" aria-label="2 실행 순서 Transform Validate permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>2. 실행 순서: Transform → Validate</h2> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Type (class-transformer) → (ValidateIf + IsOptional) → IsDefined → ETC</code></pre></div> <p>NestJS의 <code class="language-text">ValidationPipe</code> 내부를 보면 다음과 같은 흐름으로 동작하는 것을 확인할 수 있습니다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token comment">// validation.pipe.ts</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token function">transform</span><span class="token punctuation">(</span>value<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> ArgumentMetadata<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token comment">// class-transformer</span> <span class="token keyword">let</span> entity <span class="token operator">=</span> classTransformer<span class="token punctuation">.</span><span class="token function">plainToInstance</span><span class="token punctuation">(</span> metatype<span class="token punctuation">,</span> value<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>transformOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">...</span> <span class="token comment">// class-validator</span> <span class="token keyword">const</span> errors <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>entity<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>validatorOptions<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">...</span> <span class="token punctuation">}</span></code></pre></div> <p>즉, class-transformer가 먼저, 그 다음 class-validator가 동작합니다.</p> <p>이 순서가 중요한 이유는 다음과 같습니다.</p> <ul> <li>"123" 같은 문자열 숫자가 먼저 number로 변환된 후에 @IsInt() 같은 데코레이터가 정상 동작되어야 합니다.</li> <li>변환이 잘못되면 검증도 제대로 되지 않습니다.</li> </ul> <h2 id="3-데코레이터-평가-순서" style="position:relative;"><a href="#3-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-%ED%8F%89%EA%B0%80-%EC%88%9C%EC%84%9C" aria-label="3 데코레이터 평가 순서 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>3. 데코레이터 평가 순서</h2> <p>validator 데코레이터의 내부 동작을 살펴보면, 다음과 같은 순서로 실행됩니다.</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">(ValidateIf + IsOptional) → IsDefined → 기타 class-validator 데코레이터들</code></pre></div> <p>이 순서를 결정하는 것은<code class="language-text">class-validator</code>의 핵심 클래스인 <code class="language-text">ValidationExecutor</code>의 내부 코드입니다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token comment">// class-validator/ValidationExcutor.js</span> <span class="token function">performValidations</span><span class="token punctuation">(</span>object<span class="token punctuation">,</span> value<span class="token punctuation">,</span> propertyName<span class="token punctuation">,</span> definedMetadatas<span class="token punctuation">,</span> metadatas<span class="token punctuation">,</span> validationErrors<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> customValidationMetadatas <span class="token operator">=</span> metadatas<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>metadata <span class="token operator">=></span> metadata<span class="token punctuation">.</span>type <span class="token operator">===</span> ValidationTypes_1<span class="token punctuation">.</span>ValidationTypes<span class="token punctuation">.</span><span class="token constant">CUSTOM_VALIDATION</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> nestedValidationMetadatas <span class="token operator">=</span> metadatas<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>metadata <span class="token operator">=></span> metadata<span class="token punctuation">.</span>type <span class="token operator">===</span> ValidationTypes_1<span class="token punctuation">.</span>ValidationTypes<span class="token punctuation">.</span><span class="token constant">NESTED_VALIDATION</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> conditionalValidationMetadatas <span class="token operator">=</span> metadatas<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>metadata <span class="token operator">=></span> metadata<span class="token punctuation">.</span>type <span class="token operator">===</span> ValidationTypes_1<span class="token punctuation">.</span>ValidationTypes<span class="token punctuation">.</span><span class="token constant">CONDITIONAL_VALIDATION</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> validationError <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">generateValidationError</span><span class="token punctuation">(</span>object<span class="token punctuation">,</span> value<span class="token punctuation">,</span> propertyName<span class="token punctuation">)</span><span class="token punctuation">;</span> validationErrors<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>validationError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> canValidate <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">conditionalValidations</span><span class="token punctuation">(</span>object<span class="token punctuation">,</span> value<span class="token punctuation">,</span> conditionalValidationMetadatas<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>canValidate<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token comment">// 조건이 만족되지 않으면 나머지 검증은 건너뜀</span> <span class="token punctuation">}</span> <span class="token comment">// handle IS_DEFINED validation type the special way - it should work no matter skipUndefinedProperties/skipMissingProperties is set or not</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">customValidations</span><span class="token punctuation">(</span>object<span class="token punctuation">,</span> value<span class="token punctuation">,</span> definedMetadatas<span class="token punctuation">,</span> validationError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">mapContexts</span><span class="token punctuation">(</span>object<span class="token punctuation">,</span> value<span class="token punctuation">,</span> definedMetadatas<span class="token punctuation">,</span> validationError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">===</span> <span class="token keyword">undefined</span> <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>validatorOptions <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>validatorOptions<span class="token punctuation">.</span>skipUndefinedProperties <span class="token operator">===</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">===</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>validatorOptions <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>validatorOptions<span class="token punctuation">.</span>skipNullProperties <span class="token operator">===</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>value <span class="token operator">===</span> <span class="token keyword">null</span> <span class="token operator">||</span> value <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>validatorOptions <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>validatorOptions<span class="token punctuation">.</span>skipMissingProperties <span class="token operator">===</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">customValidations</span><span class="token punctuation">(</span>object<span class="token punctuation">,</span> value<span class="token punctuation">,</span> customValidationMetadatas<span class="token punctuation">,</span> validationError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">nestedValidations</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> nestedValidationMetadatas<span class="token punctuation">,</span> validationError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">mapContexts</span><span class="token punctuation">(</span>object<span class="token punctuation">,</span> value<span class="token punctuation">,</span> metadatas<span class="token punctuation">,</span> validationError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">mapContexts</span><span class="token punctuation">(</span>object<span class="token punctuation">,</span> value<span class="token punctuation">,</span> customValidationMetadatas<span class="token punctuation">,</span> validationError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h2 id="4-ValidateIf와-IsOptional의-관계" style="position:relative;"><a href="#4-ValidateIf%EC%99%80-IsOptional%EC%9D%98-%EA%B4%80%EA%B3%84" aria-label="4 ValidateIf와 IsOptional의 관계 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>4. @ValidateIf와 @IsOptional의 관계</h2> <p>두 데코레이터는 둘 다 조건부 검증에 사용됩니다. 차이점은 다음과 같습니다.</p> <ul> <li>@ValidateIf(fn) : fn이 true를 반환할 때만 이후 데코레이터를 적용합니다.</li> <li>@IsOptional() : 값이 undefined 또는 null이면 이후 검증을 건너뜁니다.</li> </ul> <h3 id="-평가-순서는-중요하지-않다" style="position:relative;"><a href="#-%ED%8F%89%EA%B0%80-%EC%88%9C%EC%84%9C%EB%8A%94-%EC%A4%91%EC%9A%94%ED%95%98%EC%A7%80-%EC%95%8A%EB%8B%A4" aria-label=" 평가 순서는 중요하지 않다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>💡 평가 순서는 중요하지 않다?</h3> <p>@ValidateIf 와 @IsOptional 의 평가 순서에 대해서 처음에 이해하기 어려웠지만, 내부 구현 코드를 보며 어느정도 이해할 수 있었습니다.</p> <p>우선, 두 데코레이터의 평가 순서는 데코레이터 선언 순서에 따라 달라집니다. 하지만, 결국 두 데코레이터는 AND 연산으로 평가되기 때문에 평가 순서에 따라 평가 결과가 달라지지는 않습니다.</p> <p>즉, 아래의 두 코드는 서로 다른 순서로 데코레이터가 선언되어 있어도 <strong>검증 결과는 항상 동일</strong>합니다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token decorator"><span class="token at operator">@</span><span class="token function">ValidateIf</span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token operator">=></span> obj<span class="token punctuation">.</span>price <span class="token operator">!==</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token decorator"><span class="token at operator">@</span><span class="token function">IsOptional</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token decorator"><span class="token at operator">@</span><span class="token function">IsNotEmpty</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> price<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span></code></pre></div> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token decorator"><span class="token at operator">@</span><span class="token function">IsOptional</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token decorator"><span class="token at operator">@</span><span class="token function">ValidateIf</span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token operator">=></span> obj<span class="token punctuation">.</span>price <span class="token operator">!==</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token decorator"><span class="token at operator">@</span><span class="token function">IsNotEmpty</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> price<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span></code></pre></div> <p>그 이유는 <code class="language-text">class-validator</code> 내부적으로 <code class="language-text">@ValidateIf</code>와 <code class="language-text">@IsOptional</code>은 둘 다 <code class="language-text">CONDITIONAL_VALIDATION</code> 타입으로 처리되며, 아래처럼 <strong>AND 연산</strong>으로 평가되기 때문입니다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">private</span> <span class="token function">conditionalValidations</span><span class="token punctuation">(</span>object<span class="token operator">:</span> object<span class="token punctuation">,</span> value<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> metadatas<span class="token operator">:</span> ValidationMetadata<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> ValidationMetadata<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> metadatas <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>metadata <span class="token operator">=></span> metadata<span class="token punctuation">.</span>constraints<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">(</span>object<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>resultA<span class="token punctuation">,</span> resultB<span class="token punctuation">)</span> <span class="token operator">=></span> resultA <span class="token operator">&amp;&amp;</span> resultB<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h2 id="5-마무리" style="position:relative;"><a href="#5-%EB%A7%88%EB%AC%B4%EB%A6%AC" aria-label="5 마무리 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>5. 마무리</h2> <p>class-transformer는 "변환", class-validator는 "검증"의 책임을 가지고 있으며, NestJS의 ValidationPipe 내부에서는 이 둘이 유기적으로 동작합니다.</p> <p>자주 헷갈릴 수 있는 @ValidateIf와 @IsOptional은 둘 다 조건부 검증을 수행하며, 내부적으로는 순서에 상관없이 AND 조건으로 평가된다는 사실을 실제 내부 코드를 직접 뜯어보며 알아볼 수 있었습니다.</p> <p>console.log를 찍거나 테스트 코드를 작성해도 충분히 확인할 수 있었지만, 결국 core 레벨의 코드를 직접 확인해야 마음이 놓이는 것 같네요.</p> <h2 id="참고-자료" style="position:relative;"><a href="#%EC%B0%B8%EA%B3%A0-%EC%9E%90%EB%A3%8C" aria-label="참고 자료 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>참고 자료</h2> <ul> <li><a href="https://github.com/typestack/class-validator">class-validator GitHub</a></li> <li><a href="https://github.com/typestack/class-transformer">class-transformer GitHub</a></li> <li><a href="https://docs.nestjs.com/pipes">NestJS 공식문서 - Pipes</a></li> </ul>https://eeeasycode.dev/clustered-index/https://eeeasycode.dev/clustered-index/Wed, 14 May 2025 00:00:00 GMT<h1 id="Clustered-Index-vs-Non-Clustered-Index" style="position:relative;"><a href="#Clustered-Index-vs-Non-Clustered-Index" aria-label="Clustered Index vs Non Clustered Index permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Clustered Index vs Non-Clustered Index</h1> <p>데이터베이스에서 성능 최적화와 효율적인 데이터 검색은 매우 중요한 과제입니다. 이와 관련하여 인덱스는 데이터베이스의 핵심 요소 중 하나로, 데이터 정렬과 검색 속도를 크게 향상시킵니다. MySQL(InnoDB) 기준으로 Clustered Index와 Non-Clustered Index의 차이점과 사용 목적을 자세히 정리해보겠습니다.</p> <h2 id="Clustered-Index란" style="position:relative;"><a href="#Clustered-Index%EB%9E%80" aria-label="Clustered Index란 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Clustered Index란?</h2> <p>Clustered Index는 테이블의 데이터 전체가 인덱스와 함께 물리적으로 정렬되는 방식의 인덱스입니다. 즉, 데이터 자체가 인덱스의 일부로 동작하기 때문에 데이터 검색에서 매우 효율적입니다. Clustered Index는 다음과 같은 특징을 가지고 있습니다:</p> <ol> <li><strong>테이블 당 하나만 생성 가능</strong>: 물리적으로 데이터를 정렬해야 하므로 한 테이블에 여러 개의 Clustered Index를 생성할 수 없습니다.</li> <li><strong>기본 키(PK)와 연관</strong>: 기본적으로 테이블의 기본 키(PK)가 Clustered Index로 설정됩니다. 만약 기본 키가 없을 경우, <code class="language-text">unique</code>와 <code class="language-text">NOT NULL</code> 조건을 만족하는 열이 사용됩니다. 이러한 조건이 없는 경우, MySQL은 내부적으로 <code class="language-text">row_id</code>를 생성하여 Clustered Index를 설정합니다.</li> <li><strong>빠른 데이터 검색</strong>: 데이터가 물리적으로 정렬되어 있으므로, 범위 검색(range query)이나 순차 검색(sequential scan)에서 매우 효율적입니다.</li> </ol> <h3 id="Clustered-Index의-장점" style="position:relative;"><a href="#Clustered-Index%EC%9D%98-%EC%9E%A5%EC%A0%90" aria-label="Clustered Index의 장점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Clustered Index의 장점</h3> <ul> <li>데이터와 인덱스가 동일한 구조를 가지므로 검색 속도가 빠릅니다.</li> <li>범위 검색이 자주 필요한 경우 특히 유리합니다.</li> </ul> <h3 id="Clustered-Index의-단점" style="position:relative;"><a href="#Clustered-Index%EC%9D%98-%EB%8B%A8%EC%A0%90" aria-label="Clustered Index의 단점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Clustered Index의 단점</h3> <ul> <li>데이터 삽입/삭제 시 물리적 정렬이 필요하므로 성능 저하가 발생할 수 있습니다.</li> <li>테이블 당 하나만 생성 가능하므로, 인덱스 설계에 제약이 따릅니다.</li> </ul> <h2 id="Non-Clustered-Index란" style="position:relative;"><a href="#Non-Clustered-Index%EB%9E%80" aria-label="Non Clustered Index란 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Non-Clustered Index란?</h2> <p>Non-Clustered Index는 Clustered Index와 달리, 데이터 자체와는 별도로 생성되는 보조 인덱스입니다. Non-Clustered Index는 데이터가 물리적으로 정렬되지 않으며, 대신 정렬된 별도의 인덱스 페이지를 생성하여 관리합니다. 이로 인해 Non-Clustered Index는 다음과 같은 특징을 가집니다:</p> <ol> <li><strong>테이블 당 여러 개 생성 가능</strong>: Clustered Index와 달리, Non-Clustered Index는 한 테이블에서 여러 개를 생성할 수 있습니다.</li> <li><strong>보조 인덱스</strong>: Non-Clustered Index는 데이터의 실제 저장 위치를 참조(pointer)하며, 데이터 자체를 포함하지 않습니다.</li> <li><strong>데이터 정렬 없음</strong>: 물리적으로 데이터를 정렬하지 않으므로 삽입/삭제 작업에서 성능 저하가 적습니다.</li> </ol> <h3 id="Non-Clustered-Index의-장점" style="position:relative;"><a href="#Non-Clustered-Index%EC%9D%98-%EC%9E%A5%EC%A0%90" aria-label="Non Clustered Index의 장점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Non-Clustered Index의 장점</h3> <ul> <li>하나의 테이블에 여러 개의 인덱스를 생성할 수 있어 다양한 검색 조건에서 유연하게 사용할 수 있습니다.</li> <li>삽입/삭제 작업에서 성능 저하가 적습니다.</li> </ul> <h3 id="Non-Clustered-Index의-단점" style="position:relative;"><a href="#Non-Clustered-Index%EC%9D%98-%EB%8B%A8%EC%A0%90" aria-label="Non Clustered Index의 단점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Non-Clustered Index의 단점</h3> <ul> <li>데이터 검색 시, 인덱스에서 데이터를 다시 참조해야 하므로 Clustered Index에 비해 검색 성능이 느릴 수 있습니다.</li> <li>추가적인 저장 공간이 필요합니다.</li> </ul> <h2 id="Clustered-Index와-Non-Clustered-Index의-비교" style="position:relative;"><a href="#Clustered-Index%EC%99%80-Non-Clustered-Index%EC%9D%98-%EB%B9%84%EA%B5%90" aria-label="Clustered Index와 Non Clustered Index의 비교 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Clustered Index와 Non-Clustered Index의 비교</h2> <table> <thead> <tr> <th>특징</th> <th>Clustered Index</th> <th>Non-Clustered Index</th> </tr> </thead> <tbody> <tr> <td>데이터 정렬 여부</td> <td>물리적으로 데이터 정렬</td> <td>데이터 정렬 없음</td> </tr> <tr> <td>생성 가능 개수</td> <td>테이블 당 하나</td> <td>테이블 당 여러 개</td> </tr> <tr> <td>저장 공간</td> <td>추가 공간 불필요</td> <td>추가 저장 공간 필요</td> </tr> <tr> <td>검색 성능</td> <td>데이터와 인덱스가 동일하여 빠름</td> <td>인덱스에서 데이터 참조로 인해 다소 느림</td> </tr> <tr> <td>삽입/삭제 성능</td> <td>물리적 정렬 필요로 인해 성능 저하 가능</td> <td>물리적 정렬 없음으로 인해 성능 저하 적음</td> </tr> </tbody> </table> <h2 id="결론" style="position:relative;"><a href="#%EA%B2%B0%EB%A1%A0" aria-label="결론 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>결론</h2> <p>Clustered Index와 Non-Clustered Index는 각각의 장단점과 목적에 따라 사용됩니다. Clustered Index는 데이터가 자주 정렬되거나 범위 검색이 필요한 경우 유리하며, Non-Clustered Index는 다양한 검색 조건에 대응하기 위해 보조 인덱스로 활용됩니다. 데이터베이스 인덱스를 설계할 때, 테이블 구조와 쿼리 패턴을 고려하여 적절히 선택하는 것이 중요합니다.</p> <p>데이터베이스 성능을 최적화하기 위해 두 인덱스의 차이점을 명확히 이해하고, 적절한 전략을 수립하는 것이 필요합니다. 이를 통해 효율적인 데이터 관리와 검색 성능 향상을 도모할 수 있습니다.</p>https://eeeasycode.dev/serverless_architecture/https://eeeasycode.dev/serverless_architecture/Sun, 02 Mar 2025 00:00:00 GMT<h2 id="1-서버리스란-무엇인가" style="position:relative;"><a href="#1-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80" aria-label="1 서버리스란 무엇인가 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>1. 서버리스란 무엇인가?</h2> <p>서버리스(Serverless)는 개발자와 인프라 엔지니어가 서버 관리의 부담에서 벗어나 애플리케이션 로직에 집중할 수 있게 해준다고 해요. 2014년 AWS Lambda의 출시와 함께 본격적으로 주목받기 시작했으며, 이는 클라우드의 <strong>Auto Scaling</strong>과 <strong>Event-driven</strong> 컴퓨팅 기술 발전의 결과물이기도 하죠.</p> <p><img src="https://i.imgur.com/oSCGcF2.png" alt="AWS Serverless Architecture Diagram"></p> <h3 id="서버리스의-핵심-특징" style="position:relative;"><a href="#%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EC%9D%98-%ED%95%B5%EC%8B%AC-%ED%8A%B9%EC%A7%95" aria-label="서버리스의 핵심 특징 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>서버리스의 핵심 특징</h3> <ul> <li><strong>Auto Scaling</strong>: 트래픽 부하에 따라 인스턴스가 자동으로 생성되고 제거됩니다.</li> <li><strong>관리형 인프라</strong>: 서버 프로비저닝, 패치 관리, 로드 밸런싱 등 인프라 운영 부담이 최소화됩니다.</li> <li><strong>Event Driven</strong>: S3 업로드, API 호출, 메시지 큐 등 특정 이벤트가 발생하면 함수가 실행됩니다.</li> <li><strong>비용 효율성</strong>: 사용한 리소스만큼만 과금되는 <strong>Pay-as-you-go</strong> 모델로, 유휴 서버 비용이 없습니다.</li> </ul> <blockquote> <p><strong>인프라 관점 인사이트</strong>: 서버리스는 전통적인 VM 기반 인프라에서 벗어나 클라우드 제공자가 하드웨어와 운영체제 레벨을 추상화한 결과로, 인프라 팀의 역할이 "서버 관리"에서 "아키텍처 설계와 모니터링"으로 전환되고 있어요.</p> </blockquote> <h2 id="2-서버리스의-대표적인-구현-방식" style="position:relative;"><a href="#2-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EC%9D%98-%EB%8C%80%ED%91%9C%EC%A0%81%EC%9D%B8-%EA%B5%AC%ED%98%84-%EB%B0%A9%EC%8B%9D" aria-label="2 서버리스의 대표적인 구현 방식 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>2. 서버리스의 대표적인 구현 방식</h2> <p>서버리스는 주로 <strong>FaaS(Function as a Service)</strong> 와 <strong>BaaS(Backend as a Service)</strong> 로 구현되는데요. 주요 클라우드 벤더별 서비스를 살펴보면 다음과 같아요.</p> <table> <thead> <tr> <th>서비스</th> <th>제공 업체</th> <th>주요 특징</th> </tr> </thead> <tbody> <tr> <td>AWS Lambda</td> <td>AWS</td> <td>다양한 이벤트 소스 트리거, 강력한 생태계</td> </tr> <tr> <td>Google Cloud Functions</td> <td>GCP</td> <td>Firebase와 연동 강점, NoSQL 친화적</td> </tr> <tr> <td>Azure Functions</td> <td>Azure</td> <td>.NET 개발자 친화적, Durable Functions 지원</td> </tr> <tr> <td>Cloudflare Workers</td> <td>Cloudflare</td> <td>엣지에서 실행, 초저지연 응답 속도</td> </tr> </tbody> </table> <h3 id="FaaS-vs-BaaS" style="position:relative;"><a href="#FaaS-vs-BaaS" aria-label="FaaS vs BaaS permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>FaaS vs BaaS</h3> <ul> <li><strong>FaaS</strong>: AWS Lambda처럼 개별 함수 단위로 코드가 실행되며, 인프라 엔지니어는 이벤트 트리거와 실행 환경만 신경 쓰면 됩니다.</li> <li><strong>BaaS</strong>: Firebase나 AWS Amplify처럼 인증, 데이터베이스, API Gateway 등 백엔드 기능을 통합 제공해 인프라 설계를 단순화합니다.</li> </ul> <blockquote> <p>FaaS는 단일 작업 단위에 최적화되어 있고, BaaS는 전체 백엔드 스택을 아우르니 프로젝트 요구사항에 따라 둘을 조합하는 하이브리드 접근도 가능해요.</p> </blockquote> <p><img src="https://i.imgur.com/4CDfBZy.png" alt=""></p> <h2 id="3-서버리스의-장점과-단점" style="position:relative;"><a href="#3-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EC%9D%98-%EC%9E%A5%EC%A0%90%EA%B3%BC-%EB%8B%A8%EC%A0%90" aria-label="3 서버리스의 장점과 단점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>3. 서버리스의 장점과 단점</h2> <h3 id="-서버리스의-장점" style="position:relative;"><a href="#-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EC%9D%98-%EC%9E%A5%EC%A0%90" aria-label=" 서버리스의 장점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>✅ 서버리스의 장점</h3> <ol> <li><strong>운영 부담 감소</strong>: 서버 프로비저닝, OS 패치, 네트워크 설정 등 인프라 유지보수가 필요 없습니다.</li> <li><strong>비용 절감</strong>: 사용량 기반 과금으로 유휴 리소스 비용이 없습니다.</li> <li><strong>확장성</strong>: 수평 확장이 자동으로 처리되어 트래픽 급등에도 안정적입니다.</li> <li><strong>빠른 개발</strong>: MVP(최소 기능 제품) 구축 속도가 빨라집니다.</li> <li><strong>이벤트 기반 설계</strong>: 실시간 데이터 처리와 같은 이벤트 드리븐 시스템에 적합합니다.</li> </ol> <h3 id="️-서버리스의-단점" style="position:relative;"><a href="#%EF%B8%8F-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EC%9D%98-%EB%8B%A8%EC%A0%90" aria-label="️ 서버리스의 단점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>⚠️ 서버리스의 단점</h3> <ol> <li><strong>콜드 스타트</strong>: 초기 요청 시 지연이 발생하며, Java나 .NET 같은 무거운 런타임에서 두드러집니다.</li> <li><strong>벤더 락인</strong>: 특정 클라우드 벤더의 생태계에 종속될 위험이 큽니다.</li> <li><strong>트랜잭션 관리</strong>: 상태 유지(Stateful) 워크로드에는 부적합합니다.</li> <li><strong>실행 시간 제한</strong>: Lambda의 경우 최대 15분으로, 장시간 작업에 제약이 있습니다.</li> <li><strong>모니터링 복잡성</strong>: 분산 환경에서 로깅과 디버깅이 더 까다롭습니다.</li> </ol> <blockquote> <p>콜드 스타트는 프로비저닝된 동시성(Provisioned Concurrency)으로 완화할 수 있지만 추가 비용이 발생해요. 벤더 락인을 줄이려면 오픈소스 프레임워크(Serverless Framework 등)를 활용하는 것도 방법이 되겠네요.</p> </blockquote> <h2 id="4-서버리스에-대한-흔한-오해" style="position:relative;"><a href="#4-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EC%97%90-%EB%8C%80%ED%95%9C-%ED%9D%94%ED%95%9C-%EC%98%A4%ED%95%B4" aria-label="4 서버리스에 대한 흔한 오해 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>4. 서버리스에 대한 흔한 오해</h2> <h3 id="-오해-1-서버리스는-서버가-없는-것이다" style="position:relative;"><a href="#-%EC%98%A4%ED%95%B4-1-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EB%8A%94-%EC%84%9C%EB%B2%84%EA%B0%80-%EC%97%86%EB%8A%94-%EA%B2%83%EC%9D%B4%EB%8B%A4" aria-label=" 오해 1 서버리스는 서버가 없는 것이다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>❌ 오해 1: “서버리스는 서버가 없는 것이다?”</h3> <p>서버리스는 <strong>“서버가 없는(Server-less)”</strong> 것이 아니라 <strong>“서버를 신경 쓰지 않아도 되는(Server-abstracted)”</strong> 아키텍처에요. 실제로는 클라우드 제공자의 서버에서 실행되지만, 인프라 관리를 우리가 직접 하지 않는다는 점이 핵심이죠.</p> <h3 id="-오해-2-서버리스는-항상-비용이-저렴하다" style="position:relative;"><a href="#-%EC%98%A4%ED%95%B4-2-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EB%8A%94-%ED%95%AD%EC%83%81-%EB%B9%84%EC%9A%A9%EC%9D%B4-%EC%A0%80%EB%A0%B4%ED%95%98%EB%8B%A4" aria-label=" 오해 2 서버리스는 항상 비용이 저렴하다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>❌ 오해 2: “서버리스는 항상 비용이 저렴하다?”</h3> <p>짧고 간헐적인 트래픽에서는 비용 효율적이지만, 지속적인 고부하 트래픽에서는 EC2 같은 전통 인프라가 더 저렴할 수 있어요.</p> <ul> <li><strong>예시</strong>: EC2 월 $30 고정 비용 vs Lambda 호출 횟수 증가 시 비용 상승.</li> </ul> <h3 id="-오해-3-모든-애플리케이션에-적합하다" style="position:relative;"><a href="#-%EC%98%A4%ED%95%B4-3-%EB%AA%A8%EB%93%A0-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%97%90-%EC%A0%81%ED%95%A9%ED%95%98%EB%8B%A4" aria-label=" 오해 3 모든 애플리케이션에 적합하다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>❌ 오해 3: “모든 애플리케이션에 적합하다?”</h3> <p>이벤트 기반 시스템이나 API 백엔드에는 적합하지만, 고성능 컴퓨팅이나 상태 유지 애플리케이션에는 한계가 있어요.</p> <h3 id="-오해-4-서버리스면-DevOps가-필요-없다" style="position:relative;"><a href="#-%EC%98%A4%ED%95%B4-4-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EB%A9%B4-DevOps%EA%B0%80-%ED%95%84%EC%9A%94-%EC%97%86%EB%8B%A4" aria-label=" 오해 4 서버리스면 DevOps가 필요 없다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>❌ 오해 4: “서버리스면 DevOps가 필요 없다?”</h3> <p>여전히 배포 파이프라인, 모니터링(AWS X-Ray, CloudWatch), 보안 설정이 필요합니다. 인프라 팀의 역할이 "관리"에서 "최적화"로 바뀔 뿐이에요.</p> <h2 id="5-서버리스를-도입해야-할-때와-피해야-할-때" style="position:relative;"><a href="#5-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EB%A5%BC-%EB%8F%84%EC%9E%85%ED%95%B4%EC%95%BC-%ED%95%A0-%EB%95%8C%EC%99%80-%ED%94%BC%ED%95%B4%EC%95%BC-%ED%95%A0-%EB%95%8C" aria-label="5 서버리스를 도입해야 할 때와 피해야 할 때 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>5. 서버리스를 도입해야 할 때와 피해야 할 때</h2> <h3 id="-도입에-적합한-경우" style="position:relative;"><a href="#-%EB%8F%84%EC%9E%85%EC%97%90-%EC%A0%81%ED%95%A9%ED%95%9C-%EA%B2%BD%EC%9A%B0" aria-label=" 도입에 적합한 경우 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>✅ 도입에 적합한 경우</h3> <ul> <li><strong>이벤트 기반 워크로드</strong>: 파일 업로드 후 자동 처리 등.</li> <li><strong>변동 트래픽</strong>: 크리스마스 시즌 같은 대규모 이벤트.</li> <li><strong>빠른 개발</strong>: 스타트업의 MVP 제작.</li> <li><strong>IoT/실시간 처리</strong>: 센서 데이터 처리, 챗봇 등.</li> </ul> <h3 id="-피해야-할-경우" style="position:relative;"><a href="#-%ED%94%BC%ED%95%B4%EC%95%BC-%ED%95%A0-%EA%B2%BD%EC%9A%B0" aria-label=" 피해야 할 경우 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>❌ 피해야 할 경우</h3> <ul> <li><strong>고성능 컴퓨팅</strong>: 머신러닝 학습처럼 CPU 집약적인 작업.</li> <li><strong>장시간 작업</strong>: 배치 프로세싱(대안으로 ECS나 Batch 추천).</li> <li><strong>복잡한 트랜잭션</strong>: 상태 관리와 롤백이 중요한 시스템.</li> <li><strong>지속 고부하</strong>: Kubernetes로 컨테이너 오케스트레이션이 더 나음.</li> </ul> <h2 id="6-내가-서버리스-아키텍처에-빠진-이유" style="position:relative;"><a href="#6-%EB%82%B4%EA%B0%80-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%97%90-%EB%B9%A0%EC%A7%84-%EC%9D%B4%EC%9C%A0" aria-label="6 내가 서버리스 아키텍처에 빠진 이유 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>6. 내가 서버리스 아키텍처에 빠진 이유?!</h2> <p>최근 진행한 프로젝트들은 인증/인가부터 간단한 데이터 서빙, 저장 등 다양한 부분에서 서버리스 아키텍처 기반으로 설계하고 있어요.</p> <p>특히, AWS Lambda, DynamoDB를 정말 잘 사용하고 있는데, Cognito 등 AWS에서 제공하는 다양한 서비스들과 연동하기도 편리하고 1인 개발을 할 때에 인프라 구축에 들어가는 비용을 절감할 수 있다는 점이 너무 좋은 것 같아요.</p> <p>중요 비즈니스 로직, 유휴 시간이 상대적으로 짧은 서비스들은 EC2 내에서 돌리지만 나머지는 대부분 Function 형태로 분리해서 설계하고 있구요.</p> <p>토이 프로젝트에서 가장 중요한 부분 중에 하나는 비용이잖아요? 사실, 우리가 간단하게 만드는 프로젝트는 대부분 무료 제공 할당량 내에 존재하기에 서버리스 서비스를 사용해도 비용이 들지 않는 장점도 있구요.</p> <p>이렇게 좋고 편리한 서버리스 아키텍처도 만능이 아니라는 것에 대해서는 계속 염두해두고 사용 중이에요.</p> <h2 id="7-결론-서버리스는-강력하지만-만능은-아니다" style="position:relative;"><a href="#7-%EA%B2%B0%EB%A1%A0-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EB%8A%94-%EA%B0%95%EB%A0%A5%ED%95%98%EC%A7%80%EB%A7%8C-%EB%A7%8C%EB%8A%A5%EC%9D%80-%EC%95%84%EB%8B%88%EB%8B%A4" aria-label="7 결론 서버리스는 강력하지만 만능은 아니다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>7. 결론: 서버리스는 강력하지만 만능은 아니다</h2> <p>서버리스는 인프라 관리 부담을 줄이고 빠른 개발과 확장성을 제공하는 강력한 도구에요. 하지만 콜드 스타트, 벤더 락인, 트랜잭션 관리의 한계를 고려해야 해요. 도입 전에는 다음을 점검하는게 좋을 것 같아요.</p> <ul> <li><strong>비용 분석</strong>: 트래픽 패턴에 따른 Lambda vs EC2 비교</li> <li><strong>성능 요구사항</strong>: 콜드 스타트 허용 범위 확인</li> <li><strong>운영 전략</strong>: 모니터링 및 배포 파이프라인 설계</li> </ul> <p>적절히 활용하면 서버리스는 클라우드 환경에서 인프라 엔지니어와 개발자 모두에게 혁신적인 가치를 제공할 수 있다는 것은 분명하다고 생각해요.</p> <p>토이 프로젝트를 생각하고 계신 분들, 이참에 서버리스 아키텍처로 도전해보시는 거 어때요?</p>https://eeeasycode.dev/nestjs/https://eeeasycode.dev/nestjs/Wed, 05 Feb 2025 00:00:00 GMT<p>NestJS 관련된 글을 몇 개 작성했었는데, 최근들어 NestJS에 대해 더 깊이 파보고 싶다는 생각이 들었다. 그래서, 이왕 이렇게 된 거 끝까지 극한으로 파헤쳐보려고 한다.</p> <br> <p>카테고리 자체는 공식 문서 정리, 성능 개선, 오픈소스 기여 등 다양하게 가져가볼 계획이고, 시리즈는 전부 NestJS에 넣을 생각이다.</p> <br> <blockquote> <p><strong>NestJS Deep Dive Repository</strong> <br> <a href="https://github.com/EeeasyCode/dive-to-nestjs">https://github.com/EeeasyCode/dive-to-nestjs</a></p> </blockquote>https://eeeasycode.dev/lambda-exectution-environment/https://eeeasycode.dev/lambda-exectution-environment/Sun, 19 Jan 2025 00:00:00 GMT<h1 id="AWS-Lambda의-Execution-Environment-이해하기" style="position:relative;"><a href="#AWS-Lambda%EC%9D%98-Execution-Environment-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0" aria-label="AWS Lambda의 Execution Environment 이해하기 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>AWS Lambda의 Execution Environment 이해하기</h1> <p><strong>AWS Lambda</strong> 는 서버리스 컴퓨팅의 핵심 서비스로, 서버 관리 없이 코드를 실행할 수 있게 해줍니다. 이러한 편리함 뒤에는 <strong>'Execution Environment(실행 환경)'</strong> 라는 개념이 중요한 역할을 한다고 하는데요. 이번 글에서는 AWS Lambda의 Execution Environment가 무엇인지, 어떻게 동작하는지, 그리고 이를 최적화하여 활용하는 방법에 대해 알아보겠습니다.</p> <h2 id="Execution-Environment" style="position:relative;"><a href="#Execution-Environment" aria-label="Execution Environment permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Execution Environment</h2> <p>Execution Environment는 Lambda 함수가 실행되는 격리된 환경으로, 함수 실행에 필요한 모든 리소스를 관리합니다. 각 Execution Environment는 다음과 같은 구성 요소를 포함합니다.</p> <ul> <li><strong>메모리(Memory)</strong> : 함수에 할당된 메모리 용량으로, 함수 생성 시 설정함</li> <li><strong>CPU</strong> : 할당된 메모리에 비례하여 CPU 성능이 결정됨</li> <li><strong>임시 스토리지(Ephemeral Storage)</strong> : 함수 실행 중 <code class="language-text">/tmp</code> 디렉터리를 통해 최대 512MB의 임시 스토리지를 제공함</li> <li><strong>타임아웃(Timeout)</strong> : 함수의 최대 실행 시간을 설정하며, 기본값은 3초, 최대 15분까지 가능함</li> <li><strong>핸들러(Handler)</strong> : 함수의 진입점으로, 이벤트를 처리하는 메인 메서드</li> <li><strong>프로비저닝된 동시성(Provisioned Concurrency)</strong> : 함수의 초기화를 미리 수행하여 콜드 스타트 지연을 최소화하는 옵션</li> <li><strong>환경 변수(Environment Variables)</strong> : 함수 실행 시 필요한 설정값들을 저장함</li> </ul> <p>각 함수는 고유한 Execution Environment에서 실행되며, 함수 간에 이 환경이 공유되지 않습니다. 그러나 동일한 함수가 반복 호출될 경우, 동일한 Execution Environment가 재사용될 수 있습니다. 이러한 재사용 특성은 함수의 성능 최적화에 중요한 영향을 미칩니다.</p> <h2 id="Execution-Environment의-생명주기" style="position:relative;"><a href="#Execution-Environment%EC%9D%98-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0" aria-label="Execution Environment의 생명주기 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Execution Environment의 생명주기</h2> <p>Execution Environment는 함수 실행 시 다음과 같은 단계를 거칩니다.</p> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/40906fec3ee86bf718cef36654db3be7/42cbc/aws-lambda.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 18.823529411764707%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAIAAAABPYjBAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAyUlEQVR42hWOuU4DQRBE9///hQCJwE4QAYlFgpAwh8CYU3vPbHdvX97ZYSxVUE/1gqpqhBaojzrBNGMjPKkE5V65UwUVOJczjuo+kRWzRWoIgbm6OuxvPurbe/48Po3HC4d9pjtuttZtsrxmedZuUzDjLuf0+E7Xu2b7drh8eWhnqkbnINYFY6HF2rTMq0eMP4J/a5pLGH4xficPOa8kqe7pqx86RnGvTuZmBghiLpbETmLLEDCiqC8lAWSIVCYtZnkO0zAEEWXRfx534Gm0wrmaAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='Lambda 실행 환경 수명 주기' title='' src='/static/40906fec3ee86bf718cef36654db3be7/ca1dc/aws-lambda.png' srcset='/static/40906fec3ee86bf718cef36654db3be7/e7570/aws-lambda.png 170w, /static/40906fec3ee86bf718cef36654db3be7/f46e7/aws-lambda.png 340w, /static/40906fec3ee86bf718cef36654db3be7/ca1dc/aws-lambda.png 680w, /static/40906fec3ee86bf718cef36654db3be7/02d09/aws-lambda.png 1020w, /static/40906fec3ee86bf718cef36654db3be7/9d567/aws-lambda.png 1360w, /static/40906fec3ee86bf718cef36654db3be7/42cbc/aws-lambda.png 1600w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>Lambda 실행 환경 수명 주기</figcaption> </figure></p> <ol> <li> <p><strong>초기화 단계(Init)</strong></p> <ul> <li><strong>Extension Init</strong> : Lambda에 부가 기능을 추가하는 확장 프로그램을 초기화합니다.</li> <li><strong>Runtime Init</strong> : 선택한 언어의 런타임을 초기화하여 코드 실행을 준비합니다.</li> <li><strong>Function Init</strong> : 함수의 정적 코드를 실행하여 핸들러를 준비합니다.</li> </ul> <p>이 단계는 함수의 첫 호출 시 발생하며, 최대 10초의 지연이 생길 수 있습니다. 이러한 지연을 **'콜드 스타트(Cold Start)'**라고 합니다. <strong>프로비저닝된 동시성</strong>을 사용하면 이 지연을 최소화할 수 있습니다.</p> </li> <li> <p><strong>호출 단계(Invoke)</strong> 실제 함수의 비즈니스 로직이 실행되는 단계로, 설정된 타임아웃 내에 완료되어야 합니다.</p> </li> <li> <p><strong>종료 단계(Shutdown)</strong> 함수 실행이 완료되면 런타임과 확장 프로그램을 종료하고, Execution Environment를 정리합니다. 이 단계는 최대 2초가 주어지며, 이후 강제로 종료됩니다.</p> </li> </ol> <h2 id="Execution-Environment의-재사용과-최적화" style="position:relative;"><a href="#Execution-Environment%EC%9D%98-%EC%9E%AC%EC%82%AC%EC%9A%A9%EA%B3%BC-%EC%B5%9C%EC%A0%81%ED%99%94" aria-label="Execution Environment의 재사용과 최적화 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Execution Environment의 재사용과 최적화</h2> <p>Lambda는 성능 최적화를 위해 종료 단계 이후에도 Execution Environment를 일정 시간 유지합니다. 이로 인해 다음과 같은 이점이 있는데요,</p> <ul> <li><strong>전역 변수의 유지</strong></li> </ul> <p>핸들러 외부에서 초기화된 변수나 객체는 Execution Environment가 재사용될 때 그대로 유지됩니다. 이 환경이 유지되어 전역변수로 만들어진 객체들의 참조가 지속적으로 남아있는 것이죠. </br></p> <p>예를 들어, 데이터베이스 연결을 전역 변수로 선언하면 첫 호출 이후 재사용되어 성능이 향상됩니다.</p> <div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token comment">// 초기화 코드</span> <span class="token keyword">let</span> dbConnection <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dbConnection<span class="token punctuation">)</span> <span class="token punctuation">{</span> dbConnection <span class="token operator">=</span> <span class="token function">initializeDBConnection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> exports<span class="token punctuation">.</span><span class="token function-variable function">handler</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// dbConnection을 사용한 로직</span> <span class="token punctuation">}</span></code></pre></div> <ul> <li><strong>임시 스토리지의 재사용</strong></li> </ul> <p><code>/tmp</code> 디렉터리에 저장된 파일은 Execution Environment가 유지되는 한 다음 호출 시에도 접근 가능합니다. 이를 활용하여 캐시나 임시 데이터를 저장할 수 있습니다.</p> <ul> <li><strong>백그라운드 프로세스의 지속</strong></li> </ul> <p>함수 종료 시 완료되지 않은 백그라운드 작업이나 콜백 함수는 Execution Environment 재사용 시 다시 실행될 수 있는데요, NodeJS 기반에서 Promise가 다음 함수에서 resolve 되어버리는 문제가 발생할 수 있습니다.</p> <h2 id="최적화-사례" style="position:relative;"><a href="#%EC%B5%9C%EC%A0%81%ED%99%94-%EC%82%AC%EB%A1%80" aria-label="최적화 사례 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>최적화 사례</h2> <ul> <li><strong>전역 변수 활용</strong></li> </ul> <p>데이터베이스 연결이나 외부 API 클라이언트 등 재사용 가능한 객체는 전역 변수로 선언하여 초기화 비용을 절감</p> <ul> <li><strong>임시 스토리지 활용</strong></li> </ul> <p>반복적으로 필요한 데이터는 <code>/tmp</code> 디렉터리에 저장하여 I/O 비용을 최소화</p> <ul> <li><strong>프로비저닝된 동시성 사용</strong></li> </ul> <p>콜드 스타트 지연이 허용되지 않는 경우, 프로비저닝된 동시성을 설정하여 항상 준비된 Execution Environment를 유지시킬 수 있음</p> <h2 id="마무리" style="position:relative;"><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC" aria-label="마무리 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>마무리</h2> <p>AWS Lambda의 Execution Environment는 함수의 성능과 효율성에 직접적인 영향을 미칩니다. 그 동작 원리와 생명주기를 깊이 이해하고 적절히 활용하면 서버리스 애플리케이션의 성능을 극대화할 수 있습니다. 앞으로 Lambda Extensions나 프로비저닝된 동시성 등 고급 주제에 대해서도 지속적으로 학습하여 더욱 최적화된 서버리스 아키텍처를 구축해보려고 합니다.</p> <p>감사합니다.</p>https://eeeasycode.dev/koyeb-free-tier/https://eeeasycode.dev/koyeb-free-tier/Sat, 07 Dec 2024 00:00:00 GMT<p>최근 프로젝트에서 백오피스를 만들게 되었는데, <strong>React</strong>로 Front 구현, **NodeJS(Express)**로 서버 개발을 진행하게 되었다. 백오피스 환경은 전부 무료 클라우드를 사용하고 싶어, React는 Netlify로 배포를 진행했고 NodeJS는 Koyeb로 배포를 진행했다. React는 성공적으로 배포가 되었지만, NodeJS는 배포 과정에서 에러를 계속 뱉어냈다.</p> <h1 id="NodeJS-배포" style="position:relative;"><a href="#NodeJS-%EB%B0%B0%ED%8F%AC" aria-label="NodeJS 배포 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>NodeJS 배포</h1> <p>Koyeb에서 지원하는 배포 방식에는 크게 두 가지가 있다.</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">1. GitHub Repository를 통한 배포 2. Docker를 통한 배포</code></pre></div> <h2 id="GitHub-Repository-배포" style="position:relative;"><a href="#GitHub-Repository-%EB%B0%B0%ED%8F%AC" aria-label="GitHub Repository 배포 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>GitHub Repository 배포</h2> <p>나는 처음에 GitHub Repository를 통해 배포를 시도했다. 배포를 시켜놓고 잠깐 볼 일을 보고오니 에러가 발생해 있었다. <img src="https://velog.velcdn.com/images/eeeasy-code/post/2c8da60d-73d4-47ae-9a7a-4bf8d0ce4db9/image.png" alt=""></p> <p>무슨 문제로 에러가 발생했는지 확인하기 위해서 로그를 확인했다. <img src="https://velog.velcdn.com/images/eeeasy-code/post/e59e0b62-954b-4bb8-ac31-c9dad982c4cd/image.png" alt=""></p> <p>확인 결과, node_modules를 찾지 못해 발생한 에러였다. 기존 런타임 command를 <code> npm start</code>로만 지정해주어 발생한 것으로, <code> npm install; npm start</code>를 입력하면 정상적으로 배포가 수행된다. <img src="https://velog.velcdn.com/images/eeeasy-code/post/090f718f-2025-4efb-8633-0836d00834ea/image.png" alt=""></p> <h2 id="Docker-배포" style="position:relative;"><a href="#Docker-%EB%B0%B0%ED%8F%AC" aria-label="Docker 배포 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Docker 배포</h2> <p>GitHub로 배포를 하고나니 시간이 어느정도 여유가 생겨서 Docker Image로 배포하는 방법도 궁금해졌다. 클라우드에 배포를 진행할 때 거의 대부분 github repository로만 배포하다보니까 다른 배포 방식은 어떻게 진행되는지 알아보고자 docker로도 배포를 해보았다.</p> <h3 id="Docker-배포-flow" style="position:relative;"><a href="#Docker-%EB%B0%B0%ED%8F%AC-flow" aria-label="Docker 배포 flow permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Docker 배포 flow</h3> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">1. Dockerfile을 통해 NodeJS 서버를 이미지로 변환 2. 생성된 이미지를 Docker Repository에 push 3. Docker Repository 주소를 사용하여 클라우드에 배포</code></pre></div> <p>Docker로 배포하게 되면 위와같은 flow로 진행된다. Docker에 대한 기본적인 지식과 학습이 선행되어야 매끄럽게 진행될 것 같다.</p> <p><strong>[Dockerfile]</strong></p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">FROM node:20 WORKDIR "/app" COPY ./package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD ["npm", "start"]</code></pre></div> <p>Dockerfile은 위와 같은 형태로 작성했다.</p> <p>이후, <code>docker build .</code> 명령어로 이미지를 빌드하고 나의 docker repository에 push하면 된다.</p> <p><img src="https://velog.velcdn.com/images/eeeasy-code/post/63aa54f4-8afe-4719-884a-1d72ea448fc3/image.png" alt=""></p> <p>마지막으로 koyeb 배포 설정에서 docker-repository 주소를 입력 후 배포하면 된다. <img src="https://velog.velcdn.com/images/eeeasy-code/post/8147f915-c502-4a48-866a-7c37dd1ebd75/image.png" alt=""></p>https://eeeasycode.dev/k8s_tls_https/https://eeeasycode.dev/k8s_tls_https/Wed, 27 Nov 2024 00:00:00 GMT<h3 id="Prerequisite-" style="position:relative;"><a href="#Prerequisite-" aria-label="Prerequisite permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Prerequisite :</h3> <p>본 글에서 언급되지만, 직접적인 설명은 하지 않는 것들입니다.</p> <ul> <li>kubernetes</li> <li>TLS</li> <li>istio</li> </ul> <h3 id="환경" style="position:relative;"><a href="#%ED%99%98%EA%B2%BD" aria-label="환경 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>환경</h3> <p>저희 프로젝트는 이미 GKE 내에 istio 관련 설정이 기본적으로 셋팅되어 있습니다.</p> <p><img src="/static/ezgif.com-video-to-gif-converter%20(1)-874781ad6c17bf6e844a1284aee4dc49.gif" alt="Service Traffic"></p> <ul> <li>GKE</li> <li>istio service mesh</li> <li>istio ingress gateway</li> <li>kiali</li> </ul> <h2 id="소개" style="position:relative;"><a href="#%EC%86%8C%EA%B0%9C" aria-label="소개 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>소개</h2> <p>서비스를 실제로 배포하고, 사용자들에게 더 안전한 서비스를 보장하기 위해 https 를 사용합니다. 저희 프로젝트의 end-point는 istio ingress gateway로 연결되어 있습니다. 그렇기에 TLS 설정을 istio ingress gateway에 적용해야 합니다.</p> <h2 id="Cert-Manager" style="position:relative;"><a href="#Cert-Manager" aria-label="Cert Manager permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Cert Manager</h2> <p>cert-manager는 kubernetes 클러스터에서 TLS 인증서의 관리를 자동화해 주는 오픈소스입니다. 이를 통해 인증서의 발급, 갱신 그리고 배포 과정을 자동화하여 보안을 강화하고 운영의 편의성을 높일 수 있습니다.</p> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/aab33472b561943d0008eff58de03c97/11bd9/image-1.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 57.64705882352942%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAACBUlEQVR42n1SW4/SQBjlpxlf/A/uA79gf4GJ8UGTJfFhE6Ix0Rjf1ahZ3bgiaNzltq5GZOkCkVLo0lKm3Hph2jLtcaYEMLvgSb5k5vQ757tME/gHUQR0uwYkSYFUV1Fv9jCjAa5CNyaQ6x3UeF7zTx+WTVf6hG3b8SUMQ0RhhJZC8PN3G5VqiydrGBALua8/UCxLKJ020O4QGIMpWtywWmvzHGNlGHJ9IgwZ3JmHQukc+aKE43wV51I3LsBrglIfqjqI47JHMJ64EBqB6XCKX8UKCuU6TgoSWrKBhPjAWIjR2MaQJ+h9k58tsDmLecbYegI+kwjBiTulHszBiE8xibWu6y0MN2HRIUDIGLlcGd+Oz5DNlVCpNGLTZYGr2GoouhMQBrd37mB3dw/J5F2kUi9W+9rgt91w/fIMQUDheSJmmM/9eLfbkFgLIy7wuSDg3UX8ZYc4+GDi/aGBdwcEjaaDk/wEH48Isl/MmGsr7up3uWYodkIIgWVN4fsM6UcqUg8vkX6s4979Lj59Jnj2XMd+WsOTp3082Ovi+9l0Nf7GkZevJxKI6UHTXPR6NjTdheMEnPPR7894ONA5R+n8/zuUZRmKIsdnT32LSfYmzMwt0PIOwrkHX32NUebGgjtNcs5ZLmx7h4wtqgbjC1DlFWjnDQLtKBYF4xpm7ZcLTs9wil3r8C+96oUzy4V+ZwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='cert-manager' title='' src='/static/aab33472b561943d0008eff58de03c97/ca1dc/image-1.png' srcset='/static/aab33472b561943d0008eff58de03c97/e7570/image-1.png 170w, /static/aab33472b561943d0008eff58de03c97/f46e7/image-1.png 340w, /static/aab33472b561943d0008eff58de03c97/ca1dc/image-1.png 680w, /static/aab33472b561943d0008eff58de03c97/02d09/image-1.png 1020w, /static/aab33472b561943d0008eff58de03c97/9d567/image-1.png 1360w, /static/aab33472b561943d0008eff58de03c97/11bd9/image-1.png 1420w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>cert-manager</figcaption> </figure></p> <p>cert manager는 다양한 CA와 연동할 수 있는데, 저희 서비스 설정에서는 Let`s Encrypt를 사용했습니다.</p> <h3 id="설정-과정" style="position:relative;"><a href="#%EC%84%A4%EC%A0%95-%EA%B3%BC%EC%A0%95" aria-label="설정 과정 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>설정 과정</h3> <h4 id="install-cert-manager" style="position:relative;"><a href="#install-cert-manager" aria-label="install cert manager permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>install cert-manager</h4> <p><code> kubectl apply -f <a href="https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml">https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml</a> </code></p> <p>해당 명령어를 통해 cert-manager를 설치합니다.</p> <p>저는 cert-manager-cainjector pod가 Kubernetes API 버전 호환성 문제로 인해 crash가 발생했는데, cert-manager의 버전을 v1.13.0 을 지정하여 설치했습니다.</p> <h4 id="ClusterIssuer-리소스-생성" style="position:relative;"><a href="#ClusterIssuer-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%83%9D%EC%84%B1" aria-label="ClusterIssuer 리소스 생성 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>ClusterIssuer 리소스 생성</h4> <div class="gatsby-highlight" data-language="zsh"><pre class="language-zsh"><code class="language-zsh">kkubectl apply -f - &lt;&lt;EOF #prod apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod-istio spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: your email privateKeySecretRef: name: letsencrypt-prod-istio solvers: - http01: ingress: class: istio --- #staging apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-staging-istio spec: acme: server: https://acme-staging-v02.api.letsencrypt.org/directory email: your email privateKeySecretRef: name: letsencrypt-staging-istio solvers: - http01: ingress: class: istio EOF clusterissuer.cert-manager.io/letsencrypt-prod-istio created clusterissuer.cert-manager.io/letsencrypt-staging-istio created</code></pre></div> <p>이 설정은 cert-manager를 사용하여 Let`s Encrypt로부터 TLS 인증서를 발급하는 리소스를 생성합니다. 해당 설정을 통해 클러스터 내에서 자동으로 인증서를 발급받고 갱신할 수 있게 됩니다.</p> <blockquote> <p>두 개의 ClusterIssuer 리소스 생성</p> <ul> <li>letsencrypt-prod-istio: Let’s Encrypt의 프로덕션 환경을 사용하여 실제 서비스에서 신뢰할 수 있는 인증서를 발급받기 위한 ClusterIssuer입니다.</li> <li>letsencrypt-staging-istio: Let’s Encrypt의 스테이징 환경을 사용하여 테스트 목적으로 인증서를 발급받기 위한 ClusterIssuer입니다. 이 환경에서 발급된 인증서는 브라우저에서 신뢰하지 않으며, 발급 제한 없이 테스트할 수 있습니다.</li> </ul> </blockquote> <blockquote> <p>설정의 목적과 역할</p> <ol> <li> <p>Let’s Encrypt와의 연동을 위한 ClusterIssuer 생성 • cert-manager가 Let’s Encrypt를 통해 TLS 인증서를 발급받을 수 있도록 설정합니다. • 두 개의 ClusterIssuer를 생성하여 테스트 환경과 실제 서비스 환경에서 모두 인증서를 발급받을 수 있습니다.</p> </li> <li> <p>ACME 프로토콜을 통한 인증서 발급 자동화 • ACME(Automatic Certificate Management Environment) 프로토콜을 사용하여 도메인 소유권을 자동으로 검증하고 인증서를 발급받습니다. • cert-manager는 ACME 프로토콜을 구현하여 Let’s Encrypt와 통신합니다.</p> </li> <li> <p>Istio Ingress Gateway와의 통합 • ingress.class: istio 설정을 통해 HTTP-01 챌린지를 처리할 때 Istio Ingress Gateway를 사용하도록 지정합니다. • 이를 통해 도메인 검증 요청이 Istio Ingress Gateway를 통해 cert-manager로 전달됩니다.</p> </li> </ol> </blockquote> <h4 id="Certificate-리소스-생성" style="position:relative;"><a href="#Certificate-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%83%9D%EC%84%B1" aria-label="Certificate 리소스 생성 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Certificate 리소스 생성</h4> <p>certificate.yaml 파일을 작성하여 원하는 도메인에 대한 TLS 인증서를 요청합니다.</p> <ol> <li>certificate.yaml 파일 작성</li> </ol> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> cert<span class="token punctuation">-</span>manager.io/v1 <span class="token key atrule">kind</span><span class="token punctuation">:</span> Certificate <span class="token key atrule">metadata</span><span class="token punctuation">:</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">namespace</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>system <span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token key atrule">secretName</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">issuerRef</span><span class="token punctuation">:</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> letsencrypt<span class="token punctuation">-</span>prod<span class="token punctuation">-</span>istio <span class="token comment"># 이전에 생성한 ClusterIssuer 이름</span> <span class="token key atrule">kind</span><span class="token punctuation">:</span> ClusterIssuer <span class="token key atrule">commonName</span><span class="token punctuation">:</span> your.domain.name <span class="token comment"># 실제 도메인 이름으로 변경</span> <span class="token key atrule">dnsNames</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> your.domain.name <span class="token comment"># 실제 도메인 이름으로 변경</span></code></pre></div> <ol start="2"> <li>certificate.yaml 파일 적용</li> </ol> <p><code> kubectl apply -f certificate.yaml </code></p> <p>이 명령어를 실행하여 Certificate 리소스를 클러스터에 적용합니다. cert-manager는 이 리소스를 감지하고, 지정된 ClusterIssuer를 통해 인증서를 발급받기 시작합니다.</p> <h4 id="인증서-발급-상태-확인" style="position:relative;"><a href="#%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%B0%9C%EA%B8%89-%EC%83%81%ED%83%9C-%ED%99%95%EC%9D%B8" aria-label="인증서 발급 상태 확인 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>인증서 발급 상태 확인</h4> <p><code> kubectl describe certificate istio-ingressgateway-certs -n istio-system </code></p> <p>위의 명령어를 입력하게 되면 아래와 같은 정보가 나오게 됩니다.</p> <div class="gatsby-highlight" data-language="zsh"><pre class="language-zsh"><code class="language-zsh">Name: istio-ingressgateway-certs Namespace: istio-system Labels: &lt;none&gt; Annotations: &lt;none&gt; API Version: cert-manager.io/v1 Kind: Certificate Metadata: Creation Timestamp: 2024-11-26T13:48:49Z Generation: 1 Resource Version: 1367115 UID: uuid Spec: Common Name: dns name Dns Names: dns name Issuer Ref: Kind: ClusterIssuer Name: letsencrypt-prod-istio Secret Name: istio-ingressgateway-certs Status: Conditions: Last Transition Time: 2024-11-26T14:08:12Z Message: Certificate is up to date and has not expired Observed Generation: 1 Reason: Ready Status: True Type: Ready Not After: 2025-02-24T13:09:40Z Not Before: 2024-11-26T13:09:41Z Renewal Time: 2025-01-25T13:09:40Z Revision: 1 Events: &lt;none&gt;</code></pre></div> <p>전체적인 의미로는</p> <p>인증서 발급 상태</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">• Ready 상태이며, 인증서가 정상적으로 발급되어 사용 중임을 나타냅니다. • Message에서 “Certificate is up to date and has not expired”라고 되어 있으므로, 인증서가 최신 상태입니다.</code></pre></div> <p>인증서의 유효 기간</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">• Not Before: 2024-11-26T13:09:41Z • 인증서의 유효 시작일입니다. • Not After: 2025-02-24T13:09:40Z • 인증서의 만료일로, 약 3개월의 유효 기간을 가집니다. Let’s Encrypt 인증서는 일반적으로 90일의 유효 기간을 갖습니다. • Renewal Time: 2025-01-25T13:09:40Z • cert-manager는 이 시점 전에 인증서를 자동으로 갱신합니다. 일반적으로 만료일의 30일 전에 갱신을 시작합니다.</code></pre></div> <p>인증서의 발급자</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">• Issuer Ref에서 Kind가 ClusterIssuer이고 Name이 letsencrypt-prod-istio이므로, 이 인증서는 Let’s Encrypt 프로덕션 환경을 사용하여 발급되었습니다.</code></pre></div> <p>인증서의 대상 도메인</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">• Common Name과 DNS Names에 dns name이 설정되어 있습니다. • 이 인증서는 dns name 도메인에 대해 발급되었습니다.</code></pre></div> <p>인증서의 저장 위치</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">• 발급된 인증서와 개인 키는 istio-ingressgateway-certs라는 이름의 Secret에 저장됩니다. • 이 Secret은 istio-system 네임스페이스에 존재하며, Istio Ingress Gateway에서 참조하여 TLS 통신에 사용됩니다.</code></pre></div> <h2 id="istio-ingress-gateway-설정" style="position:relative;"><a href="#istio-ingress-gateway-%EC%84%A4%EC%A0%95" aria-label="istio ingress gateway 설정 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>istio ingress gateway 설정</h2> <p>여기까지 왔다면, Kubernetes 클러스터 내에서 자동으로 TLS 인증서를 관리하는 설정까지 완료했습니다.</p> <p>그 다음으로는 Istio Ingress Gateway에 인증서를 적용하고, HTTPS 설정을 진행해야 합니다. 이를 통해 외부에서 들어오는 트래픽에 대해 안전한 통신을 보장할 수 있습니다.</p> <h3 id="설정-과정-1" style="position:relative;"><a href="#%EC%84%A4%EC%A0%95-%EA%B3%BC%EC%A0%95-1" aria-label="설정 과정 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>설정 과정</h3> <h4 id="Istio-Ingress-Gateway에-인증서-적용" style="position:relative;"><a href="#Istio-Ingress-Gateway%EC%97%90-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EC%A0%81%EC%9A%A9" aria-label="Istio Ingress Gateway에 인증서 적용 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Istio Ingress Gateway에 인증서 적용</h4> <p>Istio Ingress Gateway가 발급된 TLS 인증서를 사용하도록 설정하려면, 인증서가 저장된 Secret을 Gateway에 마운트해야 합니다.</p> <p>1.1 Istio Ingress Gateway 디플로이먼트 수정</p> <p>먼저, Istio Ingress Gateway의 Deployment를 수정하여 인증서 Secret을 마운트합니다.</p> <p><code> kubectl edit deployment istio-ingressgateway -n istio-system </code></p> <p>위 명령어를 실행하면 기본 에디터에서 Deployment의 YAML 파일이 열립니다. 여기서 다음과 같이 수정합니다.</p> <p>volumes 섹션 추가</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token key atrule">template</span><span class="token punctuation">:</span> <span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">secret</span><span class="token punctuation">:</span> <span class="token key atrule">secretName</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">optional</span><span class="token punctuation">:</span> <span class="token boolean important">true</span></code></pre></div> <p>volumeMounts 섹션 수정</p> <p>containers 섹션 아래의 volumeMounts에 다음 내용을 추가합니다.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">containers</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>proxy <span class="token comment"># Istio Ingress Gateway의 컨테이너 이름입니다.</span> <span class="token key atrule">volumeMounts</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">mountPath</span><span class="token punctuation">:</span> /etc/istio/ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">name</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">readOnly</span><span class="token punctuation">:</span> <span class="token boolean important">true</span></code></pre></div> <p>전체 예시:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1 <span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment <span class="token key atrule">metadata</span><span class="token punctuation">:</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway <span class="token key atrule">namespace</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>system <span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token key atrule">selector</span><span class="token punctuation">:</span> <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span> <span class="token key atrule">app</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway <span class="token key atrule">template</span><span class="token punctuation">:</span> <span class="token key atrule">metadata</span><span class="token punctuation">:</span> <span class="token key atrule">labels</span><span class="token punctuation">:</span> <span class="token key atrule">app</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway <span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">secret</span><span class="token punctuation">:</span> <span class="token key atrule">secretName</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">optional</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token key atrule">containers</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>proxy <span class="token key atrule">volumeMounts</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">mountPath</span><span class="token punctuation">:</span> /etc/istio/ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">name</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway<span class="token punctuation">-</span>certs <span class="token key atrule">readOnly</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token comment"># 나머지 설정들은 기존 내용 유지</span></code></pre></div> <p>변경 사항을 저장하면 Kubernetes는 Deployment의 변경을 감지하고 Istio Ingress Gateway 파드를 재시작하여 새로운 설정을 적용합니다.</p> <h4 id="Istio-Ingress-Gateway-서비스의-포트-확인" style="position:relative;"><a href="#Istio-Ingress-Gateway-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%9D%98-%ED%8F%AC%ED%8A%B8-%ED%99%95%EC%9D%B8" aria-label="Istio Ingress Gateway 서비스의 포트 확인 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Istio Ingress Gateway 서비스의 포트 확인</h4> <p>Istio Ingress Gateway 서비스에서 HTTPS를 위한 포트가 올바르게 설정되어 있는지 확인합니다.</p> <p><code>kubectl get svc istio-ingressgateway -n istio-system</code></p> <p>출력 결과에서 PORT(S) 열에 443 포트가 포함되어 있어야 합니다.</p> <p>예시:</p> <div class="gatsby-highlight" data-language="zsh"><pre class="language-zsh"><code class="language-zsh">NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE istio-ingressgateway LoadBalancer 10.100.200.1 34.123.45.67 15021/TCP,80/TCP,443/TCP,15443/TCP,15012/TCP,15017/TCP 2d</code></pre></div> <p>만약 443 포트가 없거나 다른 포트로 설정되어 있다면, 다음과 같이 서비스의 설정을 수정합니다.</p> <p><code>kubectl edit svc istio-ingressgateway -n istio-system</code></p> <p>ports 섹션에 다음 내용을 추가하거나 수정합니다.</p> <p>ports:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> https <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">443</span> <span class="token key atrule">targetPort</span><span class="token punctuation">:</span> <span class="token number">8443</span> <span class="token key atrule">protocol</span><span class="token punctuation">:</span> TCP</code></pre></div> <h4 id="Gateway-및-VirtualService-설정" style="position:relative;"><a href="#Gateway-%EB%B0%8F-VirtualService-%EC%84%A4%EC%A0%95" aria-label="Gateway 및 VirtualService 설정 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Gateway 및 VirtualService 설정</h4> <p>이제 Istio의 Gateway 및 VirtualService 리소스를 생성하여 Istio Ingress Gateway가 HTTPS 요청을 처리하고 적절한 서비스로 라우팅하도록 설정합니다.</p> <p>Gateway 리소스 생성</p> <p>gateway.yaml 파일을 생성하고 다음과 같이 작성합니다.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> networking.istio.io/v1beta1 <span class="token key atrule">kind</span><span class="token punctuation">:</span> Gateway <span class="token key atrule">metadata</span><span class="token punctuation">:</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> my<span class="token punctuation">-</span>gateway <span class="token key atrule">namespace</span><span class="token punctuation">:</span> your<span class="token punctuation">-</span>namespace <span class="token comment"># 실제 네임스페이스로 변경</span> <span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token key atrule">selector</span><span class="token punctuation">:</span> <span class="token key atrule">istio</span><span class="token punctuation">:</span> ingressgateway <span class="token comment"># Istio Ingress Gateway를 선택</span> <span class="token key atrule">servers</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token key atrule">number</span><span class="token punctuation">:</span> <span class="token number">443</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> https <span class="token key atrule">protocol</span><span class="token punctuation">:</span> HTTPS <span class="token key atrule">tls</span><span class="token punctuation">:</span> <span class="token key atrule">mode</span><span class="token punctuation">:</span> SIMPLE <span class="token key atrule">credentialName</span><span class="token punctuation">:</span> istio<span class="token punctuation">-</span>ingressgateway<span class="token punctuation">-</span>certs <span class="token comment"># Secret의 이름과 일치해야 함</span> <span class="token key atrule">hosts</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"your.domain.com"</span> <span class="token comment"># 실제 도메인 이름으로 변경</span></code></pre></div> <p>적용하기:</p> <p><code>kubectl apply -f gateway.yaml</code></p> <p>VirtualService 리소스 생성</p> <p>virtualservice.yaml 파일을 생성하고 다음과 같이 작성합니다.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> networking.istio.io/v1beta1 <span class="token key atrule">kind</span><span class="token punctuation">:</span> VirtualService <span class="token key atrule">metadata</span><span class="token punctuation">:</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> my<span class="token punctuation">-</span>virtualservice <span class="token key atrule">namespace</span><span class="token punctuation">:</span> your<span class="token punctuation">-</span>namespace <span class="token comment"># 실제 네임스페이스로 변경</span> <span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token key atrule">hosts</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"your.domain.com"</span> <span class="token comment"># 실제 도메인 이름으로 변경</span> <span class="token key atrule">gateways</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> my<span class="token punctuation">-</span>gateway <span class="token key atrule">http</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">match</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">uri</span><span class="token punctuation">:</span> <span class="token key atrule">prefix</span><span class="token punctuation">:</span> <span class="token string">"/"</span> <span class="token comment"># 모든 요청 매칭</span> <span class="token key atrule">route</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">destination</span><span class="token punctuation">:</span> <span class="token key atrule">host</span><span class="token punctuation">:</span> your<span class="token punctuation">-</span>service <span class="token comment"># 실제 서비스의 이름으로 변경</span> <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token key atrule">number</span><span class="token punctuation">:</span> <span class="token number">80</span> <span class="token comment"># 서비스의 포트 번호로 변경</span></code></pre></div> <p>적용하기:</p> <p><code>kubectl apply -f virtualservice.yaml</code></p> <h2 id="DNS-설정-확인-및-업데이트" style="position:relative;"><a href="#DNS-%EC%84%A4%EC%A0%95-%ED%99%95%EC%9D%B8-%EB%B0%8F-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8" aria-label="DNS 설정 확인 및 업데이트 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>DNS 설정 확인 및 업데이트</h2> <p>도메인 이름이 Istio Ingress Gateway의 외부 IP 주소를 가리키도록 DNS 설정을 확인하고 필요하다면 업데이트합니다.</p> <h3 id="설정-과정-2" style="position:relative;"><a href="#%EC%84%A4%EC%A0%95-%EA%B3%BC%EC%A0%95-2" aria-label="설정 과정 2 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>설정 과정</h3> <h4 id="Istio-Ingress-Gateway의-외부-IP-확인" style="position:relative;"><a href="#Istio-Ingress-Gateway%EC%9D%98-%EC%99%B8%EB%B6%80-IP-%ED%99%95%EC%9D%B8" aria-label="Istio Ingress Gateway의 외부 IP 확인 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Istio Ingress Gateway의 외부 IP 확인</h4> <p><code>kubectl get svc istio-ingressgateway -n istio-system</code></p> <p>출력 결과에서 EXTERNAL-IP 열에 표시된 IP 주소를 확인합니다.</p> <h4 id="DNS-레코드-업데이트" style="position:relative;"><a href="#DNS-%EB%A0%88%EC%BD%94%EB%93%9C-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8" aria-label="DNS 레코드 업데이트 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>DNS 레코드 업데이트</h4> <p>DNS 제공자의 관리 콘솔에서 해당 도메인의 A 레코드를 Istio Ingress Gateway의 외부 IP 주소로 설정합니다.</p> <h4 id="HTTPS-연결-테스트" style="position:relative;"><a href="#HTTPS-%EC%97%B0%EA%B2%B0-%ED%85%8C%EC%8A%A4%ED%8A%B8" aria-label="HTTPS 연결 테스트 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>HTTPS 연결 테스트</h4> <p>설정이 완료되면 HTTPS 연결이 정상적으로 동작하는지 테스트합니다.</p> <div class="gatsby-highlight" data-language="zsh"><pre class="language-zsh"><code class="language-zsh">curl -vk https://your.domain.com</code></pre></div> <h2 id="마무리" style="position:relative;"><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC" aria-label="마무리 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>마무리</h2> <p>이렇게해서 Istio Ingress Gateway에 TLS 인증서를 적용하고, HTTPS 설정을 진행해보았습니다. 이제 외부 클라이언트는 안전하게 HTTPS를 통해 서비스에 접근할 수 있습니다.</p> <p>추가적으로, 내부 서비스 간 통신에 대해 mTLS를 적용하여 클러스터 내 보안을 강화할 수도 있습니다.</p> <h3 id="참고-자료" style="position:relative;"><a href="#%EC%B0%B8%EA%B3%A0-%EC%9E%90%EB%A3%8C" aria-label="참고 자료 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>참고 자료</h3> <p><a href="https://istio.io/latest/docs/tasks/traffic-management/ingress/secure-ingress/">Istio 공식 문서 - TLS 설정</a></p> <p><a href="https://cert-manager.io/docs/">cert-manager 공식 문서</a></p>https://eeeasycode.dev/oss_project_01/https://eeeasycode.dev/oss_project_01/Sat, 16 Nov 2024 00:00:00 GMT<p>최근에 김인제 님께서 진행하시는 "오픈 소스 멘토링 7기" 활동을 진행했어요. 인제 님에 대한 소개는 <a href="https://github.com/injae-kim">깃허브 링크</a>로 대신하겠습니다!</p> <p>주변 지인의 추천으로 오픈 소스 멘토링을 소개받았었고, 지원자가 정말 많을 것이라는 이야기를 듣고 솔직히 멘토링에 선발될거라고 생각도 못했어요. 그런데 한 번에 되어버렸네요! ㅋㅋ</p> <p><span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; margin-bottom: 16px;'> <a class='gatsby-resp-image-link' href='/static/037dd241cf2b41ae9f383b0cf271c0e3/00706/image.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 25.294117647058822%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABBklEQVR42nWPS2qDUBSG3UApHfgKOGg1UdNKm0CLDwpaFSw+QkAQQyKIKBTFgUM3kU10OV1NN/C33oGTtIOP8/FzHvdSW/MN74cP3N6JkGUZPM+DpmmwLHsBwzD/5hMcx4GqxjM+v76hbZ6x3TzhQdOwWq0uUFV1RlEUrNdr4tMjJiYXBAGUpNzj8eWVhIc8R1VVOB6PKIoCTdOgLEvUdT37fr9HHMfIf3uzLEMYhtjtdkjTFJIkgWIZGjfXV+Tq6XQig+M4YhgGdF2Hvu/Rti3Jp0NRFJGFlmVB1/UZwzAgiiKoxWJB/r5cLuH7PjzPQ5IkxG3bJtVxHLiuSzwIAlJN0/xz4Q/gMKjtkL3TWwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='image' title='' src='/static/037dd241cf2b41ae9f383b0cf271c0e3/ca1dc/image.png' srcset='/static/037dd241cf2b41ae9f383b0cf271c0e3/e7570/image.png 170w, /static/037dd241cf2b41ae9f383b0cf271c0e3/f46e7/image.png 340w, /static/037dd241cf2b41ae9f383b0cf271c0e3/ca1dc/image.png 680w, /static/037dd241cf2b41ae9f383b0cf271c0e3/00706/image.png 862w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span></p> <h2 id="왜-오픈-소스를" style="position:relative;"><a href="#%EC%99%9C-%EC%98%A4%ED%94%88-%EC%86%8C%EC%8A%A4%EB%A5%BC" aria-label="왜 오픈 소스를 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>왜 오픈 소스를?</h2> <p>사실, 오픈 소스에 대한 관심은 이전부터 있었고, 올해 첫 TOSS/AOP 라이브러리에 한글 문서 번역 기여를 진행했었어요. 물론 문서 번역이라는 간단한 기여였지만 제가 올린 PR이 merge되었을 때 정말 뿌듯했었죠.</p> <p>그럼 왜 오픈 소스 기여 활동을 하고 싶은 것이냐고 묻는다면, 아래의 내용으로 정리해볼 수 있을 것 같네요.</p> <h3 id="1-내가-사용하는-라이브러리-혹은-프레임워크를-운영하시는-메인테이너-분들의-가치관을-가장-직관적으로-알-수-있다" style="position:relative;"><a href="#1-%EB%82%B4%EA%B0%80-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%ED%98%B9%EC%9D%80-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EB%A5%BC-%EC%9A%B4%EC%98%81%ED%95%98%EC%8B%9C%EB%8A%94-%EB%A9%94%EC%9D%B8%ED%85%8C%EC%9D%B4%EB%84%88-%EB%B6%84%EB%93%A4%EC%9D%98-%EA%B0%80%EC%B9%98%EA%B4%80%EC%9D%84-%EA%B0%80%EC%9E%A5-%EC%A7%81%EA%B4%80%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%95%8C-%EC%88%98-%EC%9E%88%EB%8B%A4" aria-label="1 내가 사용하는 라이브러리 혹은 프레임워크를 운영하시는 메인테이너 분들의 가치관을 가장 직관적으로 알 수 있다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>1. 내가 사용하는 라이브러리 혹은 프레임워크를 운영하시는 메인테이너 분들의 가치관을 가장 직관적으로 알 수 있다.</h3> <blockquote> <p>이게 무슨 말이냐면, 결국 기여하는 과정에서 메인테이너 분들의 리뷰를 받게 되어있는데 이 과정에서 그들이 어떠한 가치관을 갖고 해당 기술을 운영, 유지보수하는지 내 코드와 비교하며 대화를 나눌 수 있다는 것이죠.</p> <p>PR 뿐만 아니라 github에 올라온 Issue들을 잘 살펴보면, 메인테이너 분들이 의견을 공유해주시는데 이것도 그분들의 생각을 직관적으로 볼 수 있어요.</p> <p>이를 통해서, "해당 기술은 어떠한 방향성으로 사용해야 하는구나" 와 같은 생각을 정리할 수 있었던 것 같아요. 그리고, PR이 merge가 되지 않거나, issue가 더 이상 논의되지 않아도 저는 충분히 가치가 있다고 생각해요. 그 과정에서 수많은 개발자들, 메인테이너들과 의견을 나누는 그 과정이 중요하다고 생각하거든요.</p> </blockquote> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/7d7bf61cfb17bfb13daf9ab5c0d4e429/4020a/image-3.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 59.411764705882355%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB/ElEQVR42nVSyZbTMBD0EUgy8b7KluPdsRNngRneXDjy/19UVCsJE+BxqNf1JHV1t7qs44+fSPSAZjwh1T2ivEFctIjzG+wwx9ZXBg65F+s/4EYFEr6XXCfKYYUkqhpx/PqObr5gvnzHuHwzfDq/GeyXV3Oe1yNevATbIGWBD9hhRijDrc8bj1UU8mpAwYSynaCbPTS5F+W3hCBjdwprJ8Invpe4dkKs7dDwlYB8w2gJiVSFbjqhP1wYz2iJgV0WFK+GI3b9AbtuRs+uK3LJWd3FjKj9IW6tWMkNFAo1IS86tBSp+QXn4Yrr/IbT8o6+W6D5T4VuoVTN2CBNK8RxjaisEQqyEls3hiWqtptAZyMqPWEYTijZ8TVWeNU1Dt0BM7saqgkhC/teipjLicijIKdogZDfIXcOdSyZ3/ETqKxBmc8YmFzrPTo9osgKqFQzEqpEzI36fobgCSIUeDfueL8FWTXSprpHyKXPR46TEPE9JnDZgSfgGxE24tL1ndtGkJ/pJxpp0SArOygi3/X0YG0WUXMpDwc8EKSlWYJs9W9YX7YBjdlg4AabceEmr2aTsm1J3IjvOIHEDT9dfCi4WedfPNnmTIsc0O4XIyidVSwgZl5T6GGVZ/xXUEZ+jCPj5bRNIeamhUqOre9+rFnAnBPl/U6Kyv2LTOHG+AXL+T7k21IZcAAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='NestJS의 Member 분이 제안한 이슈' title='' src='/static/7d7bf61cfb17bfb13daf9ab5c0d4e429/ca1dc/image-3.png' srcset='/static/7d7bf61cfb17bfb13daf9ab5c0d4e429/e7570/image-3.png 170w, /static/7d7bf61cfb17bfb13daf9ab5c0d4e429/f46e7/image-3.png 340w, /static/7d7bf61cfb17bfb13daf9ab5c0d4e429/ca1dc/image-3.png 680w, /static/7d7bf61cfb17bfb13daf9ab5c0d4e429/02d09/image-3.png 1020w, /static/7d7bf61cfb17bfb13daf9ab5c0d4e429/9d567/image-3.png 1360w, /static/7d7bf61cfb17bfb13daf9ab5c0d4e429/4020a/image-3.png 1642w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>NestJS의 Member 분이 제안한 이슈</figcaption> </figure></p> <h3 id="2-내가-기여한-코드가-몇-년-후에는-수-억명의-개발자들이-사용하는-코드가-될-수-있다" style="position:relative;"><a href="#2-%EB%82%B4%EA%B0%80-%EA%B8%B0%EC%97%AC%ED%95%9C-%EC%BD%94%EB%93%9C%EA%B0%80-%EB%AA%87-%EB%85%84-%ED%9B%84%EC%97%90%EB%8A%94-%EC%88%98-%EC%96%B5%EB%AA%85%EC%9D%98-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%93%A4%EC%9D%B4-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%BD%94%EB%93%9C%EA%B0%80-%EB%90%A0-%EC%88%98-%EC%9E%88%EB%8B%A4" aria-label="2 내가 기여한 코드가 몇 년 후에는 수 억명의 개발자들이 사용하는 코드가 될 수 있다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>2. 내가 기여한 코드가 몇 년 후에는 수 억명의 개발자들이 사용하는 코드가 될 수 있다.</h3> <blockquote> <p>이건 오픈 소스 멘토링을 진행하며 인제님께서 말씀해주셨던 내용인데요. 제가 앞으로도 오픈 소스 기여 활동을 꾸준히 해야겠다는 의지를 갖게 해준 말이었어요.</p> <p>내 작은 코드 하나가 전 세계 개발자들에게 도달해 큰 가치를 줄 수 있다는 것 자체만으로도 오픈 소스에 기여할 충분한 동기가 되는 것 같아요. 그게 문서화 작업이라도, 누군가는 그 문서를 보고 도움을 받을 수 있으니까요!</p> </blockquote> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/f6270a3e7b210ac248515360d30faaa6/7f46a/image-1.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 22.352941176470587%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAm0lEQVR42p3NTQ6CMBQE4MadCpYWWkpbSoEQftQYjTFuvID3v89Y2ehGTVx8s3ozj9yud9h6xLA/o9ueoH0P5TpI24LnDkyWHyXCQugaRbg3oZdpD7JYxpCmQTsc4NopjA+zV9F9lYgSNLPzOM0MyCYtkKoKVX9Es7tAN1P4WiFiOdaJ/Cli73KQZ9BUgxceXHlEXGNFBWKu/vIAroBl1oVqbVgAAAAASUVORK5CYII='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='어떤 분이 제 PR 내용을 보고 남긴 코멘트..!' title='' src='/static/f6270a3e7b210ac248515360d30faaa6/ca1dc/image-1.png' srcset='/static/f6270a3e7b210ac248515360d30faaa6/e7570/image-1.png 170w, /static/f6270a3e7b210ac248515360d30faaa6/f46e7/image-1.png 340w, /static/f6270a3e7b210ac248515360d30faaa6/ca1dc/image-1.png 680w, /static/f6270a3e7b210ac248515360d30faaa6/02d09/image-1.png 1020w, /static/f6270a3e7b210ac248515360d30faaa6/9d567/image-1.png 1360w, /static/f6270a3e7b210ac248515360d30faaa6/7f46a/image-1.png 1646w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>어떤 분이 제 PR 내용을 보고 남긴 코멘트..!</figcaption> </figure></p> <h3 id="3-개발-스킬-외에도-문서화-테스트-코드-커뮤니케이션-스킬을-키울-수-있다" style="position:relative;"><a href="#3-%EA%B0%9C%EB%B0%9C-%EC%8A%A4%ED%82%AC-%EC%99%B8%EC%97%90%EB%8F%84-%EB%AC%B8%EC%84%9C%ED%99%94-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%BB%A4%EB%AE%A4%EB%8B%88%EC%BC%80%EC%9D%B4%EC%85%98-%EC%8A%A4%ED%82%AC%EC%9D%84-%ED%82%A4%EC%9A%B8-%EC%88%98-%EC%9E%88%EB%8B%A4" aria-label="3 개발 스킬 외에도 문서화 테스트 코드 커뮤니케이션 스킬을 키울 수 있다 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>3. 개발 스킬 외에도 문서화, 테스트 코드, 커뮤니케이션 스킬을 키울 수 있다.</h3> <blockquote> <p>실제로 내 아이디어를 누군가에게 설명하고 납득시켜야 하는 과정이 필요해요. 내가 아무리 좋은 코드를 제안해도 (물론 너무 좋다면 approve가 되겠지만), 테스트 코드가 없어 검증하지 못하는 상황, 무슨 기능에 대한 내용인지에 대한 명확한 문서화가 없는 상황, 커뮤니케이션이 올바르게 되지 않아 지연되는 상황 등 기여에 대한 기회를 잃을 수 있는 문제들이 존재해요.</p> <p>그래서, 좋은 코드를 작성하는 것도 중요하지만 내가 어떠한 문제를 파악했고, 이를 위한 개선점이 뭐가 존재하고 실제 구현 코드는 이렇다. 이에 대한, 검증은 이런 식으로 이루어졌다. 등 논리적으로 설명해 상대방을 납득시키는 것이 가장 중요한 점인 것 같아요.</p> <p>또한, 대부분의 오픈 소스는 영어로 커뮤니케이션을 진행해야 하는데 내 생각을 영어로 정리해볼 수 있는 좋은 기회인 것 같아요. (저도 아직 번역기의 도움을 많이 받긴하지만) 글로벌 취업이 목표가 아니더라도 대부분의 공식 문서나 커뮤니티에서는 영어를 기본적으로 사용하기에 이런 스킬을 기르는 것도 강점이 될 것 같아요.</p> </blockquote> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/bff9bae223371852bad782a3ca5e1bb4/cc39a/image-2.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 55.294117647058826%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB2UlEQVR42oWSW3ObMBCF/ZaZmDhgOyBANy7GYMLFBJs4Tqd96LT9/3/odCU7aWfaTB4+rbTSHOmsdvbz1w+IvIEsj4g3A3zdIkg6+KrBgm2vFB9yf913owqLtcAsiDV8nmE/fUNWDfACjVWYYhkmNFfwfGlzHtM2t2TJezQ5l86YuUtnnGWI2Y2zwjrUOLx8RTe+YH88o+6P6McTdsMr8u4LkqKGyisb/0ZvdpBpiXT7CCYyzF0fMzOsmETVjiiJup9sLOo9Ap6DqS1CmcOPE3uxH6fkKLUxIGfGXUDrFVNwvIAEabhfx3TTBlKnECqBTjJwIRGEEaKYEBwRl4iFQmyjphxFeSHiAoslw/xN0HuIUJQl+mHE4fiMcTqjpXnz9IynacIwdhjHA7q2RdPU6Psej80Oj22JhiirwmrMXSPoXgTzPCfBCdPpjP3hhKLqsK0HFLs9srxAluXQWv9BKShpuKyX/psgvdA1gmkCqSRZFZBkQ3AOTnY5jyEoZzF5Qv4Hdx1eLDu2hhFCqk/EOAIm7IvNgX9hH3JHNXQ8ZgR9akhO/cZxR+KL68Yt/f4tWTDMPfYpzpXZzcJFXXynmr1CbipqkQ21h7KtwESOB2oPc5lpL+PmM34DumUmq0cqF/QAAAAASUVORK5CYII='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='제가 제안한 내용에 대한 검증 과정을 설명하고 있어요.' title='' src='/static/bff9bae223371852bad782a3ca5e1bb4/ca1dc/image-2.png' srcset='/static/bff9bae223371852bad782a3ca5e1bb4/e7570/image-2.png 170w, /static/bff9bae223371852bad782a3ca5e1bb4/f46e7/image-2.png 340w, /static/bff9bae223371852bad782a3ca5e1bb4/ca1dc/image-2.png 680w, /static/bff9bae223371852bad782a3ca5e1bb4/02d09/image-2.png 1020w, /static/bff9bae223371852bad782a3ca5e1bb4/9d567/image-2.png 1360w, /static/bff9bae223371852bad782a3ca5e1bb4/cc39a/image-2.png 1694w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>제가 제안한 내용에 대한 검증 과정을 설명하고 있어요.</figcaption> </figure></p> <p>이런 장점들이 저를 오픈 소스라는 생태계로 빠지게 만들어 준 것 같아요. 제가 작성한 내용 말고도 오픈 소스 활동은 정말 많은 장점이 존재하니까 꼭 시작했으면 좋겠어요.</p> <h2 id="오픈-소스-멘토링" style="position:relative;"><a href="#%EC%98%A4%ED%94%88-%EC%86%8C%EC%8A%A4-%EB%A9%98%ED%86%A0%EB%A7%81" aria-label="오픈 소스 멘토링 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>오픈 소스 멘토링?</h2> <p>Line 백엔드 개발자로, 다양한 오픈 소스 활동을 하고 계신 김인제님께서 진행하는 '<a href="https://medium.com/@injae-kim">오픈 소스 멘토링</a>'의 7기 과정에 참여했어요. 멘토링은 인제님이 먼저 가이드라인을 제시해주고 저희는 따라가기만 하면 되는 활동으로 진행되었어요.</p> <h3 id="진행-과정" style="position:relative;"><a href="#%EC%A7%84%ED%96%89-%EA%B3%BC%EC%A0%95" aria-label="진행 과정 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>진행 과정</h3> <ol> <li>진행하고 싶은 issue 선정</li> <li>선정한 issue에 대한 논의</li> <li>issue에 대한 PR 올리기</li> <li>오픈소스 기부</li> </ol> <p>멘토링은 위의 과정대로 진행되었어요. 사실, 진행하면서 내가 아무것도 기여하지 못하면 어떻게 하지? 라는 걱정이 앞섰는데요. 그 이유는 이슈 선정부터 아무것도 못하겠다는 생각이 들었기 때문이에요. ㅋㅋㅋ 하나같이 이슈들은 개발 천재들이 올린 것 같고, 그 안에서 이야기하는 분들도 뭔가 범접하기 힘든 아우라가 있었거든요. 그래서, issue의 다양한 label들을 활용해서 그 중 가장 쉬워보이는 issue들을 몇 개 선정했어요.</p> <p>issue를 선정하고 나면, 인제님께서 각 issue에 대한 피드백을 꼼꼼하게 달아주시는데 정말 많은 참여자 분들의 이슈를 하나하나 작성해주시는 것보고 정말 너무 감사했습니다. 해당 피드백을 기반으로 내가 어떤 식으로 기여할지 가이드를 잡고, 멘토링이 있기 전까지 계속 그 issue들에 대해서 고민했어요.</p> <p>멘토링 당일에 인제님과 멘티분들이 한 자리에 모여 간단한 소개를 시작으로 각자 선정한 issue를 한번씩 훑어보는데요. 정말 다양한 분야의 오픈 소스를 선정하고 하나도 겹치지 않는 것을 보고 신기했습니다. 하나쯤은 겹치는 이슈가 있을까 걱정했었거든요.</p> <p>그렇게, issue에 대한 PR을 작성하면서 모르거나 막히는 부분이 있을 때, 인제님이 직접 피드백을 주시는데 이게 큰 도움이 되었던 것 같아요. 특정 기술에 대한 해결 방법보다, 어떤 식으로 오픈 소스에 접근하는지, 메인테이너 분들과 소통을 어떻게 해야하는지, 상대방을 설득하기 위해 어떤 식으로 PR을 작성해야 하는지 등 다양한 피드백을 주셨어요. (멘토링의 가장 큰 장점인 것 같았어요!)</p> <p>그 결과, 저는 NestJS에 대한 PR 2개, Spring Cloud에 대한 PR 1개를 작성해 기여할 수 있었습니다!!! 그리고, 멘토링의 관례?라고 해야할까요? 내가 원하는 오픈소스에 기부하는 활동이 있었는데, 저는 제가 기여한 NestJS에 기부를 진행했어요. 뭔가 내가 코드로도 기여하고, 직접 기부도 하니까 더 잘됐으면 좋겠다는 생각이 들더라구요. 좋은 경험이었던 것 같아요.</p> <h2 id="마무리" style="position:relative;"><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC" aria-label="마무리 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>마무리</h2> <h3 id="멘토링에서-느낀-점" style="position:relative;"><a href="#%EB%A9%98%ED%86%A0%EB%A7%81%EC%97%90%EC%84%9C-%EB%8A%90%EB%82%80-%EC%A0%90" aria-label="멘토링에서 느낀 점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>멘토링에서 느낀 점</h3> <p>멘토링을 참가하면서 느낀 점은 크게 3가지로 정리할 수 있을 것 같아요.</p> <h3 id="1-오픈-소스-기여에-대해-무서워하지-말자-PR-올리고-issue-만드는-거-쫄지말-것" style="position:relative;"><a href="#1-%EC%98%A4%ED%94%88-%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC%EC%97%90-%EB%8C%80%ED%95%B4-%EB%AC%B4%EC%84%9C%EC%9B%8C%ED%95%98%EC%A7%80-%EB%A7%90%EC%9E%90-PR-%EC%98%AC%EB%A6%AC%EA%B3%A0-issue-%EB%A7%8C%EB%93%9C%EB%8A%94-%EA%B1%B0-%EC%AB%84%EC%A7%80%EB%A7%90-%EA%B2%83" aria-label="1 오픈 소스 기여에 대해 무서워하지 말자 PR 올리고 issue 만드는 거 쫄지말 것 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>1. 오픈 소스 기여에 대해 무서워하지 말자. (PR 올리고, issue 만드는 거 쫄지말 것)</h3> <blockquote> <p>처음에는 PR을 올릴지 말지, 인제님께 계속 물어봤어요. 이렇게 올려도 되는건가? 라는 걱정도 있었고, 누군가가 내 PR을 보고 좋지 않은 리뷰를 남기면 어떻게 하지? 라는 생각이 들었거든요. 근데 인제님꼐서 제가 올린 PR을 보고 이슈어나 메인테이너 분들은 좋아할 거라고, 걱정하지말고 PR 올려보라고 말씀해주셨어요. 사실, 어떻게보면 정답이 있는 것도 아니고 잘못된 부분이 있어도 그걸 굳이 비난할까? 싶더라구요. 그래서 PR을 올리고 issue를 만드는 것에 대해 걱정과 부담감을 내려놓고 마음껏 기여할 수 있게 된 계기가 된 것 같아요.</p> </blockquote> <h3 id="2-인내심을-갖자" style="position:relative;"><a href="#2-%EC%9D%B8%EB%82%B4%EC%8B%AC%EC%9D%84-%EA%B0%96%EC%9E%90" aria-label="2 인내심을 갖자 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>2. 인내심을 갖자.</h3> <blockquote> <p>메인테이너 분들도 똑같이 본업이 있고 오픈 소스 활동하는 일원이에요. 개인적으로 바쁠 수도 있고, 우선순위가 낮아질 수도 있는거죠. 또, 대부분 우리랑 시차가 맞지 않기떄문에 비동기 커뮤니케이션은 기본적인 것 같아요. 그래서 내가 올린 PR, issue에 그들의 반응이 늦어져도 그럴 수 있다는 생각을 갖고 하는 것이 중요한 것 같아요. 이 해답으로 하나의 issue말고 여러 개의 issue를 동시에 진행하는 것 ㅋㅋㅋ 이 있을 것 같아요!</p> </blockquote> <h3 id="3-고민할-시간에-뭐라도-기여해보자" style="position:relative;"><a href="#3-%EA%B3%A0%EB%AF%BC%ED%95%A0-%EC%8B%9C%EA%B0%84%EC%97%90-%EB%AD%90%EB%9D%BC%EB%8F%84-%EA%B8%B0%EC%97%AC%ED%95%B4%EB%B3%B4%EC%9E%90" aria-label="3 고민할 시간에 뭐라도 기여해보자 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>3. 고민할 시간에 뭐라도 기여해보자.</h3> <blockquote> <p>이건 진짜 제가 계속 느낀 건데요. 아 이거 도전해볼까? 고민하는 찰나에 이미 다른 누군가가 기여해서 놓친 issue가 한두개가 아닌 것 같아요. 그냥 마음이 들었을 때, '내가 이 이슈에 대해서 기여해도 될까?' 혹은 PR을 만들어서 '내가 이 이슈에 대한 개선안을 코드로 구현해봤는데, 혹시 피드백 줄래?' 와 같이 바로 행동으로 옮기는 것이 속편해요. 거절당해도 좋은 경험이라 생각하면 되는 것이고, 피드백이 온다면 그것대로 또 얻는 것이 많으니까요.</p> </blockquote> <p>그리고, 마지막으로 내가 사용중인 기술에 대해 더 깊은 이해와 애정을 갖게 되는 계기였어요. 모두 오픈 소스 활동을 하기를 바라고, 저처럼 이 활동이 두려우신 분들은 인제님의 오픈소스 멘토링 활동에 참여해보는 것을 추천드립니다!!</p> <h3 id="내가-기여한-것들" style="position:relative;"><a href="#%EB%82%B4%EA%B0%80-%EA%B8%B0%EC%97%AC%ED%95%9C-%EA%B2%83%EB%93%A4" aria-label="내가 기여한 것들 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>내가 기여한 것들</h3> <p>마지막으로 제가 멘토링 진행하면서 기여한 것들을 공유할게요! 긴 글 읽어주셔서 감사합니다! :)</p> <h3 id="NestJS-nestjsmicroservices-should-handle-RabbitMQ-bindings-and-auto-generated-queues" style="position:relative;"><a href="#NestJS-nestjsmicroservices-should-handle-RabbitMQ-bindings-and-auto-generated-queues" aria-label="NestJS nestjsmicroservices should handle RabbitMQ bindings and auto generated queues permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>[NestJS] @nestjs/microservices should handle RabbitMQ bindings and auto-generated queues</h3> <ul> <li><a href="https://github.com/nestjs/nest/issues/13931">진행 이슈 link</a></li> <li><a href="https://github.com/nestjs/nest/pull/14129">PR link</a></li> <li><a href="https://eeeasycode.dev/oss_project_03/">해결 과정</a></li> </ul> <blockquote> <p>NestJS의 createParamDecorator의 callback 으로 전달되는 context의 type이 현재 any로 추론되는 것을 ExecutionContext Type으로 지정하여 사용자들이 NestJS의 Docs를 참고하지 않아도 createParamDecorator를 사용할 수 있게 하면 좋을 것 같다는 내용의 이슈</p> <p>→ NestJS Common 에 존재하는 custom-route-param-metadata.decorator.ts 파일을 수정하여 context의 type을 ExecutionContext로 명시하여 type 안정성 및 type 추론을 할 수 있도록 기여함</p> </blockquote> <h3 id="NestJS-type-narrowingcontextparameter-oncreateParamDecorators-callback" style="position:relative;"><a href="#NestJS-type-narrowingcontextparameter-oncreateParamDecorators-callback" aria-label="NestJS type narrowingcontextparameter oncreateParamDecorators callback permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>[NestJS] type narrowing context parameter on createParamDecorator's callback</h3> <ul> <li><a href="https://github.com/nestjs/nest/issues/14093">진행 이슈 link</a></li> <li><a href="https://github.com/nestjs/nest/pull/14126">PR link</a></li> <li><a href="https://eeeasycode.dev/oss_project_02/">해결 과정</a></li> </ul> <blockquote> <p>nestjs 에서 제공하는 microservices 에서 RabbitMQ 를 사용할 경우, RMQ의 binding 과 auto-generated queues 가 동작하지 않는 이슈</p> </blockquote> <blockquote> <p>→ 1차 : 실제 원인 부분을 디버깅하여, 해당 문제에 대한 수정점을 코드로 제안하고 그 결과를 메인테이너 및 이슈어에게 제안함</p> <p>→ 2차 : microservice의 RMQ 부분 로직에서 빠져있던, binding options을 추가하고 이를 queue에 binding 해주는 로직을 추가함. 또한, DEFAULT로 명시되던 queue name을 auto-generated 된 name으로 지정되도록 로직을 수정함. 이후, PR을 올려 메인테이너가 해당 이슈를 closed 한 뒤, 다음 마일스톤으로 할당함.</p> </blockquote> <h3 id="Spring-Cloud-Gateway-online-docs415-404-from-link-to-properties" style="position:relative;"><a href="#Spring-Cloud-Gateway-online-docs415-404-from-link-to-properties" aria-label="Spring Cloud Gateway online docs415 404 from link to properties permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>[Spring Cloud Gateway] [online-docs][4.1.5] 404 from link to properties</h3> <ul> <li><a href="https://github.com/spring-cloud/spring-cloud-gateway/issues/3500">진행 이슈 link</a></li> <li><a href="https://github.com/spring-cloud/spring-cloud-gateway/pull/3588">PR link</a></li> </ul> <blockquote> <p>Spring Cloud Docs의 잘못 명시된 link로 404 에러가 발생하는 이슈</p> <p>→ 문제가 발생하는 부분 수정 후 PR 제안</p> </blockquote>https://eeeasycode.dev/oss_project_02/https://eeeasycode.dev/oss_project_02/Sat, 16 Nov 2024 00:00:00 GMT<h3 id="NestJS-type-narrowingcontextparameter-oncreateParamDecorators-callback" style="position:relative;"><a href="#NestJS-type-narrowingcontextparameter-oncreateParamDecorators-callback" aria-label="NestJS type narrowingcontextparameter oncreateParamDecorators callback permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>[NestJS] type narrowing context parameter on createParamDecorator's callback</h3> <ul> <li><a href="https://github.com/nestjs/nest/issues/14093">진행 이슈 link</a></li> <li><a href="https://github.com/nestjs/nest/pull/14126">PR link</a></li> </ul> <h2 id="이슈-내용" style="position:relative;"><a href="#%EC%9D%B4%EC%8A%88-%EB%82%B4%EC%9A%A9" aria-label="이슈 내용 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>이슈 내용</h2> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/3f752e5ccab0772777f2c13692abe363/e2af0/image.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 111.76470588235294%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAABYlAAAWJQFJUiTwAAADIUlEQVR42n1Ux3bbQBDzNbZDSey9d0qirOL2HN/y/5+EYFYi3XPA2yIRixnMzNX4+hd+uUU97JFUG/hpQ7QzLD/Hyo6xchLobvoFcu/GNbyk4T7GlWYGMP0U7eZI0jv04wnd9ohmc0DDR/rxnucTht09krLH7crGb937AM2Y9i6ubpY2L1z4SYmoaBHmDaK8RUysrADXC0v9USD//YDV2/525bwRGm6Mdn1AT2WiZKCq7fEF/e4Rm/0zOqrvtgd13673TEGIheFTSADN5moRJhWS9EqYNTIHXoPQr5CmRFzilHZ4KUeMwwPaeocsaeF5KTw3QRQW8L0MrpMzdwWcJIcTpFgy7Jkw9EuU2R3W6xPqfMAQ5hiiBB3T0Fc9OubPY9JNKlMwfViETQ8s47wuub5TmCKkyowup1GNNKypIqaKCL7AjeDYEUkC9bEiktUK57ul8U6hRQN0Xqz0N+h80TADtf6E978vppCXJAvSGmF2dljgJxXrckDWbOh+d6kAus/Q7SBTDt9SiHz/Hspli3W4OTxhffdAVx+V07IXkgXzdLN0cK1ZqoR+wgdCKYOsHlAQOQ0omrU6V/2IisXuUa0TFeyIUj0uCh2aJqvHO5v7qRZVyKIirPaIOqpqTwoBz2FFsrhCkDENRc9wB7UXhJc1Lga111kBwjMTesWIpD6g270iaZ8QFHtYcYcl1S/ppLSoYGHyTLdnWOezzioRL2bCFavdDxLsmLuOXZNTTcrwRaHnZzC9RIWrs4Q+GyGQkFXrTYQi2eJHTb9jex3VsCg6dki9hnEhk5wJ8XeEcy+rOmT96exnTXfgs63iYouyHZUZhs12oyGWm8F2aExwNmYy4WdChrKwgjlX0ySR3zTj3PznMSWTx/kywibM02Y8PmP/8EfNQ9lvWZeytjIXOWHKblTzUkpsUvMZcx0KYc6OkAEqOSv4sXSJIGD3TDXnMnRx8pdm/ljcb/PwokSm9bQXtDyL2ql7kkvrySPTQ9NeUqIIxTkJR5QV7XYOr6LjchbVspd+l1yLKROmchJo07RZqIL1P4TyXVg3Agr4Xz//AzFcRSMzCLTiAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='메인테이너 분이 올린 issue 내용' title='' src='/static/3f752e5ccab0772777f2c13692abe363/ca1dc/image.png' srcset='/static/3f752e5ccab0772777f2c13692abe363/e7570/image.png 170w, /static/3f752e5ccab0772777f2c13692abe363/f46e7/image.png 340w, /static/3f752e5ccab0772777f2c13692abe363/ca1dc/image.png 680w, /static/3f752e5ccab0772777f2c13692abe363/02d09/image.png 1020w, /static/3f752e5ccab0772777f2c13692abe363/9d567/image.png 1360w, /static/3f752e5ccab0772777f2c13692abe363/e2af0/image.png 1650w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>메인테이너 분이 올린 issue 내용</figcaption> </figure></p> <p>NestJS의 createParamDecorator의 callback 으로 전달되는 context의 type이 현재 any로 추론되는 것을 ExecutionContext Type으로 지정하여 사용자들이 NestJS의 Docs를 참고하지 않아도 createParamDecorator를 사용할 수 있게 하면 좋을 것 같다는 내용의 이슈</p> <p>위의 사진에서 보이는 것처럼 현재 createParamDecorator의 parameter ctx type이 any로 지정되는 것을 볼 수 있다.</p> <p>createParamDecorator의 context 매개변수 타입이 any로 설정되어 있어, 타입 체크가 이 context의 구조나 내용에 대해 알려주지 못한다. 실제로는 ExecutionContextHost 인스턴스가 context로 전달되며, 이 인스턴스는 ExecutionContext 인터페이스를 구현한다. 타입을 ExecutionContext로 변경하면 코드 작성 시 올바른 타입 추론을 제공할 수 있다.</p> <h2 id="해결-과정" style="position:relative;"><a href="#%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95" aria-label="해결 과정 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>해결 과정</h2> <p>네, 작성하신 해결 방안을 순차적으로 설명드리겠습니다. createParamDecorator 함수의 context 매개변수 타입을 any에서 ExecutionContext로 좁히는 방안을 적용한 과정입니다.</p> <ol> <li>CustomParamFactory 인터페이스 수정</li> </ol> <p>먼저 CustomParamFactory 인터페이스에서 context 타입을 ExecutionContext로 변경하여 더 명확하게 정의했습니다.</p> <h3 id="기존-코드" style="position:relative;"><a href="#%EA%B8%B0%EC%A1%B4-%EC%BD%94%EB%93%9C" aria-label="기존 코드 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>기존 코드</h3> <p>기존 CustomParamFactory의 타입 정의에서는 context 매개변수 타입이 명확하지 않았습니다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">CustomParamFactory<span class="token operator">&lt;</span>TData <span class="token operator">=</span> <span class="token builtin">any</span><span class="token punctuation">,</span> TInput <span class="token operator">=</span> <span class="token builtin">any</span><span class="token punctuation">,</span> TOutput <span class="token operator">=</span> <span class="token builtin">any</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">(</span> data<span class="token operator">:</span> TData<span class="token punctuation">,</span> input<span class="token operator">:</span> TInput <span class="token punctuation">)</span> <span class="token operator">=></span> TOutput</code></pre></div> <h3 id="수정된-코드" style="position:relative;"><a href="#%EC%88%98%EC%A0%95%EB%90%9C-%EC%BD%94%EB%93%9C" aria-label="수정된 코드 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>수정된 코드</h3> <p>context의 타입을 ExecutionContext로 지정하여, CustomParamFactory의 두 번째 매개변수가 ExecutionContext임을 명확히 하였습니다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">CustomParamFactory<span class="token operator">&lt;</span>TData <span class="token operator">=</span> <span class="token builtin">any</span><span class="token punctuation">,</span> TOutput <span class="token operator">=</span> <span class="token builtin">any</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">(</span> data<span class="token operator">:</span> TData<span class="token punctuation">,</span> context<span class="token operator">:</span> ExecutionContext <span class="token punctuation">)</span> <span class="token operator">=></span> TOutput</code></pre></div> <p>이로 인해 CustomParamFactory를 사용하는 모든 곳에서 context가 ExecutionContext 타입을 가질 것임을 명확히 했습니다.</p> <ol start="2"> <li>createParamDecorator 함수 매개변수 타입 변경</li> </ol> <p>이제 createParamDecorator 함수에서 CustomParamFactory 인터페이스를 반영하여 context 매개변수를 ExecutionContext로 지정하였습니다.</p> <h3 id="기존-코드-1" style="position:relative;"><a href="#%EA%B8%B0%EC%A1%B4-%EC%BD%94%EB%93%9C-1" aria-label="기존 코드 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>기존 코드</h3> <p>기존 createParamDecorator의 factory 파라미터는 CustomParamFactory&#x3C;FactoryData, FactoryInput, FactoryOutput>으로 정의되어 context의 타입이 명확하지 않았습니다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">function</span> createParamDecorator<span class="token operator">&lt;</span> FactoryData <span class="token operator">=</span> <span class="token builtin">any</span><span class="token punctuation">,</span> FactoryInput <span class="token operator">=</span> <span class="token builtin">any</span><span class="token punctuation">,</span> FactoryOutput <span class="token operator">=</span> <span class="token builtin">any</span><span class="token punctuation">,</span> <span class="token punctuation">(</span> factory<span class="token operator">:</span> CustomParamFactory<span class="token operator">&lt;</span>FactoryData<span class="token punctuation">,</span> FactoryInput<span class="token punctuation">,</span> FactoryOutput<span class="token operator">></span><span class="token punctuation">,</span> enhancers<span class="token operator">:</span> ParamDecoratorEnhancer<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token punctuation">(</span> <span class="token operator">...</span>dataOrPipes<span class="token operator">:</span> <span class="token punctuation">(</span>Type<span class="token operator">&lt;</span>PipeTransform<span class="token operator">></span> <span class="token operator">|</span> PipeTransform <span class="token operator">|</span> FactoryData<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token operator">=></span> ParameterDecorator <span class="token punctuation">{</span> <span class="token comment">// function body</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="수정된-코드-1" style="position:relative;"><a href="#%EC%88%98%EC%A0%95%EB%90%9C-%EC%BD%94%EB%93%9C-1" aria-label="수정된 코드 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>수정된 코드</h3> <p>CustomParamFactory 인터페이스를 수정하여 ExecutionContext 타입을 반영함으로써 createParamDecorator의 factory 매개변수도 ExecutionContext 타입을 받게 되었습니다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">function</span> createParamDecorator<span class="token operator">&lt;</span> FactoryData <span class="token operator">=</span> <span class="token builtin">any</span><span class="token punctuation">,</span> FactoryOutput <span class="token operator">=</span> <span class="token builtin">any</span><span class="token punctuation">,</span> <span class="token punctuation">(</span> factory<span class="token operator">:</span> CustomParamFactory<span class="token operator">&lt;</span>FactoryData<span class="token punctuation">,</span> FactoryOutput<span class="token operator">></span><span class="token punctuation">,</span> enhancers<span class="token operator">:</span> ParamDecoratorEnhancer<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token punctuation">(</span> <span class="token operator">...</span>dataOrPipes<span class="token operator">:</span> <span class="token punctuation">(</span>Type<span class="token operator">&lt;</span>PipeTransform<span class="token operator">></span> <span class="token operator">|</span> PipeTransform <span class="token operator">|</span> FactoryData<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token operator">=></span> ParameterDecorator <span class="token punctuation">{</span> <span class="token comment">// function body</span> <span class="token punctuation">}</span></code></pre></div> <p>이를 통해 createParamDecorator가 ExecutionContext 타입의 context를 처리할 수 있도록 개선했습니다. 또한, context가 항상 ExecutionContext임을 보장할 수 있으며, 타입 안전성을 더욱 높였습니다.</p> <h2 id="결과" style="position:relative;"><a href="#%EA%B2%B0%EA%B3%BC" aria-label="결과 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>결과</h2> <p>NestJS Common 에 존재하는 custom-route-param-metadata.decorator.ts 파일을 수정하여 context의 type을 ExecutionContext로 명시하여 type 안정성 및 type 추론을 할 수 있도록 기여했습니다.</p> <p>또한, ExecutionContext 타입을 더욱 명확히 하여 코드의 타입 안정성을 강화하고, 개발자가 NestJS의 문서를 참조하지 않아도 직관적인 코딩을 할 수 있게 개선했습니다.</p> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/55c53d9fe73605ce5df4fbf2c7d1ae6a/c1e63/image-1.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 27.058823529411764%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA7klEQVR42nWPS0/DMBCEc6S0zcOxHSeOnWdD3Va0CAHiDBJwgP//b4a1KZGQyuHTrnZHo5no5fUN7x+f0O0Wu9MT7HiA7hx0v4OyE7KiAVPtv/i/11XNDe0WUd1NaAaHunfgVYuEayTiDO1xXhLVmfIiCa9mbbSIORYxgzADsmqDqzXDMpWB60QElukvP/dVVsz81UlEXsh4j834QNF7KDLOlUVKCf2UPrW0SIoWqTThvmZqNmRUk5e+vqXKBtGKCahyj+fHL2xv7zC4YzAVZKTMiLqdkFNybhyEHiB1R6Z1qOkr2nGP6XAfpukdvgGJOoG18E5aYAAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='메인테이너 분의 LGTM' title='' src='/static/55c53d9fe73605ce5df4fbf2c7d1ae6a/ca1dc/image-1.png' srcset='/static/55c53d9fe73605ce5df4fbf2c7d1ae6a/e7570/image-1.png 170w, /static/55c53d9fe73605ce5df4fbf2c7d1ae6a/f46e7/image-1.png 340w, /static/55c53d9fe73605ce5df4fbf2c7d1ae6a/ca1dc/image-1.png 680w, /static/55c53d9fe73605ce5df4fbf2c7d1ae6a/02d09/image-1.png 1020w, /static/55c53d9fe73605ce5df4fbf2c7d1ae6a/9d567/image-1.png 1360w, /static/55c53d9fe73605ce5df4fbf2c7d1ae6a/c1e63/image-1.png 1680w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>메인테이너 분의 LGTM</figcaption> </figure></p>https://eeeasycode.dev/oss_project_03/https://eeeasycode.dev/oss_project_03/Sat, 16 Nov 2024 00:00:00 GMT<h3 id="NestJS-nestjsmicroservices-should-handle-RabbitMQ-bindings-and-auto-generated-queues" style="position:relative;"><a href="#NestJS-nestjsmicroservices-should-handle-RabbitMQ-bindings-and-auto-generated-queues" aria-label="NestJS nestjsmicroservices should handle RabbitMQ bindings and auto generated queues permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>[NestJS] @nestjs/microservices should handle RabbitMQ bindings and auto-generated queues</h3> <ul> <li><a href="https://github.com/nestjs/nest/issues/13931">진행 이슈 link</a></li> <li><a href="https://github.com/nestjs/nest/pull/14129">PR link</a></li> </ul> <h2 id="이슈-내용" style="position:relative;"><a href="#%EC%9D%B4%EC%8A%88-%EB%82%B4%EC%9A%A9" aria-label="이슈 내용 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>이슈 내용</h2> <p>nestjs 에서 제공하는 microservices 에서 RabbitMQ 를 사용할 경우, RMQ의 binding 과 auto-generated queues 가 동작하지 않는 이슈</p> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/cd4ce777d653059301717e953592d660/134d7/image.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 112.94117647058823%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAABYlAAAWJQFJUiTwAAAC8ElEQVR42o1V2XLaQBDkJZXYsQ3oQveFTiRxCGNsV1xJ/JD//6PJ9IhVHMo4eeia3UXb23My+fX6g9ykorjoyIlKRkF2mJNmRzSzQkYwAmeGm3yAlCaa6VKUt9TsjlQ0O7HV+p7y1U72q82DoOXzKG/oZm7Trea8D92hyfXUIsMOKVzWFGUrivlSfLI6n08Nl2amJ7jVbLq6M5h0cRGTrzOL3ChnRVuquj2VbS9YbQ6Ulp2oXNYb+V3W1VqIcQ8EsADOAFZoirqufxR3u/2ToOV92fXiKqAewXf1ei/kOKs5PLB+UtIULoNddyIKs0ZiGcJltn5aUZDWlLKiuGgpYbXBckUeJ9CNC/LiIYHDviQTSXFimkD21PBptX2k++dX2hxexLb9M5Nt+QEQNbL2kloyqaA7g9XsmEkrqZKB0PRZxVqI8mbPalsBCHQnEeASLpve8iLmi3AgRI3hcv/4neP4JK+9JbD8TPARmellUqejQhDun35SvTmK2n8TXCLkpGCRrXoq2nuxiNkiyEdl/0eei1eicA6X2U2bSZA9nX9APHR+aEB8yuIJp7UpyUGMY9IW3KrsqZTN3PKHeuMay7m+2t0DNdsHqTtYtCLqLOBSGmwp1uPySbikLC+h66nxp1NAWLT9AC5m9HG9PvB+6OeMOwUEAJoAxLDYqxZVnSOdgldUN6D98BFabMZ9/OlqSle3+thi6qLC+V4Umm5MBXqZXYMi9LYdLMVCBVw6v3hxOEgd8iSBSgyA7DQIoBZry08kJG8VjuTvKB6nDZoc7oJExQ0xTfmhqhviiyTBmzseAhhrCpgyfxEiqMhmf3yRaaIyjjUmT9YceFbWEluVGGUxdA0eLiCT8QXChZ/KcMUHcB1rXAAJJpC3bMkJh5jC/c83Gn3hRGHYKguAa8zympXAZak7tioEcHN//Cbn+AbZBzFqUNUiADFInii0w0zcQTwQn/MYIWnqHGttEYhSQK3V38XYeh/V2Nvz82yfZ/43PGttKKmEPPsAAAAASUVORK5CYII='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='이슈 내용' title='' src='/static/cd4ce777d653059301717e953592d660/ca1dc/image.png' srcset='/static/cd4ce777d653059301717e953592d660/e7570/image.png 170w, /static/cd4ce777d653059301717e953592d660/f46e7/image.png 340w, /static/cd4ce777d653059301717e953592d660/ca1dc/image.png 680w, /static/cd4ce777d653059301717e953592d660/02d09/image.png 1020w, /static/cd4ce777d653059301717e953592d660/9d567/image.png 1360w, /static/cd4ce777d653059301717e953592d660/134d7/image.png 1628w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>이슈 내용</figcaption> </figure></p> <h2 id="해결-과정" style="position:relative;"><a href="#%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95" aria-label="해결 과정 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>해결 과정</h2> <p>우선 제안한 해결 방안에 대한 가이드는 두 개로 다음과 같았습니다.</p> <ol> <li>The queues should be named in the expected format</li> </ol> <p>=> queue들은 예상된 형태로 이름이 부여되어야 함</p> <h3 id="디버깅-포인트-1" style="position:relative;"><a href="#%EB%94%94%EB%B2%84%EA%B9%85-%ED%8F%AC%EC%9D%B8%ED%8A%B8-1" aria-label="디버깅 포인트 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>디버깅 포인트 1</h3> <blockquote> <p>queue의 이름이 ''로 지정될 때, RabbitMQ 의 옵션처럼 'amq.gen-asdnfks...' 형태가 아닌 'default'로 부여되는지를 파악</p> </blockquote> <p>우선 packages/microservices/constants.ts 에서 RQM_DEFAULT_QUEUE이 default로 지정되어 있었습니다. 그리고 queue 옵션이 ''로 넘어오면, queue에 RQM_DEFAULT_QUEUE이 할당되어 실제 queue name은 default로 할당되는 문제가 발생했습니다.</p> <p>단순히, packages/microservices/constants.ts 의 <code>export const RQM_DEFAULT_QUEUE = 'default';</code> 를 <code>export const RQM_DEFAULT_QUEUE = '';</code> 로 수정하니, 문제가 해결되었습니다.</p> <ol start="2"> <li>The binding should automatically happen</li> </ol> <p>=> binding은 자동적으로 진행되어야 함</p> <h3 id="디버깅-포인트-2" style="position:relative;"><a href="#%EB%94%94%EB%B2%84%EA%B9%85-%ED%8F%AC%EC%9D%B8%ED%8A%B8-2" aria-label="디버깅 포인트 2 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>디버깅 포인트 2</h3> <blockquote> <p>binding 에 관련된 option들을 파악</p> </blockquote> <p>우선 binding 옵션은 exchange와 routingKey 인데, 실제 NestJS microservices의 rabbitMQ option에는 해당 값들이 존재하지 않았습니다. 그래서, RMQOption interface에 해당 값들을 optional로 추가해주었습니다.</p> <p>AS-IS</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"> isGlobalPrefetchCount<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> queueOptions<span class="token operator">?</span><span class="token operator">:</span> AmqplibQueueOptions<span class="token punctuation">;</span> socketOptions<span class="token operator">?</span><span class="token operator">:</span> AmqpConnectionManagerSocketOptions<span class="token punctuation">;</span> noAck<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> consumerTag<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> serializer<span class="token operator">?</span><span class="token operator">:</span> Serializer<span class="token punctuation">;</span></code></pre></div> <p>TO-BE</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"> isGlobalPrefetchCount<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> queueOptions<span class="token operator">?</span><span class="token operator">:</span> AmqplibQueueOptions<span class="token punctuation">;</span> socketOptions<span class="token operator">?</span><span class="token operator">:</span> AmqpConnectionManagerSocketOptions<span class="token punctuation">;</span> <span class="token comment">// 해당 binding option을 optional로 추가</span> exchange<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> routingKey<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> noAck<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> consumerTag<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> serializer<span class="token operator">?</span><span class="token operator">:</span> Serializer<span class="token punctuation">;</span></code></pre></div> <p>그 다음, 사용자가 지정한 binding option이 실제로 server에 적용되는 조건문이 존재하지 않았습니다. 그래서, client-rmq.ts 와 server-rmq.ts 에 해당 설정을 binding 해주는 로직을 추가했습니다.</p> <p>client-rmq.ts</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>noAssert<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> channel<span class="token punctuation">.</span><span class="token function">assertQueue</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>queue<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queueOptions<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 기존에 존재하지 않았던 로직</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>exchange <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>routingKey<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> channel<span class="token punctuation">.</span><span class="token function">bindQueue</span><span class="token punctuation">(</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queue<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>exchange<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>routingKey <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">await</span> channel<span class="token punctuation">.</span><span class="token function">prefetch</span><span class="token punctuation">(</span>prefetchCount<span class="token punctuation">,</span> isGlobalPrefetchCount<span class="token punctuation">)</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">consumeChannel</span><span class="token punctuation">(</span>channel<span class="token punctuation">)</span> <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre></div> <p>server-rmq.ts</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>noAssert<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> channel<span class="token punctuation">.</span><span class="token function">assertQueue</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>queue<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queueOptions<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 기존에 존재하지 않았던 로직</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>exchange <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>routingKey<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> channel<span class="token punctuation">.</span><span class="token function">assertExchange</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>exchange<span class="token punctuation">,</span> <span class="token string">'topic'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> durable<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> channel<span class="token punctuation">.</span><span class="token function">bindQueue</span><span class="token punctuation">(</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queue<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>exchange<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>routingKey<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">await</span> channel<span class="token punctuation">.</span><span class="token function">prefetch</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>prefetchCount<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>isGlobalPrefetchCount<span class="token punctuation">)</span><span class="token punctuation">;</span> channel<span class="token punctuation">.</span><span class="token function">consume</span><span class="token punctuation">(</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queue<span class="token punctuation">,</span></code></pre></div> <h2 id="결과" style="position:relative;"><a href="#%EA%B2%B0%EA%B3%BC" aria-label="결과 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>결과</h2> <p>그 결과로 실제 local에서 RabbitMQ와 nestjs microservice 설정을 진행해보았는데, 제기되었던 이슈의 문제를 말끔하게 해결할 수 있게 되었습니다.</p> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/fc2fcd382816d4102877cccd1926a734/64756/image-1.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 128.23529411764707%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAABYlAAAWJQFJUiTwAAAEJUlEQVR42p2Sa0+bZRjHa5zRDd2xLQX6tH0O7fOUUnqkUE66DcRsn2IvTPwcJsbTnFliTExMfGEGyRKFjbApFJgm6hsQKIkEaIFgWQsUKBAGBcrf676hMLNSD3fyz3Uff/d10hnKBEiqE6JdhU1RUWGxQbBJsIgyygUbzFYZglWC+WifyVQhwFQu4KK+DCUXS/H6pRPpKgQrvD4PGlra8FbLVdy4eRMtrS0INzUh/GY9rrc1ItxYC4fdAVVVUVlZCVmWIUkS9KUVBDT+HVhutsBmtcBV2wRvjQ9tN95BfX0QTn8QoeYAmq77EKhzw1WtQiSIQA7YbDYCirhSWl4YqNCPLmc1vK4APFV+VHrDCNQEyRs7FLsGB6XEoWpQHQ44VRkygRn8VCC7oNLD5qarqK1vRKguhJZrzagJ1UJR3dBcftg1DzzuKoS8KuwEFsViQFmCRKpraIDPV03eiqhyU76qNGiqnUK08rRYrVYIVJyiIbOiqKoDZrMZoXCYoPVwulQKVYJdFSErLG8CP39eAumyoVCVWQ4Vhf/q9wdwrbUVAR8VhVrIYrGSVzYKT3xBxYuiyBAJGAjWIBQIwu3UeAr4Y0n8b8ATD61UhBry8G14fG5UugkqyQVh/whkjSorDmjaYQXZB4r9sHn/F1Ch8FSXG9WBEKo8fjg1b1FYcSBV2eFQqLIeuD1UDE3hPcaAxcRaTW+q4JA3Lpu4OPDSFSNKS00wmcpgNBqh1xtgMOjJniJD/o4Rr5ZcgO5MCXSvlODl184fAt//4CP0RSK4196BnkeP0dsXoXU/+gcGMTBIGjjS4BO+19vbx88jfWR/7MXjRz9g4MlPuH3nLs5dMEDX+bAH2Z0dTExMIJl8irm5OWQyGezt7WH72TNks1ns0Pn29jafLywscJvMbCKxvonljQzW19cwSNCz5wl4/7tOHORyBEticXER4+PjGB4eRjQaxejoGK2jGBkZwdDQEN+bmZkhaAKjiUX8PJvC7/NzWFldQnvHfV4g3fdd3WCDAVdWVjh0a2sL/3bs7+9zG4kM4KUz56D76utvEI/P4Jdff8PoWBR/UOgxWjNNx+KIk0fxo3V8ZvbQ5s9JU7EY/kwkKPf9CNY1Q/ftvQ7u3RjBYvE4lpeXsbi0hCWy6XQaqVQKKfKaKfk0iSU6S6dX6F76WMlkCgcHB+h60A3dg+6eY9fZJhurq6tI0K8MPjU1hRh5MT8/z/dYwbLZHezu7r4QNmMdA3epqnngxsYGZmdnuXcMwnLLPmGQyclJtLe3Y3p6mt9lbwoC85v5OWsNZnPUAflHzLL9zc1Nbgt62Nn1kC/ygBPluGX9uLaW4flkURx/kMsd383DGaugh88PBsw3N5sXax3u4a1338PdL77Eh598ho9vf366Pr1zqAJn7C1jMNZflXbTQPGu2iwAAAAASUVORK5CYII='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='실제 테스트 결과' title='' src='/static/fc2fcd382816d4102877cccd1926a734/ca1dc/image-1.png' srcset='/static/fc2fcd382816d4102877cccd1926a734/e7570/image-1.png 170w, /static/fc2fcd382816d4102877cccd1926a734/f46e7/image-1.png 340w, /static/fc2fcd382816d4102877cccd1926a734/ca1dc/image-1.png 680w, /static/fc2fcd382816d4102877cccd1926a734/02d09/image-1.png 1020w, /static/fc2fcd382816d4102877cccd1926a734/64756/image-1.png 1200w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>실제 테스트 결과</figcaption> </figure></p> <p>queue를 ''으로 설정했을 때, Name이 'default'가 아닌 'amq.gen-...'으로 지정되었고, Binding의 option 또한 내가 지정한대로 잘 적용된 것을 확인할 수 있습니다.</p> <p>아직, merge되진 않았지만 다른 분께서 좋은 제안인 것 같다는 코멘트와 메인테이너 분께서 해당 이슈를 completed로 변경하신 뒤 milestone을 적용해주신 것으로 보아, 다음 milestone까지 해당 PR에서 추가 피드백이나 논의가 진행될 것으로 예상됩니다.</p> <ul> <li>2024.11.20 기준으로 드디어 nestjs 11 milestone에 PR이 merge 되었습니다~~~!</li> </ul> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/18004b8262f85ce36cb0194ffc63277f/650fc/image-2.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 44.705882352941174%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABpUlEQVR42mVSyW7bMBTUMXFia6EkUqJkLZZFS7Fhp64TpCjQU+/5oX77dB4dIyl6GJCPy3BmHoPHWCPKKrSbCbYdYeqNrwVhWnK0Hg9Rjrulwv0q/QcLQvbuVwqLMEMghMvE4HT5ge9vvzAfL3D7bxifnrE7nDGxno4vcJx3bo923KN3BwzT0Y9lMyA1Hd5//sF5fv8kLKjMtg5Z2SItGiSmRVJ0yEyDomyguW5Y6+K6L+cU55Fu6KDmmQkqa6+EYWqxP12wnU8oaVuZNUI9YKkdrF5jtBXWfGBjR2wq5x+JdY3IDIiKmQ/3eEhoP0xvhCWqboS2nSfzuXFN1uO8QkJSIVixlvOSmeAx/grtEUgR5TVmZiSZ1GzO1TYtUYmueh9FsR5osfHx3C7f4vqKa4bKoN89oRlm3+n0IydRllBhzDHMqVy3yGzvH7wRik3prkBUB4tIoa1fcTn/Zief2ckDcloXQiEXxOUWyjqkZfefStvtUPUTuz3yXo9gqTQ7NGPrzrS78/8wZgQJMxNSQUxlCb+GV8z18CNfQdFs2UgHw0hE+V9zz+ugsnHF+AAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='메인테이너가 직접 merge를 해주셨습니다~!' title='' src='/static/18004b8262f85ce36cb0194ffc63277f/ca1dc/image-2.png' srcset='/static/18004b8262f85ce36cb0194ffc63277f/e7570/image-2.png 170w, /static/18004b8262f85ce36cb0194ffc63277f/f46e7/image-2.png 340w, /static/18004b8262f85ce36cb0194ffc63277f/ca1dc/image-2.png 680w, /static/18004b8262f85ce36cb0194ffc63277f/02d09/image-2.png 1020w, /static/18004b8262f85ce36cb0194ffc63277f/9d567/image-2.png 1360w, /static/18004b8262f85ce36cb0194ffc63277f/650fc/image-2.png 1690w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>메인테이너가 직접 merge를 해주셨습니다~!</figcaption> </figure></p>https://eeeasycode.dev/istio-srping-cloud-gateway/https://eeeasycode.dev/istio-srping-cloud-gateway/Mon, 11 Nov 2024 00:00:00 GMT<p>최근에 진행하고 있는 프로젝트에서 저희는 마이크로 서비스 아키텍처를 도입했습니다. 기술 스택 선정에 있어서 우선 가장 자주 사용되는 기술들과 Cloud Native 환경에 적합한 기술들을 선정하기 위해 노력했습니다.</p> <h3 id="프로젝트에서-사용중인-기술-Spec" style="position:relative;"><a href="#%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%EC%A4%91%EC%9D%B8-%EA%B8%B0%EC%88%A0-Spec" aria-label="프로젝트에서 사용중인 기술 Spec permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>프로젝트에서 사용중인 기술 Spec</h3> <blockquote> <ul> <li>K8s</li> <li>istio (Service mesh, Ingress Gateway)</li> <li>argoCD</li> <li>Prometheus</li> <li>jaeger</li> <li>EFK</li> </ul> </blockquote> <p>또한, 내부 service들은 Knative로 구성된 NodeJS 기반 알림 서버를 제외하고 Spring 기반의 WAS로 운영되고 있습니다. 내부 service들은 service mesh로 구성되어 있고, 외부의 트래픽은 gateway를 통해 내부 service들로 접근하게 됩니다. 간략히 그림으로 나타내면 다음과 같겠네요.</p> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/2c8cb7f30381983ec78bda68e8fe9dfa/908d1/image.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 72.94117647058825%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB8ElEQVR42oWUbZObIBSF9///sp3ptF+aT9tp0mZNUEQQVHxBPL0XV7czTSwTRhDycM69F1/woC3Lgm6YMExhn4cQEOOcxlvf1rYxt5dHwCnMKNoZup3SPMYIKSXETWAcR8zznA5wTYOBnuth8QgYCBhRupE1pF/tDC7yDUpVqCrqWsMbg1jfMA5dOuQ5kKzmbibgkIBsyJka6ssJtbVwzhFUo7EGbZnBt5aAHwrZQtd1KQ6b7B3YjPQ+pvfOWYj7TxhSZQla01PWCu/S0Nh+Wm4oDryJgQznhZGAwgVUFMOJ3g18qGuQvX6FVCU02VWlQmFr+N7Dd39Z5oBuk60x8G5HKNfvltumhTxnZFVRzHr0fQ/DSq1LDo+TQsDTWeBa6L00LMXtkv2CkArGtfDeU4JUyv6W+afAcZog7ERJWRXGuKAlFaUSeBM1Tr9LWF2lWHK4JtrP/RCYNxzDtWxYIVvUtSY1PRYqcFbFKlkZr/1H4Zpl1Yxpzps5EYZ6LjXVpCeIT1DurO4whglIN0WlOgTCHNIfaYRvPzxevwvcrpd0wHYtDy1vN2VTGOgqXq8CeS6Q0fUzVHepFhuPwXepJhn6FBjIoiSFppt24OVM2TUVisLiftfoKH58NTuVk9rrh4MnwBXyb31+fo3w8EvDwD9Wd5G8ydWMMwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='서비스 아키텍처' title='' src='/static/2c8cb7f30381983ec78bda68e8fe9dfa/ca1dc/image.png' srcset='/static/2c8cb7f30381983ec78bda68e8fe9dfa/e7570/image.png 170w, /static/2c8cb7f30381983ec78bda68e8fe9dfa/f46e7/image.png 340w, /static/2c8cb7f30381983ec78bda68e8fe9dfa/ca1dc/image.png 680w, /static/2c8cb7f30381983ec78bda68e8fe9dfa/02d09/image.png 1020w, /static/2c8cb7f30381983ec78bda68e8fe9dfa/908d1/image.png 1286w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>서비스 아키텍처</figcaption> </figure></p> <h2 id="Gateway" style="position:relative;"><a href="#Gateway" aria-label="Gateway permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Gateway</h2> <p>일단, Gateway에 대해서 간략히 설명해보겠습니다. Gateway는 클라이언트와 서버 사이의 트래픽을 관리하고 요청을 적절한 서비스로 라우팅하는 역할을 합니다. 또한, 단일 진입점을 제공해 각 내부 서비스에서 공통으로 필요한 로직을 통합하여 처리할 수 있습니다. 인증/인가, 보안 정책, 유저 데이터 등을 처리한 뒤, 내부의 서비스로 라우팅해줍니다.</p> <p>저희 프로젝트에서는 Spring Cloud Gateway를 API 게이트웨이로, Istio Ingress Gateway를 인그레스 게이트웨이로 활용하는 구조를 택했습니다.</p> <p>실제 Kiali를 통해 볼 수 있는 저희 서비스의 gateway traffic 흐름은 다음과 같습니다. <figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/37cc39fee08e5ac6a8246fc457082588/312cc/image-1.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 39.411764705882355%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABcUlEQVR42q3RSy8DURTA8StKH9POdEqDBq3SiNiQiKWF59LWQpQSFiQSiU+hNa3HNCg+gNjbUXwF4tGiIXTBxhf4uzMaiY2VxS/n5CTn5N5zhBqo5z+Jfx+o1QWxuFU/Xqsoc5+Mij+AR9NtLsldyX8L4JJ9HlVH0b4Jp+LD4fLQGGrG61VRKkZHxzC3TYx1g6yRxpQxk0zZNlPrbMiYlg7NLNnsNmtpg2TGQPT09TM4NML+1iYT4+MMDwyge32sLC7xWS7zfHvHR6nE69MTrzK+v5UpFYuUX14o3Rd4Kz7yULjj6uaawmMRsXeY4/jkiPxZnvOLS07z5+gNTXT39JKYX2ByOvFjamaW+Owc8cQck/EEy6srGLsZTHOHXO6AZEq+MBSO0BRuJdweIyKFox32Tl3y28JRS5WlxvnN4bRrQuaiugaPX6Ml1kY01klHZxeh1ghCsRYquX1ahR9Vr7eH6sGGP2mBoH0Qq896gHWUL31vEJ/nk9YtAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='kiali graph' title='' src='/static/37cc39fee08e5ac6a8246fc457082588/ca1dc/image-1.png' srcset='/static/37cc39fee08e5ac6a8246fc457082588/e7570/image-1.png 170w, /static/37cc39fee08e5ac6a8246fc457082588/f46e7/image-1.png 340w, /static/37cc39fee08e5ac6a8246fc457082588/ca1dc/image-1.png 680w, /static/37cc39fee08e5ac6a8246fc457082588/02d09/image-1.png 1020w, /static/37cc39fee08e5ac6a8246fc457082588/312cc/image-1.png 1182w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>kiali graph</figcaption> </figure></p> <h2 id="Istio-Ingress-Gateway-선택-이유" style="position:relative;"><a href="#Istio-Ingress-Gateway-%EC%84%A0%ED%83%9D-%EC%9D%B4%EC%9C%A0" aria-label="Istio Ingress Gateway 선택 이유 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Istio Ingress Gateway 선택 이유</h2> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 379px; '> <a class='gatsby-resp-image-link' href='/static/b9dc334f0d10eb0dcd94c1061f80d2eb/0839d/image-3.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 35.29411764705882%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAuElEQVR42mP4jwH+gQGEAeb///cfO2BA1QYiX7//8uXrDwj3H4aJODX//QuS3nrk9sGzD4GMj1++QzT+/vPn779/+GyGyH7/8atm6oHS/n0/fv06ffXx/advj5y/f/3+i5v3Xz16/h6uDF0zxNpTVx93zT9a0rP70u3nZ64+PXDq3tbD1y/eerHzyM1jFx/iczYQfPn+A6jo5dtPv37/fv/x24dP3z5+/v75y/dPX358BgcEPs0kAQA9/JOL9/ucGQAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='istio' title='' src='/static/b9dc334f0d10eb0dcd94c1061f80d2eb/0839d/image-3.png' srcset='/static/b9dc334f0d10eb0dcd94c1061f80d2eb/e7570/image-3.png 170w, /static/b9dc334f0d10eb0dcd94c1061f80d2eb/f46e7/image-3.png 340w, /static/b9dc334f0d10eb0dcd94c1061f80d2eb/0839d/image-3.png 379w' sizes='(max-width: 379px) 100vw, 379px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>istio</figcaption> </figure></p> <p>사실, 저희 서비스에서 실제로 L4 트래픽을 관리할 일은 많지 않지만 기본적인 정책들을 적용해보고 싶었기 때문에 가장 앞단에 Ingress Gateway를 배치하게 되었습니다. 일단, Istio는 마이크로서비스 아키텍처에서 강력한 네트워크 관리와 트래픽 제어 기능을 제공하는 서비스 메시 솔루션인데요. 특히 Istio Ingress Gateway는 클러스터 외부로부터 들어오는 요청을 처리하고, 이를 적절한 내부 서비스로 라우팅하는 기능을 담당합니다.</p> <p>저희 서비스에서의 Istio Ingress Gateway는 클러스터 외부에서 들어오는 트래픽을 수신하고, 이를 내부로 전달하기 전에 초기 네트워크 정책을 적용하고 보안 설정을 적용합니다. 이를 통해 외부 트래픽에 대한 인증/인가, TLS 암호화 등을 처리하고 있습니다.</p> <h2 id="Spring-Cloud-Gateway-선택-이유" style="position:relative;"><a href="#Spring-Cloud-Gateway-%EC%84%A0%ED%83%9D-%EC%9D%B4%EC%9C%A0" aria-label="Spring Cloud Gateway 선택 이유 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Spring Cloud Gateway 선택 이유</h2> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 400px; '> <a class='gatsby-resp-image-link' href='/static/6910f73fdfea7e31fdef0bb80ecd8560/d9f49/image-4.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsTAAALEwEAmpwYAAABpElEQVR42mP4TwFgGIya//399/fvvz///v8jTTOaBiAXqxEMuHQ++3Tv8IMN++6tOvt077dfnzFNRNf87x9Ues+dZYvPt554tO3CswO77yyZe7bu3rvLYAV/sWuGGPz776/NN2avvzYN2bjXX5/MPF35/PMDNP0MyIquvjw+90zt7jtL//z9DRc8/2z/k4+377+/CpT68/cX1CK4Zpgn7wKlP/98B2T/+fcH6OBdtxcvPt+Wtt5k1+0lQMFD99ctPNf04883uH6QZmB8AEmgijNPdkOM3HdvZdJa/djVmgVbncp3eG+4Nh0qfnfl/rur4FoQmrfenHvl5XGIotWXJ2RsMO8+lDL1RHHOZpsLzw9AxIGOX3NlEljLX5izwaxLL46suTIRoujeuysPP9y49urkpuszLzw/CFd98P7avXdXoNgMB8BwBuq//OLo+ecHr7w8BmTcenPuztsLl18cufryxN67Kxeea0aOc/R4Brp85+3Fu28v3XVnCTAUdt5aBEQgxu3Fxx5u/vrrE3poo6QuSjLGP1B++IMVoSWvwZqfCWsGAMBIancRYNm1AAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='spring cloud' title='' src='/static/6910f73fdfea7e31fdef0bb80ecd8560/d9f49/image-4.png' srcset='/static/6910f73fdfea7e31fdef0bb80ecd8560/e7570/image-4.png 170w, /static/6910f73fdfea7e31fdef0bb80ecd8560/f46e7/image-4.png 340w, /static/6910f73fdfea7e31fdef0bb80ecd8560/d9f49/image-4.png 400w' sizes='(max-width: 400px) 100vw, 400px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>spring cloud</figcaption> </figure></p> <p>api-gateway와 연결된 서비스들이 Spring 기반의 WAS이기에 Spring 친화적으로 L7 트래픽에 대한 필터링과 라우팅을 제공하기 위해 선택했습니다. 다들 Spring Cloud Gateway에 대해서 잘 아실테니 설명은 따로 하지 않을게요.</p> <p>저희는 실제로, spring cloud의 api-gateway를 auth-service와 연결하여, 인증/인가 작업을 수행하며 적절한 라우팅 규칙을 통해 트래픽을 업스트림 서버로 전달합니다. 또한, 다양한 공통 작업들이 단일 지점에서 수행되는데요.</p> <ol> <li>위에서 언급한 서비스에 대한 인증/인가</li> <li>request에 대한 전/후처리</li> <li>다양한 공통 정보를 위한 필터링 로직 수행</li> <li>안정적인 L7 통신을 위한 설정</li> <li>API 호출/응답 데이터 logging</li> </ol> <p>이 수행되고 있습니다.</p> <h2 id="두-Gateway의-조합을-통한-이점" style="position:relative;"><a href="#%EB%91%90-Gateway%EC%9D%98-%EC%A1%B0%ED%95%A9%EC%9D%84-%ED%86%B5%ED%95%9C-%EC%9D%B4%EC%A0%90" aria-label="두 Gateway의 조합을 통한 이점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>두 Gateway의 조합을 통한 이점</h2> <ol> <li>보안강화</li> </ol> <p>Istio Ingress Gateway에서 외부 트래픽에 대한 초기 보안을 적용하고, Spring Cloud Gateway에서 추가적인 보안 로직을 처리함으로써 다중 보안 계층을 구축할 수 있습니다.</p> <ol start="2"> <li>유연한 트래픽 관리</li> </ol> <p>Istio는 네트워크 레벨에서의 트래픽 제어를 담당하고, Spring Cloud Gateway는 애플리케이션 레벨에서의 세밀한 요청 처리를 담당하여 트래픽을 효율적으로 관리할 수 있습니다.</p> <ol start="3"> <li>모니터링 및 관찰성 향상</li> </ol> <p>Istio의 분산 추적 기능과 Spring Cloud Gateway의 로깅 및 메트릭 수집 기능을 결합하여 클라이언트부터 내부 서비스까지의 트래픽 흐름을 종합적으로 모니터링할 수 있습니다.</p> <p>저희는 두 gateway의 조합을 통해 다양한 이점을 얻을 수 있게 되었고, 서비스의 안정성 또한 확보할 수 있었습니다.</p> <h2 id="마지막으로" style="position:relative;"><a href="#%EB%A7%88%EC%A7%80%EB%A7%89%EC%9C%BC%EB%A1%9C" aria-label="마지막으로 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>마지막으로</h2> <p>이점이 많아보이지만, 이것도 결국 trade-off가 존재하겠죠. 초기 설정에 많은 시간을 할애했습니다. 특히, 두 개의 gateway에서 cors 관련된 이슈가 정말 추적하기 어려웠고 k8s 환경 자체도 실무에서 완전 cloud native한 환경을 구축해본 경험이 없었기에 삽질을 많이 했던 것 같습니다. 또한, 트래픽이 엄청나게 많지 않은 이상 gateway도 하나로 충분할 수 있었고, MSA 까지 가지 않아도 충분했을 겁니다. 그럼에도, 실험적인 도전은 이런 프로젝트가 아닌 이상 하기 힘들거라 판단했고, 직접 삽질하며 인프라 설계, 자동화, 서비스 로직 구현까지 뚝딱 해내고 있는 스스로를 보면 나쁘지만은 않은 것 같습니다. 이런 오버 엔지니어링도 스스로가 인지하고 있고, 마감일을 놓칠정도로 문제가 생기지 않으면 한번쯤 도전해볼만한 것 같아요.</p> <p>아래처럼 서비스가 안정적으로 동작하는 것만 봐도 뿌듯하네요.</p> <p><figure class='gatsby-resp-image-figure' style='margin-bottom: 16px;'> <span class='gatsby-resp-image-wrapper' style='position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; '> <a class='gatsby-resp-image-link' href='/static/d3966fd409263fdfbf5ec4c38248d47d/8c930/image-2.png' style='display: block' target='_blank' rel='noopener'> <span class='gatsby-resp-image-background-image' style="padding-bottom: 49.411764705882355%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAACC0lEQVR42m1Sy2oUQRTtjStfIJKdG3eCuHMl+Ceu3An+gy5FIaIBUQJKCK5EF2GcSDJjNCRjgoIZ4wNENA4T59Ez3T1d1V1V/TieqsmMIl4o7q1Tt06dOlVet+9jGI4gU4VYJhB2JAlrCaU1iqJAlufIstzVZVkyl8jL8eiNBNqRwM9gBF8k8DabX1B/+wHP1xpYfFbFUm0dT6p1LDytYLv5CUEssd8b4Jc/xJCbQ26yuStThMpAJQpt1h2pKErDG3ESE0yUdkDCJpsFFec6Q2kKGJND6jEu2WcsrqiaebIv5XokUngmyyB5TaUUJlEe5GbQwVrnOwrWoQyoso8iK+DrBEt7n7GfRNCct/sDhNHYNk8bEgoJSd+sP9YvG/OtHZyo3cGR1du4vLuMvDAAvftG4vONBRxencXZzUfY6v3AgJaEQjiVjlDwAazhBQmtPJkbnNt4iKMrs5ipzeEYSV8PW+6g61834FVv4NTLe/CWb+LqbgWgSk0hfwip0L7i+L5USfKLW49x6MUtHCfZSSrdGfXc8t29d45wpj7n8jUeYIPv7/x0HlpCxYmNyZUbQRsX3izizPo8HrTeT/0VVH/l4wpOv7qPS80KBiaZrtkHdYRpmkJrfSCwdMNGzlNNWeB/YfHyH8wp9MOY/0q6v/X38KMYA37YgHU/iqe4xexawD2THovbvm4Q4TcEFdw39BSOHgAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span> <img class='gatsby-resp-image-image' alt='나의 작고 소중한 서비스들' title='' src='/static/d3966fd409263fdfbf5ec4c38248d47d/ca1dc/image-2.png' srcset='/static/d3966fd409263fdfbf5ec4c38248d47d/e7570/image-2.png 170w, /static/d3966fd409263fdfbf5ec4c38248d47d/f46e7/image-2.png 340w, /static/d3966fd409263fdfbf5ec4c38248d47d/ca1dc/image-2.png 680w, /static/d3966fd409263fdfbf5ec4c38248d47d/02d09/image-2.png 1020w, /static/d3966fd409263fdfbf5ec4c38248d47d/9d567/image-2.png 1360w, /static/d3966fd409263fdfbf5ec4c38248d47d/8c930/image-2.png 1962w' sizes='(max-width: 680px) 100vw, 680px' style='width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;' loading='lazy' decoding='async'> </a> </span> <figcaption class='gatsby-resp-image-figcaption'>나의 작고 소중한 서비스들</figcaption> </figure></p>https://eeeasycode.dev/do_you_know_singleton/https://eeeasycode.dev/do_you_know_singleton/Sat, 09 Nov 2024 00:00:00 GMT<h3 id="Prerequisite-" style="position:relative;"><a href="#Prerequisite-" aria-label="Prerequisite permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Prerequisite :</h3> <p>본 글에서 언급되지만, 직접적인 설명은 하지 않는 것들입니다.</p> <ul> <li>객체</li> <li>싱글톤</li> <li>정적 팩토리 메서드 패턴</li> <li>DI/IoC</li> </ul> <hr> <p>최근에 프론트엔드 개발자 지인과 함께 커피챗을 했는데, "객체"와 "싱글톤" 에 관련된 주제로 이야기를 잠깐 나눴다. 정확히는 내가 정적 팩토리 메서드에 관한 이야기를 하다가 잘못 이야기를 한 부분이 있었는데, 정적 팩토리 메서드 패턴으로 객체를 생성하면 객체들을 싱글톤으로 관리할 수 있다고 이야기를 했다. 그리고, 그 자리에서 지인이 JS로 내가 말한 내용이 맞는지 바로 테스트했다. 그때 작성했던 코드는 다음과 같다.</p> <div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Latte</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">"Latte"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">LatteFactory</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Latte</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> factoryList <span class="token operator">=</span> <span class="token punctuation">{</span> LatteFactory <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">CoffeeFactory</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token parameter">type</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> factory <span class="token operator">=</span> factoryList<span class="token punctuation">[</span>type<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> factory<span class="token punctuation">.</span><span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> coffee <span class="token operator">=</span> CoffeeFactory<span class="token punctuation">.</span><span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token string">"LatteFactory"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> coffee2 <span class="token operator">=</span> CoffeeFactory<span class="token punctuation">.</span><span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token string">"LatteFactory"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>coffee<span class="token operator">==</span>coffee2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// False</span></code></pre></div> <p>내가 말한대로라면, 정적 팩토리 메서드 패턴으로 생성된 두 객체는 싱글톤 객체니까, 비교했을 때 True가 나와야 했다. 근데, False가 나오게 되었다. 나는 '엥? 뭐지' 라고 생각을 했고, 그냥 서로 어찌저찌 이야기를 하다가 넘어가게 되었다.</p> <h2 id="이걸-그냥-넘어간다고" style="position:relative;"><a href="#%EC%9D%B4%EA%B1%B8-%EA%B7%B8%EB%83%A5-%EB%84%98%EC%96%B4%EA%B0%84%EB%8B%A4%EA%B3%A0" aria-label="이걸 그냥 넘어간다고 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>이걸 그냥 넘어간다고?</h2> <p>커피챗이 끝나고 집으로 돌아가서 잠을 자려고 하는데, 아무리 생각해도 내가 뭔가를 잘못 설명한 기분이 들었다. 너무 찜찜한 기분이라 해결하고 자야겠다는 생각을 했다.</p> <p>다시 돌아가보자. 내가 분명 "정적 팩토리 메서드 패턴을 사용하면, 객체는 싱글톤으로 관리돼" 라고 이야기를 했다. 그래서, 다시 나누어 생각해보기로 한다.</p> <ol> <li>정적 팩토리 메서드 패턴 -> 객체를 생성하는 디자인 패턴</li> <li>싱글톤 객체 -> 객체의 인스턴스를 한개만 생성되게 하는 패턴</li> </ol> <h3 id="정적-팩토리-메서드-패턴이-싱글톤을-보장하는가" style="position:relative;"><a href="#%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%8C%A8%ED%84%B4%EC%9D%B4-%EC%8B%B1%EA%B8%80%ED%86%A4%EC%9D%84-%EB%B3%B4%EC%9E%A5%ED%95%98%EB%8A%94%EA%B0%80" aria-label="정적 팩토리 메서드 패턴이 싱글톤을 보장하는가 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>정적 팩토리 메서드 패턴이 싱글톤을 보장하는가?</h3> <p>당연하게도 정적 팩토리 메서드 패턴만을 사용한다고 객체가 싱글톤으로 생성된다는 것은 말이 안된다. 그 당시에 무슨 생각으로 그렇게 말을 했을까? 어쨌든, 정적 팩토리 메서드에 인스턴스가 싱글톤으로 생성될 수 있도록 검증하는 로직이 필요하다. 아까, JS로 테스트한 코드에 추가해보자.</p> <div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">Latte</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">"Latte"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">LatteFactory</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>LatteFactory<span class="token punctuation">.</span>instance<span class="token punctuation">)</span> <span class="token punctuation">{</span> LatteFactory<span class="token punctuation">.</span>instance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Latte</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> LatteFactory<span class="token punctuation">.</span>instance<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> factoryList <span class="token operator">=</span> <span class="token punctuation">{</span> LatteFactory <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">CoffeeFactory</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token parameter">type</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> factory <span class="token operator">=</span> factoryList<span class="token punctuation">[</span>type<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> factory<span class="token punctuation">.</span><span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> coffee <span class="token operator">=</span> CoffeeFactory<span class="token punctuation">.</span><span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token string">"LatteFactory"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> coffee2 <span class="token operator">=</span> CoffeeFactory<span class="token punctuation">.</span><span class="token function">makeCoffee</span><span class="token punctuation">(</span><span class="token string">"LatteFactory"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>coffee<span class="token operator">==</span>coffee2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// True</span></code></pre></div> <p>LatteFactory -> makeCoffee 메서드 내부에 해당 객체가 이미 존재하는지를 검증하는 로직이 추가되었다. 그 결과, 객체를 최초로 생성한 이후 추가 생성에 대해서는 새로 객체를 생성하지 않는다.</p> <p>이로써, 싱글톤 객체를 보장하는 정적 팩토리 메서드 패턴을 코드로 구현해보았다.</p> <p>지인에게 바로 연락해서 잘못된 내용을 바로 잡았다. <img src="https://i.imgur.com/zEDenOZ.png" alt=""></p> <h2 id="근데-왜-잘못-말했지" style="position:relative;"><a href="#%EA%B7%BC%EB%8D%B0-%EC%99%9C-%EC%9E%98%EB%AA%BB-%EB%A7%90%ED%96%88%EC%A7%80" aria-label="근데 왜 잘못 말했지 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>근데 왜 잘못 말했지?</h2> <p>지인에게 잘못된 내용에 대해 이야기하고 내가 왜 그렇게 생각했었는지 다시 고민해보았다. 우린, 객체 지향 프레임워크 (Spring, NestJS 등)을 사용하여 서비스를 구현한다. DI/IoC 기능으로 우리는 객체에 대한 생명 주기를 신경쓰지 않아도 되는데, 나는 이 부분과 혼동하여 무턱대고 "정적 팩토리 메서드 패턴을 사용하면 객체가 싱글톤으로 관리됨" 이라는 판단을 내린 것이다.</p> <h2 id="그럼-객체-지향-프레임워크에서는-싱글톤을-어떻게-관리할까" style="position:relative;"><a href="#%EA%B7%B8%EB%9F%BC-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EC%97%90%EC%84%9C%EB%8A%94-%EC%8B%B1%EA%B8%80%ED%86%A4%EC%9D%84-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B4%80%EB%A6%AC%ED%95%A0%EA%B9%8C" aria-label="그럼 객체 지향 프레임워크에서는 싱글톤을 어떻게 관리할까 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>그럼 객체 지향 프레임워크에서는 싱글톤을 어떻게 관리할까?</h2> <p>갑자기, 객체 지향 프레임워크 내에서는 어떻게 객체를 싱글톤으로 관리하는지 궁금했다. 분명히, 내가 위에서 말한 싱글톤을 위한 검증 로직이 존재할텐데 어떤 식으로 구성을 하고 있는지 직접 두 눈으로 보고 싶다는 호기심이 생겼다.</p> <h3 id="NestJS-Core부터-까보자" style="position:relative;"><a href="#NestJS-Core%EB%B6%80%ED%84%B0-%EA%B9%8C%EB%B3%B4%EC%9E%90" aria-label="NestJS Core부터 까보자 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>NestJS Core부터 까보자</h3> <p>우선, 내가 가장 잘 이해할 수 있는 NestJS 부터 까보기로 했다. 평소에도 NestJS의 Core를 뜯어봤기 때문에, 빠르게 확인할 수 있을 것 같았다. NestJS의 IoC 컨테이너를 바로 확인해봤다.</p> <p>NestJS에서 싱글톤 객체를 생성하고 관리하는 부분은 다음 메소드들에서 이루어진다:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token function">addModule</span><span class="token punctuation">(</span>metatype<span class="token operator">:</span> ModuleMetatype<span class="token punctuation">,</span> scope<span class="token operator">:</span> ModuleScope<span class="token punctuation">)</span> <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">setModule</span><span class="token punctuation">(</span><span class="token punctuation">{</span> token<span class="token punctuation">,</span> dynamicMetadata<span class="token punctuation">,</span> type <span class="token punctuation">}</span><span class="token operator">:</span> ModuleFactory<span class="token punctuation">,</span> scope<span class="token operator">:</span> ModuleScope<span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token function">addProvider</span><span class="token punctuation">(</span>provider<span class="token operator">:</span> Provider<span class="token punctuation">,</span> token<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> enhancerSubtype<span class="token operator">?</span><span class="token operator">:</span> EnhancerSubtype<span class="token punctuation">)</span></code></pre></div> <p>여기서, addModule 메서드를 살펴보면,</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token comment">// NestContainer.ts</span> <span class="token operator">...</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token function">addModule</span><span class="token punctuation">(</span> metatype<span class="token operator">:</span> ModuleMetatype<span class="token punctuation">,</span> scope<span class="token operator">:</span> ModuleScope<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span> <span class="token operator">|</span> <span class="token punctuation">{</span> moduleRef<span class="token operator">:</span> Module<span class="token punctuation">;</span> inserted<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token keyword">undefined</span> <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token comment">// In DependenciesScanner#scanForModules we already check for undefined or invalid modules</span> <span class="token comment">// We still need to catch the edge-case of `forwardRef(() => undefined)`</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>metatype<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UndefinedForwardRefException</span><span class="token punctuation">(</span>scope<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> dynamicMetadata<span class="token punctuation">,</span> token <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>moduleCompiler<span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span>metatype<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>modules<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> moduleRef<span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>modules<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">,</span> inserted<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> moduleRef<span class="token operator">:</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setModule</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> token<span class="token punctuation">,</span> type<span class="token punctuation">,</span> dynamicMetadata<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> scope<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> inserted<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">...</span></code></pre></div> <p>위 코드에서 객체가 이미 생성되어 존재하는지를 확인하는 로직이 if 문으로 구현되어 있는 것을 확인할 수 있다. token은 모듈의 고유 식별자로, 각 모듈마다 유일한 token이 생성되며 중복 생성 방지에 중요한 역할을 한다.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>modules<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> moduleRef<span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>modules<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">,</span> inserted<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>이 코드에서는 모듈 token이 이미 존재할 경우 기존 모듈 인스턴스를 반환하여 싱글톤 패턴을 유지한다. 만약 존재하지 않는다면 setModule을 통해 새로 객체를 생성한다.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">return</span> <span class="token punctuation">{</span> moduleRef<span class="token operator">:</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setModule</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> token<span class="token punctuation">,</span> type<span class="token punctuation">,</span> dynamicMetadata<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> scope<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> inserted<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>이처럼, NestJS에서는 addModule 메소드 내 token 확인을 통해 싱글톤 패턴을 유지하고 있다.</p> <h3 id="Spring-Core도-봐야지" style="position:relative;"><a href="#Spring-Core%EB%8F%84-%EB%B4%90%EC%95%BC%EC%A7%80" aria-label="Spring Core도 봐야지 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Spring Core도 봐야지</h3> <p>자 그럼, 최근에 내가 사용하고 있는 Spring에서도 동일한 기능을 어떻게 지원하는지를 확인해보자.</p> <p>Spring에서 싱글톤 객체 관리는 DefaultSingletonBeanRegistry 클래스의 getSingleton과 addSingleton 메서드를 통해 이루어진다. 이 과정에서 Spring은 싱글톤 패턴을 유지하기 위해 캐시 맵을 활용하며, 동일한 빈에 대한 중복 생성을 방지한다. 아래에서 Spring의 싱글톤 객체 관리 과정의 각 단계와 메서드를 설명한다.</p> <p>getSingleton 메서드는 먼저 singletonObjects 캐시에 해당 빈이 존재하는지 확인한다. 빈이 이미 생성된 경우, 캐시에서 즉시 반환하여 불필요한 생성 과정을 피한다.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token comment">// DefaultSingletonBeanRegistry.java</span> <span class="token keyword">public</span> <span class="token class-name">Object</span> <span class="token function">getSingleton</span><span class="token punctuation">(</span><span class="token class-name">String</span> beanName<span class="token punctuation">,</span> <span class="token keyword">boolean</span> allowEarlyReference<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name">Object</span> singletonObject <span class="token operator">=</span> singletonObjects<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>singletonObject <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token function">isSingletonCurrentlyInCreation</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> singletonObject <span class="token operator">=</span> earlySingletonObjects<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">return</span> singletonObject<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>이 메서드는 싱글톤 객체가 생성되지 않았다면, createBean 메서드를 호출해 객체를 생성하고 이를 캐시에 등록해준다. 이렇게 하면 동일한 빈에 대해 항상 같은 인스턴스를 반환하도록 보장한다.</p> <p>싱글톤 객체가 singletonObjects에 없으면, createBean 메서드가 호출되어 새 객체를 인스턴스화하고 의존성을 주입한 후 초기화 과정을 거친다. 생성된 객체는 addSingleton을 통해 캐시에 등록된다.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token comment">// AbstractAutowireCapableBeanFactory.java</span> <span class="token keyword">protected</span> <span class="token class-name">Object</span> <span class="token function">doCreateBean</span><span class="token punctuation">(</span><span class="token class-name">String</span> beanName<span class="token punctuation">,</span> <span class="token class-name">RootBeanDefinition</span> mbd<span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name">BeanWrapper</span> instanceWrapper <span class="token operator">=</span> <span class="token function">createBeanInstance</span><span class="token punctuation">(</span>beanName<span class="token punctuation">,</span> mbd<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">populateBean</span><span class="token punctuation">(</span>beanName<span class="token punctuation">,</span> mbd<span class="token punctuation">,</span> instanceWrapper<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">return</span> <span class="token function">initializeBean</span><span class="token punctuation">(</span>beanName<span class="token punctuation">,</span> instanceWrapper<span class="token punctuation">.</span><span class="token function">getWrappedInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> mbd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>registerSingleton 메서드도 외부 객체를 싱글톤으로 등록할 때 addSingleton을 호출한다.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token comment">// DefaultSingletonBeanRegistry.java</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">registerSingleton</span><span class="token punctuation">(</span><span class="token class-name">String</span> beanName<span class="token punctuation">,</span> <span class="token class-name">Object</span> singletonObject<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token function">addSingleton</span><span class="token punctuation">(</span>beanName<span class="token punctuation">,</span> singletonObject<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token keyword">void</span> <span class="token function">addSingleton</span><span class="token punctuation">(</span><span class="token class-name">String</span> beanName<span class="token punctuation">,</span> <span class="token class-name">Object</span> singletonObject<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>singletonObjects<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>beanName<span class="token punctuation">,</span> singletonObject<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>singletonFactories<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>earlySingletonObjects<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>registeredSingletons<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>이렇게 addSingleton은 빈을 싱글톤 객체로 캐시에 등록해, 이후 같은 빈 이름으로 요청이 들어오면 동일한 인스턴스를 반환하게 한다.</p> <h2 id="마무리" style="position:relative;"><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC" aria-label="마무리 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>마무리</h2> <p>내가 잘못말한 내용이 프레임워크 코어까지 뜯어보게 한 트리거가 되었다. 궁금증이 생겼을 때 혹은 이해가 되지 않을 때 항상 끝까지 다이브하는 습관이 있는데, 나는 이 습관이 너무 잘 만들어 놓은 습관이라고 생각한다. 덕분에, 항상 새벽에 잠들긴하지만 몰랐던 부분에 대해서 그냥 넘어가는 것보다 훨씬 속편한 것 같다.</p> <p>아무튼, 이렇게 "싱글톤에 대한 오해" 로 시작해서, 실제 우리가 사용하고 있는 프레임워크 내부까지 살펴보았다. 앞으로는 어디가서 똑같은 실수를 하지 않아야겠다..^^</p>https://eeeasycode.dev/nestjs-interface-di/https://eeeasycode.dev/nestjs-interface-di/Sat, 24 Aug 2024 00:00:00 GMT<h1 id="NestJS에서의-DI와-인터페이스" style="position:relative;"><a href="#NestJS%EC%97%90%EC%84%9C%EC%9D%98-DI%EC%99%80-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4" aria-label="NestJS에서의 DI와 인터페이스 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>NestJS에서의 DI와 인터페이스</h1> <h2 id="SpringBoot" style="position:relative;"><a href="#SpringBoot" aria-label="SpringBoot permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>SpringBoot</h2> <p>우선 SpringBoot에서 OCP와 DIP를 지키기 위해, DI하는 경우 인터페이스를 사용하는 것을 볼 수 있다.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">interface</span> <span class="token class-name">UserService</span> <span class="token punctuation">{</span> <span class="token keyword">void</span> <span class="token function">signUp</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Service</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserServiceImpl</span> <span class="token keyword">implements</span> <span class="token class-name">UserService</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">signUp</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@RestController</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserController</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">UserService</span> userService<span class="token punctuation">;</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">{</span></code></pre></div> <p>간단하게, DI 받는 부분에서 <code>private final UserService: userService</code> 로 명시하는 것을 볼 수 있다.</p> <h2 id="NestJS" style="position:relative;"><a href="#NestJS" aria-label="NestJS permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>NestJS</h2> <p>그럼 NestJS도 SpringBoot와 같은 개념이니까, 그대로 따라하면 정상적으로 동작할까?</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">UserService</span> <span class="token punctuation">{</span> <span class="token function">signUp</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token decorator"><span class="token at operator">@</span><span class="token function">Injectable</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">UserServiceImpl</span> <span class="token keyword">implements</span> <span class="token class-name">UserService</span> <span class="token punctuation">{</span> <span class="token function">signUp</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token decorator"><span class="token at operator">@</span><span class="token function">Controller</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">UserController</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> userService<span class="token operator">:</span> UserService<span class="token punctuation">;</span> <span class="token punctuation">)</span> <span class="token operator">...</span> <span class="token punctuation">}</span></code></pre></div> <p>결과는 Nest can't resolve dependencies ~ 에러를 뱉어낸다. 의존성을 해결하지 못해 발생하는 에러인데, 왜 발생하는걸까?</p> <br> <h3 id="TS의-interface" style="position:relative;"><a href="#TS%EC%9D%98-interface" aria-label="TS의 interface permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>TS의 interface</h3> <p>Typescript에서 제공하는 interface는 런타임 시 사라지게 된다. DI는 런타임 시점에서 동작하게 되는데, 해당 interface를 찾지 못해 의존성을 해결하지 못하는 것이다.</p> <p>그럼 그냥 사용하지 못하는걸까?</p> <br> <h3 id="Provider-설정" style="position:relative;"><a href="#Provider-%EC%84%A4%EC%A0%95" aria-label="Provider 설정 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Provider 설정</h3> <p>그건 아니다. 다행히도 NestJS의 provider 설정을 직접 해주면 된다.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Module</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span> controllers<span class="token operator">:</span> <span class="token punctuation">[</span>UserController<span class="token punctuation">]</span><span class="token punctuation">,</span> providers<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> provide<span class="token operator">:</span> <span class="token string">'USER_SERVICE'</span><span class="token punctuation">,</span> useClass<span class="token operator">:</span> UserServiceImpl<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">UserModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token decorator"><span class="token at operator">@</span><span class="token function">Controller</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">UserController</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span> <span class="token decorator"><span class="token at operator">@</span><span class="token function">Inject</span></span><span class="token punctuation">(</span><span class="token string">'USER_SERVICE'</span><span class="token punctuation">)</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> userService<span class="token operator">:</span> UserService<span class="token punctuation">;</span> <span class="token punctuation">)</span> <span class="token operator">...</span> <span class="token punctuation">}</span></code></pre></div> <p>먼저, module의 providers에 Inject Token과 실제 구현 클래스를 명시해준다. 그 뒤, DI 하는 부분에서 내가 명시한 Inject Token으로 Inject 받으면 문제 없이 인터페이스를 DI할 수 있게 된다.</p> <br> <h1 id="Reference" style="position:relative;"><a href="#Reference" aria-label="Reference permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Reference</h1> <p><a href="https://github.com/EeeasyCode/EeeasyCode.dev/blob/main/contents/posts/nestjs-providers/index.md">NestJS Providers</a></p>https://eeeasycode.dev/opensource-toss-aop/https://eeeasycode.dev/opensource-toss-aop/Thu, 20 Jun 2024 00:00:00 GMT<h1 id="Toss-AOP-라이브러리-한글-문서-번역" style="position:relative;"><a href="#Toss-AOP-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%ED%95%9C%EA%B8%80-%EB%AC%B8%EC%84%9C-%EB%B2%88%EC%97%AD" aria-label="Toss AOP 라이브러리 한글 문서 번역 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>[Toss AOP 라이브러리] 한글 문서 번역</h1> <h2 id="왜-한글-문서-번역" style="position:relative;"><a href="#%EC%99%9C-%ED%95%9C%EA%B8%80-%EB%AC%B8%EC%84%9C-%EB%B2%88%EC%97%AD" aria-label="왜 한글 문서 번역 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>왜 한글 문서 번역?</h2> <p>최근에 오픈소스 기여에 대해 관심이 생기고 바로 내가 기여할 수 있는 게 무엇일까를 고민하던 중, 어떤 블로그에서 영어 문서를 한글 문서로 번역하는 것부터 시작해보라는 말을 보게 되었다.</p> <p>그래서, 내가 최근 가장 관심있게 보던 오픈소스인 TOSS의 AOP 라이브러리를 확인해보았고, 마침 영어 문서 밖에 없어 한글 문서 번역을 기여했다.</p> <p>어려운 내용은 크게 없었고, 내가 기여한 번역을 통해 누군가는 도움이 되었으면 하는 마음으로 진행했다.</p> <h2 id="기여한-내용" style="position:relative;"><a href="#%EA%B8%B0%EC%97%AC%ED%95%9C-%EB%82%B4%EC%9A%A9" aria-label="기여한 내용 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>기여한 내용</h2> <p>Toss의 NodeJS Developer 챕터에서 만든 라이브러리의 사용 예제에 대해 문서 번역을 기여했다.</p> <h2 id="느낀-점" style="position:relative;"><a href="#%EB%8A%90%EB%82%80-%EC%A0%90" aria-label="느낀 점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>느낀 점</h2> <p>오픈소스 기여라고 하면 굉장히 어려운 것처럼 다가오는 것 같다. 사실은 한글 문서 번역도 나름이지만 어쨌든 누군가에게 편리함을 줄 수 있는 것에 대해 도움이 될 수 있다면 그것도 나는 기여했다고 생각한다.</p> <p>다음으로 TOSS/AOP 라이브러리가 무엇인지 자세히 분석해보려고 한다.</p> <!-- [TOSS/AOP 라이브러리 분석](https://eeeasycode.github.io/toss-aop-review/) --> <h2 id="Reference" style="position:relative;"><a href="#Reference" aria-label="Reference permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Reference</h2> <p><a href="https://github.com/toss/nestjs-aop/pull/35" title="toss-github link">기여한 PR Link</a></p> yarn berry (zero install) -> pnpm으로 마이그레이션 하는 과정과 docker multi-stage 적용, nestjs에서 swc…]]>https://eeeasycode.dev/nestjs-build-time/https://eeeasycode.dev/nestjs-build-time/Tue, 18 Jun 2024 00:00:00 GMT<h1 id="Feature" style="position:relative;"><a href="#Feature" aria-label="Feature permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Feature</h1> <p>빌드 속도를 개선하기 위해, <strong>yarn classic -> yarn berry (zero install) -> pnpm</strong>으로 마이그레이션 하는 과정과 <strong>docker multi-stage</strong> 적용, nestjs에서 <strong>swc</strong>를 통한 컴파일 속도 최적화를 진행했습니다.</p> <h2 id="Situation" style="position:relative;"><a href="#Situation" aria-label="Situation permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Situation</h2> <p>현재 저희는 <strong>AWS ECS</strong>를 통해 서버를 배포하고 있습니다. github의 코드가 <strong>docker image</strong>로 빌드되고, <strong>AWS ECR</strong>을 거쳐 <strong>AWS ECS</strong>의 인스턴스로 생성되는 파이프라인이 구성되어 있습니다.</p> <h2 id="Task" style="position:relative;"><a href="#Task" aria-label="Task permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Task</h2> <ul> <li>package 설치 속도 및 의존성 관리 개선</li> <li>docker 빌드 속도 개선</li> <li>nestjs 빌드 · 컴파일 속도 개선</li> </ul> <h2 id="Action" style="position:relative;"><a href="#Action" aria-label="Action permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Action</h2> <h3 id="package-manager-마이그레이션" style="position:relative;"><a href="#package-manager-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98" aria-label="package manager 마이그레이션 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>package manager 마이그레이션</h3> <p>기존 yarn classic에서 yarn berry (zero-install)을 업그레이드한 뒤, 다양한 고려사항으로 인해 pnpm으로 최종 마이그레이션을 진행했습니다.</p> <p>실제 pacakge 설치 속도 및 크기를 크게 개선할 수 있었습니다.</p> <table> <thead> <tr> <th>yarn classic</th> <th>pnpm</th> </tr> </thead> <tbody> <tr> <td>153.4s</td> <td>49.0s</td> </tr> </tbody> </table> <h2 id="Result" style="position:relative;"><a href="#Result" aria-label="Result permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Result</h2> <p>해당 작업 결과로 docker 이미지 빌드 시간은 301.6s -> 112.6s 로 개선했고, 이미지 용량도 1GB -> 830MB로 감소시킬 수 있었다.</p> <table> <thead> <tr> <th>yarn classic</th> <th>pnpm</th> </tr> </thead> <tbody> <tr> <td>[+] Building 301.6s (12/12) FINISHED</td> <td>[+] Building 112.6s (17/17) FINISHED</td> </tr> </tbody> </table> <img width='1157' alt='upload_test' src='https://imgur.com/zJUQ9n9.png'>https://eeeasycode.dev/typeorm-join/https://eeeasycode.dev/typeorm-join/Sun, 12 May 2024 00:00:00 GMT<h1 id="Feature" style="position:relative;"><a href="#Feature" aria-label="Feature permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Feature</h1> <p>typeORM에서 연관관계가 없는 테이블 간 join 하는 방법</p> <h2 id="Tables" style="position:relative;"><a href="#Tables" aria-label="Tables permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Tables</h2> <h3 id="User-테이블" style="position:relative;"><a href="#User-%ED%85%8C%EC%9D%B4%EB%B8%94" aria-label="User 테이블 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>User 테이블</h3> <img width='1166' alt='user_table' src='https://velog.velcdn.com/images/eeeasy-code/post/50634c0e-0673-416b-89b1-c59c4ddbe95e/image.png'> <h3 id="Payment-테이블" style="position:relative;"><a href="#Payment-%ED%85%8C%EC%9D%B4%EB%B8%94" aria-label="Payment 테이블 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Payment 테이블</h3> <img width='1179' alt='payment_table' src='https://velog.velcdn.com/images/eeeasy-code/post/4e82823e-3179-49b2-8f71-561a9944d7c5/image.png'> <p>각 테이블은 연관관계를 설정하지 않았고, payment 테이블에서 userId를 저장할 수 있게 했습니다.</p> <h2 id="Join-case" style="position:relative;"><a href="#Join-case" aria-label="Join case permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Join case</h2> <p>모든 user에 대한 payment 정보를 조회하기 위해 join을 합니다.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">async</span> <span class="token function">leftJoin</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>usersRepository <span class="token punctuation">.</span><span class="token function">createQueryBuilder</span><span class="token punctuation">(</span><span class="token string">'user'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">leftJoinAndMapMany</span><span class="token punctuation">(</span><span class="token string">'user.payment'</span><span class="token punctuation">,</span> Payments<span class="token punctuation">,</span> <span class="token string">'payment'</span><span class="token punctuation">,</span> <span class="token string">'user.id = payment.userId'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">getMany</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="동작한-query" style="position:relative;"><a href="#%EB%8F%99%EC%9E%91%ED%95%9C-query" aria-label="동작한 query permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>동작한 query</h3> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">query: SELECT <span class="token variable"><span class="token variable">`</span>user<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span><span class="token function">id</span><span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>user_id<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>user<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>userEmail<span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>user_userEmail<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>user<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>password<span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>user_password<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>user<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>created_at<span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>user_created_at<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>user<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>updated_at<span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>user_updated_at<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>user<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>deleted_at<span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>user_deleted_at<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>payment<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span><span class="token function">id</span><span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>payment_id<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>payment<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>userId<span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>payment_userId<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>payment<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>created_at<span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>payment_created_at<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>payment<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>updated_at<span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>payment_updated_at<span class="token variable">`</span></span>, <span class="token variable"><span class="token variable">`</span>payment<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>deleted_at<span class="token variable">`</span></span> AS <span class="token variable"><span class="token variable">`</span>payment_deleted_at<span class="token variable">`</span></span> FROM <span class="token variable"><span class="token variable">`</span><span class="token function">users</span><span class="token variable">`</span></span> <span class="token variable"><span class="token variable">`</span>user<span class="token variable">`</span></span> LEFT JOIN <span class="token variable"><span class="token variable">`</span>payments<span class="token variable">`</span></span> <span class="token variable"><span class="token variable">`</span>payment<span class="token variable">`</span></span> ON <span class="token variable"><span class="token variable">`</span>user<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span><span class="token function">id</span><span class="token variable">`</span></span> <span class="token operator">=</span> <span class="token variable"><span class="token variable">`</span>payment<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>userId<span class="token variable">`</span></span> AND <span class="token variable"><span class="token variable">`</span>payment<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>deleted_at<span class="token variable">`</span></span> IS NULL WHERE <span class="token variable"><span class="token variable">`</span>user<span class="token variable">`</span></span><span class="token builtin class-name">.</span><span class="token variable"><span class="token variable">`</span>deleted_at<span class="token variable">`</span></span> IS NULL</code></pre></div> <h3 id="리턴된-결과값" style="position:relative;"><a href="#%EB%A6%AC%ED%84%B4%EB%90%9C-%EA%B2%B0%EA%B3%BC%EA%B0%92" aria-label="리턴된 결과값 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>리턴된 결과값</h3> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"userEmail"</span><span class="token operator">:</span> <span class="token string">"[email protected]"</span><span class="token punctuation">,</span> <span class="token property">"password"</span><span class="token operator">:</span> <span class="token string">"password123"</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span> <span class="token property">"payment"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token property">"userId"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"userId"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token property">"userEmail"</span><span class="token operator">:</span> <span class="token string">"[email protected]"</span><span class="token punctuation">,</span> <span class="token property">"password"</span><span class="token operator">:</span> <span class="token string">"password123"</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span> <span class="token property">"payment"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token property">"userId"</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token property">"userId"</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token property">"userEmail"</span><span class="token operator">:</span> <span class="token string">"[email protected]"</span><span class="token punctuation">,</span> <span class="token property">"password"</span><span class="token operator">:</span> <span class="token string">"password123"</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span> <span class="token property">"payment"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token property">"userId"</span><span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:10:18.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token property">"userEmail"</span><span class="token operator">:</span> <span class="token string">"[email protected]"</span><span class="token punctuation">,</span> <span class="token property">"password"</span><span class="token operator">:</span> <span class="token string">"password123"</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span> <span class="token property">"payment"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"id"</span><span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token property">"userEmail"</span><span class="token operator">:</span> <span class="token string">"[email protected]"</span><span class="token punctuation">,</span> <span class="token property">"password"</span><span class="token operator">:</span> <span class="token string">"password123"</span><span class="token punctuation">,</span> <span class="token property">"created_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"updated_at"</span><span class="token operator">:</span> <span class="token string">"2024-05-06T16:08:37.000Z"</span><span class="token punctuation">,</span> <span class="token property">"deleted_at"</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span> <span class="token property">"payment"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span></code></pre></div>https://eeeasycode.dev/nestjs-image-upload/https://eeeasycode.dev/nestjs-image-upload/Wed, 03 Apr 2024 00:00:00 GMT<h1 id="Feature" style="position:relative;"><a href="#Feature" aria-label="Feature permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Feature</h1> <p>Image Upload 서버에서 Sharp 라이브러리를 사용해 Content-type 변경하는 기능 구현</p> <h2 id="Situation" style="position:relative;"><a href="#Situation" aria-label="Situation permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Situation</h2> <p>기존 운영중인 서비스은 S3에 이미지 업로드 할 때, image/jpg로 업로드됩니다. JPG는 PNG에 비해 작은 용량의 크기로 사진을 압축할 수 있지만 WebP와 비교했을 때 화질면에서 크게 떨어집니다. 또한, Webp는 PNG에 비해 크기가 26%, JPEG 이미지보다 25~34% 더 작습니다.</p> <blockquote> <p>출처 : <a href="https://developers.google.com/speed/webp?hl=ko">https://developers.google.com/speed/webp?hl=ko</a></p> </blockquote> <h2 id="Task" style="position:relative;"><a href="#Task" aria-label="Task permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Task</h2> <ul> <li>업로드 할 이미지의 용량 축소 기능 구현</li> <li>Sharp 라이브러리를 활용하여 이미지의 Content-Type을 image/webp 로 변환</li> </ul> <h2 id="Action" style="position:relative;"><a href="#Action" aria-label="Action permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Action</h2> <ul> <li>이미지 업로드 API의 서비스 로직에 Sharp 라이브러리로 Image의 Buffer를 webp로 변환</li> <li>이미지 파일의 확장자를 .webp로 변환</li> <li>S3 업로드 시, ContentType을 image/webp로 설정</li> </ul> <h2 id="Result" style="position:relative;"><a href="#Result" aria-label="Result permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Result</h2> <p>테스트 결과 -> 파일 A, 파일 B</p> <blockquote> <p>png 파일 업로드 -> 131.2 KB, 333.5 KB <br> jpeg 파일 업로드 -> 86.0 KB, 241.8 KB <br> > <strong>webp 파일 업로드 -> 50.9 KB, 128.2 KB</strong> <br></p> </blockquote> <p>로 webp 변환 시 저장 공간에 대한 효율성을 향상시킬 수 있습니다. 이는 업로드에 대한 성능 뿐만 아니라, 실제 클라이언트에게 이미지 파일이 전달될 때에 확실한 성능 차이가 보여질 것으로 판단됩니다.</p> <p><img src="https://velog.velcdn.com/images/eeeasy-code/post/f8753929-ab0b-40a9-abdc-fc0ce7cbf452/image.png" alt=""></p>https://eeeasycode.dev/github-slack-bot/https://eeeasycode.dev/github-slack-bot/Tue, 27 Feb 2024 00:00:00 GMT<h2 id="Tech-Environment" style="position:relative;"><a href="#Tech-Environment" aria-label="Tech Environment permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Tech Environment</h2> <ul> <li>Python v3.12</li> <li>actions/checkout@v3</li> <li>actions/setup-python@v3</li> <li>python code의 의존성을 위해 requirements.txt로 라이브러리 설치</li> </ul> <h2 id="member_listjson" style="position:relative;"><a href="#member_listjson" aria-label="member_listjson permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>member_list.json</h2> <p>해당 파일에 멤버 리스트를 등록하여 사용합니다.</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"githubName"</span><span class="token operator">:</span> <span class="token string">"CEethan"</span><span class="token punctuation">,</span> <span class="token property">"slackUserId"</span><span class="token operator">:</span> <span class="token string">"U069RPHRU95"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"githubName"</span><span class="token operator">:</span> <span class="token string">"EeeasyCode"</span><span class="token punctuation">,</span> <span class="token property">"slackUserId"</span><span class="token operator">:</span> <span class="token string">"U069RPHRU95"</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span></code></pre></div> <p>이와 같은 형식으로 등록합니다.</p> <hr> <h2 id="PR-notificaiton-bot" style="position:relative;"><a href="#PR-notificaiton-bot" aria-label="PR notificaiton bot permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>PR notificaiton bot</h2> <blockquote> <p>지정한 레포지토리의 PR이 남아있는지 확인 후, 평일 지정한 시간에 Slack을 통해 알림을 전송하는 Bot 입니다.</p> </blockquote> <h3 id="Code-Description" style="position:relative;"><a href="#Code-Description" aria-label="Code Description permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Code Description</h3> <h3 id="pr-notificationpy" style="position:relative;"><a href="#pr-notificationpy" aria-label="pr notificationpy permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>pr-notification.py</h3> <ul> <li>python 코드로 slack, github 연동</li> <li>github repository 정보를 가져와 slack 메시지 형태로 가공</li> <li>가공된 메시지를 지정한 slack 채널로 전송</li> </ul> <h3 id="pr-notification-botyml" style="position:relative;"><a href="#pr-notification-botyml" aria-label="pr notification botyml permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>pr-notification-bot.yml</h3> <ul> <li>schedule -> cron 표현식을 통해 지정한 시간마다 동작하도록 스케줄링</li> <li>이후, github secret을 사용해 env 값 설정</li> <li>github action을 활용하여 pr-notification.py를 실행</li> </ul> <hr> <h2 id="assign-reviewer-bot" style="position:relative;"><a href="#assign-reviewer-bot" aria-label="assign reviewer bot permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>assign reviewer bot</h2> <blockquote> <p>PR을 올리면 랜덤으로 리뷰어가 할당되어 Slack을 통해 알림받을 수 있습니다.</p> </blockquote> <h3 id="Code-Description-1" style="position:relative;"><a href="#Code-Description-1" aria-label="Code Description 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Code Description</h3> <h3 id="assign-reviewerpy" style="position:relative;"><a href="#assign-reviewerpy" aria-label="assign reviewerpy permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>assign-reviewer.py</h3> <ul> <li>python 코드로 slack, github 연동</li> <li>python 내부 로직에 의해, 자동으로 리뷰어를 할당하여 등록함</li> <li>리뷰어로 할당된 멤버에게 Slack 메시지 전송</li> </ul> <h3 id="assign-reviewer-botyml" style="position:relative;"><a href="#assign-reviewer-botyml" aria-label="assign reviewer botyml permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>assign-reviewer-bot.yml</h3> <ul> <li>PR 이벤트를 감지하여 PR이 올라올 경우 해당 action 트리거</li> <li>이후, github secret을 사용해 env 값 설정</li> <li>github action을 활용하여 assign-reviewer.py를 실행</li> </ul> <hr> <h2 id="review-check-bot" style="position:relative;"><a href="#review-check-bot" aria-label="review check bot permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>review check bot</h2> <blockquote> <p>리뷰어가 PR에 대한 리뷰를 완료하면 PR 담당자에게 Slack 메시지를 전송합니다.</p> </blockquote> <h3 id="Code-Description-2" style="position:relative;"><a href="#Code-Description-2" aria-label="Code Description 2 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Code Description</h3> <h3 id="review-checkpy" style="position:relative;"><a href="#review-checkpy" aria-label="review checkpy permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>review-check.py</h3> <ul> <li>python 코드로 slack, github 연동</li> <li>python 내부 로직에 의해 PR 담당자에게 리뷰가 되었음을 알림</li> </ul> <h3 id="review-check-botyml" style="position:relative;"><a href="#review-check-botyml" aria-label="review check botyml permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>review-check-bot.yml</h3> <ul> <li>PR의 리뷰 이벤트를 감지하여 리뷰가 등록된 경우 해당 action 트리거</li> <li>이후, github secret을 사용해 env 값 설정</li> <li>github action을 활용하여 review-check.py를 실행</li> </ul>https://eeeasycode.dev/concurrency-issue/https://eeeasycode.dev/concurrency-issue/Fri, 16 Feb 2024 00:00:00 GMT<p>동시에 같은 DB 테이블 row를 업데이트 하는 상황은 <Strong>"DB의 동시성 이슈"</Strong>으로 생각해보면 좋을 것 같다.</p> <h1 id="DB의-동시성-이슈" style="position:relative;"><a href="#DB%EC%9D%98-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88" aria-label="DB의 동시성 이슈 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>DB의 동시성 이슈</h1> <p><Strong>동시성</Strong>이란 여러 요청이 동시에 동일한 자원(Data)에 접근하고 수정하려는 것을 말한다. 이로 인해, 발생하게 되는 문제를 동시성 문제라고 한다. 동시성 문제로 Data의 무결성이 깨지고 의도하지 않은 결과를 반환하게 되는 문제들이 발생한다.</p> <hr> <h1 id="해결방안" style="position:relative;"><a href="#%ED%95%B4%EA%B2%B0%EB%B0%A9%EC%95%88" aria-label="해결방안 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>해결방안</h1> <p>해결방안에는 DB수준에서의 락, 프레임워크 or 언어 수준에서의 동기화 등이 존재한다. 여기서는 DB수준에서의 락에 대해 알아보려고 한다.</p> <blockquote> <ol> <li>테이블의 row에 접근 시, Lock을 걸고 다른 Lock이 걸려있지 않는 경우에만 수정을 가능하게 함</li> <li>수정할 때 내가 먼저 수정했음을 명시하여 다른 곳에서 동일한 조건으로 값을 수정할 수 없게 함</li> </ol> </blockquote> <p>위처럼, 자원 경쟁에 대한 관점으로 두 가지의 방법을 생각해볼 수 있다. 이는 비관적 락과 낙관적 락을 나누는 기준이 된다.</p> <hr> <h2 id="비관적-락Pessimistic-Lock" style="position:relative;"><a href="#%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BDPessimistic-Lock" aria-label="비관적 락Pessimistic Lock permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>비관적 락(Pessimistic Lock)</h2> <p>현재 수정하려는 data가 언제든지 다른 요청에 의해 수정될 가능성을 고려하여 해당 data에 Lock을 거는 방식 트랜잭션이 시작될 때 Shared Lock 또는 Exclusive Lock을 걸고 시작한다.</p> <ul> <li> <p><code><Strong>공유락 (Shared Lock)</Strong></code> : Read Lock이라고 하는 공유락은 트랜잭션이 읽기를 할 때 사용하는 락이며, 데이터를 읽기만 하기 때문에 같은 공유락끼리는 동시에 접근이 가능하지만, 쓰기 작업은 막는다.</p> </li> <li> <p><code><Strong>베타락 (Exclusive Lock)</Strong></code> : Write Lock이라고 하는 배터락은, 데이터를 변경할 때 사용하는 락이다. 트랜잭션이 완료될 때까지 유지되며, 락이 끝나기 전까지 읽기/쓰기를 모두 막는다.</p> </li> </ul> <h3 id="장점" style="position:relative;"><a href="#%EC%9E%A5%EC%A0%90" aria-label="장점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>장점</h3> <ul> <li>data의 무결성을 보존할 수 있다.</li> <li>충돌 발생 미리 방지</li> </ul> <h3 id="단점" style="position:relative;"><a href="#%EB%8B%A8%EC%A0%90" aria-label="단점 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>단점</h3> <ul> <li>Lock으로 인해 이후의 다른 요청은 대기 상태로 빠짐</li> <li>기존 Lock의 트랜잭션이 commit/rollback으로 끝내면 이후 대기 요청을 실행</li> </ul> <hr> <h2 id="낙관적-락Optimistic-Lock" style="position:relative;"><a href="#%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BDOptimistic-Lock" aria-label="낙관적 락Optimistic Lock permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>낙관적 락(Optimistic Lock)</h2> <p>자원에 락을 걸지 않고, 동시성 문제가 발생하면 그때 처리한다. 숫자/시간 컬럼을 만들어 수정 시 그 data를 증가/갱신함 -> data 수정 시 컬럼을 비교하여 일치하는지 확인</p> <ul> <li> <p>Version과 같은 <Strong>별도의 컬럼을 추가</Strong>하여 충돌 발생을 막는다. Version -> hashcode / timestamp 등을 사용하여 상태를 보고 충돌 확인함</p> </li> <li> <p>충돌 발생 시, DB가 아닌 애플리케이션 단에서 처리를 한다. 낙관적 락은 UPDATE에 실패해도 자동으로 예외를 던지지 않고, 단순히 0개의 row를 업데이트한다. 따라서 이때 여러 작업이 묶은 트랜잭션 요청 실패 시,<Strong>우리가 직접 롤백 처리</Strong>를 해줘야 한다.</p> </li> </ul> <h3 id="장점-1" style="position:relative;"><a href="#%EC%9E%A5%EC%A0%90-1" aria-label="장점 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>장점</h3> <ul> <li>구현하기 용이함</li> <li>지속적인 락으로 인한 성능저하를 막을 수 있음</li> </ul> <h3 id="단점-1" style="position:relative;"><a href="#%EB%8B%A8%EC%A0%90-1" aria-label="단점 1 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>단점</h3> <ul> <li>Version Conflict 시, 처리해야 할 외부 요인이 존재함</li> </ul> <hr> <h2 id="성능-비교" style="position:relative;"><a href="#%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90" aria-label="성능 비교 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>성능 비교</h2> <blockquote> <p><Strong>비관적 락 &#x3C; 낙관적 락 </Strong></p> </blockquote> <ul> <li>낙관적 락은 트랜잭션이 필요하지 않기 때문에 성능적으로 우수함</li> <li>비관적 락은 데이터 자체에 락을 걸기 때문에 동시성이 떨어져 성능 저하가 발생하며, 서로의 자원이 필요할 경우에는 교착상태가 발생할 가능성 존재</li> </ul> <blockquote> <p><Strong>충돌이 많이 발생하는 환경</Strong></p> </blockquote> <p>충돌 발생 시, 비관적 락은 트랜잭션을 롤백하면 끝남. 하지만 낙관적 락은 까다로운 수동 롤백 처리와 성능 측면에서도 Update를 한번씩 더 해줘야 하기 떄문에, 성능 저하가 발생할 수 있음</p> <p><Strong> 데이터의 무결성 + 데이터의 충돌이 많이 발생할 것 같은 경우 -> 비관적 락 데이터 충돌이 적을 것 같은 경우 + 조회 작업이 많아 동시 접근 성능이 중요 -> 낙관적 락 </Strong></p>https://eeeasycode.dev/github-healthcheck-bot/https://eeeasycode.dev/github-healthcheck-bot/Wed, 27 Dec 2023 00:00:00 GMT<p>이번에 진행한 프로젝트에서 나는 CloudType을 사용하여 NestJS 기반 서버를 무료로 배포하였다. 무료로 서버를 배포할 수 있다는 것이 정말 큰 장점이었지만, CloudType의 무료 티어의 경우 <strong>연속 실행 제한</strong>이 걸려 매일 오전 3~9시 사이 프리티어로 구동 중인 서비스는 자동으로 중지 상태로 변경되는 문제가 존재한다.</p> <p><img src="https://velog.velcdn.com/images/eeeasy-code/post/227fe906-018a-41b5-92e3-25f37f0a37c8/image.png" alt=""></p> <p>사실, 정지되는 것은 문제가 되지 않았다. 웹 사이트에 접속하는 사용자의 수도 많지 않았고, 오전 3~9시 사이에 접속하는 사용자는 더욱 낮을 것이라고 판단했기 때문이다. 그럼에도 일단 언제 중지될지 모르는 서버와 꺼져있는 서버를 재가동시키는 것을 깜빡하는 경우에 소중한 사용자의 데이터가 저장되지 못하는 일이 발생했다. 프로젝트 배포에서 가장 중요했던 사용자의 구매량과 버튼 클릭 수를 DB 서버에 저장하고 있었기에 이는 우리에게 나름 크리티컬한 문제가 되었다.</p> <h2 id="대응-방안" style="position:relative;"><a href="#%EB%8C%80%EC%9D%91-%EB%B0%A9%EC%95%88" aria-label="대응 방안 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>대응 방안</h2> <p>일단 초기 대응 방안은 그저 9시 이후에 CloudType 프로젝트 관리창에서 꺼져있는 나의 서버를 다시 켰다. 그런데 가끔 9시 이후에도 잘 켜져있다가 이후에 꺼지는 경우도 있었던 것으로 기억한다. 그래서, 언제꺼질지 몰라 지속적으로 확인했었다. 이는 너무 귀찮기도 했고 짜증나기도 했다. 그렇게 점점 서버 재가동을 놓치는 시간이 많아졌고, 서버는 잠들어 있는 시간이 더 길어졌다.</p> <p>문제를 해결하기 위해, 아이디어를 구상해 보았다.</p> <blockquote> <ol> <li>서버 중단 시, 내부 로직에 중단되기 직전 메일 등의 알림 기능을 구현</li> <li>프론트엔드 단에서 서버의 응답이 정상적으로 오지않을 경우를 확인</li> <li>헬스체크 서버를 구현 후 배포하여 주기적으로 확인</li> </ol> </blockquote> <p>내 머리 속에 든 생각은 총 3가지였다. 우선 첫 번째 아이디어를 진행보았다. 서버 내부 메인 로직에 아래와 같은 코드를 넣어 서버가 다운될 시점에 로그 파일을 남길 수 있도록 진행했다.</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">process.on('exit', code => { console.log(code) logger.log({ level: 'error', message: 'exit' }) }) process.on('SIGINT', code => { console.log(code) logger.log({ level: 'error', message: 'sigint' }) })</code></pre></div> <p>하지만, 서버가 중단되고 재가동 후 확인해본 결과 로그 파일은 생성되지 않았다. 그 원인에 대해 아직도 명확한 해답을 찾지는 못했지만 스스로 생각해본 결과 배포 시스템이 중단되는 것과 내부 로직은 관련이 없고, AWS처럼 시스템 중단 시 발생하는 이벤트가 존재할텐데 이를 활용해야 할 것 같다는 생각이 들었다. CloudType 공식문서에는 따로 나와있는 내용이 없었다.</p> <p>두 번째 아이디어는 프론트엔드 단에서 내가 배포한 서버의 API를 호출했을 때, 응답이 정상적으로 오지않고 500 등의 에러를 응답할 경우에 알림을 보내는 기능을 생각해봤다. 사실 이 아이디어도 나름 깔끔하고 좋다고 생각했지만, 프론트엔드 개발자 분께서 본인의 코드를 건드는 것을 별로 좋아하지 않았고 그래서 일단 내가 스스로 해결해보려 노력해봤다.</p> <p>마지막 아이디어를 지금 채택해 사용 중인데, 초기에 생각했던 것은 헬스체크 서버에 기존 서버의 헬스체크 Api를 스케줄링하는 기능을 구현해 배포하려고 했다. 생각해보니 그럼 서버가 두 개나 띄워져있는 것인데 이는 낭비라고 생각이 들었다. 그래서 찾은 방법이 ** Github Action으로 헬스 체크**하는 것이었다. Github Action으로 스케줄링을 걸어놓고 특정 시간에 한 번씩 서버의 상태를 확인하고, 이를 Slack으로 알림을 전송하는 것을 생각했다.</p> <h2 id="Github-Action으로-헬스체크" style="position:relative;"><a href="#Github-Action%EC%9C%BC%EB%A1%9C-%ED%97%AC%EC%8A%A4%EC%B2%B4%ED%81%AC" aria-label="Github Action으로 헬스체크 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>Github Action으로 헬스체크</h2> <p>github action에 등록한 헬스체크 기능을 코드로 먼저 보겠다.</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">name: health check on: # 스케줄링을 설정함 / 매분마다 한 번씩 이벤트를 트리거함 schedule: - cron: '*/1 * * * *' # workflow_dispath는 수동으로 이벤트를 트리거할 수 있도록 해주는 것을 의미함 workflow_dispatch: jobs: healthcheck: runs-on: ubuntu-latest steps: # 지정한 서버에 대해 헬스 체크 진행 - name: Release API Health Check uses: jtalk/url-health-check-action@v3 with: github_token: ${{ secrets.GHP_TOKEN }} url: ${{ secrets.RELEASE_URI }} max-attempts: 3 # 시도 횟수 retry-delay: 1s # 시도 간격 # 트리거된 이벤트의 내용을 slack으로 전달 - name: action-slack uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} github_token: ${{ secrets.GHP_TOKEN }} author_name: Github Action Health Check fields: repo,message,commit,action,eventName,ref,workflow,job,took # 보낼 정보들 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEB_HOOK_URL }} if: always() # 특정 조건에 상관없이 항상 실행 </code></pre></div> <p>작성된 github action의 healthCheck 플로우는 다음과 같다. 이 후, slack의 incoming-webhook을 등록하여 진행하면 문제없이 서버의 healthCheck 기능이 정상적으로 작동된다. 덕분에 서버가 중단된 경우 Slack을 통해 알림을 받을 수 있게 되었고, 귀찮게 내가 한번씩 접속해서 서버가 중단되었는지 확인하지 않아도 알 수 있게 되어 너무 편하다.</p> <table> <thead> <tr> <th>서버 정상 작동</th> <th>서버 중단 시</th> </tr> </thead> <tbody> <tr> <td><img src="https://velog.velcdn.com/images/eeeasy-code/post/2ff2fd43-d7ec-4cb7-98b4-1618a3ac1b7b/image.png" alt=""></td> <td><img src="https://velog.velcdn.com/images/eeeasy-code/post/0db7fdfe-7582-4679-bd0c-eb87353f6925/image.png" alt=""></td> </tr> </tbody> </table> <p><img src="https://velog.velcdn.com/images/eeeasy-code/post/81ed5cf4-526b-4085-98c3-9d4c04d9a76e/image.png" alt=""></p> <h2 id="발생한-문제들" style="position:relative;"><a href="#%EB%B0%9C%EC%83%9D%ED%95%9C-%EB%AC%B8%EC%A0%9C%EB%93%A4" aria-label="발생한 문제들 permalink" class="heading-anchor before"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path d="M6.188 8.719c.439-.439.926-.801 1.444-1.087 2.887-1.591 6.589-.745 8.445 2.069l-2.246 2.245c-.644-1.469-2.243-2.305-3.834-1.949-.599.134-1.168.433-1.633.898l-4.304 4.306c-1.307 1.307-1.307 3.433 0 4.74 1.307 1.307 3.433 1.307 4.74 0l1.327-1.327c1.207.479 2.501.67 3.779.575l-2.929 2.929c-2.511 2.511-6.582 2.511-9.093 0s-2.511-6.582 0-9.093l4.304-4.306zm6.836-6.836l-2.929 2.929c1.277-.096 2.572.096 3.779.574l1.326-1.326c1.307-1.307 3.433-1.307 4.74 0 1.307 1.307 1.307 3.433 0 4.74l-4.305 4.305c-1.311 1.311-3.44 1.3-4.74 0-.303-.303-.564-.68-.727-1.051l-2.246 2.245c.236.358.481.667.796.982.812.812 1.846 1.417 3.036 1.704 1.542.371 3.194.166 4.613-.617.518-.286 1.005-.648 1.444-1.087l4.304-4.305c2.512-2.511 2.512-6.582.001-9.093-2.511-2.51-6.581-2.51-9.092 0z"/></svg></a>발생한 문제들</h2> <ol> <li>Github Action에서 발생하는 <code>"Resource not accessible by integration"</code> 문제 발생 -> github token을 넣어주면 해결 가능</li> <li><code>"The process '/usr/bin/git' failed with exit code 1"</code> 문제 발생 -> healthCheck할 서버의 url이 정상적인 응답을 보내는지 확인 후 설정</li> </ol>