<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Eagle&amp;rsquo;s dev insights</title>
    <link>https://eagle25.tistory.com/</link>
    <description>개발 인사이트를 정리하는 공간입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 10 May 2026 14:10:39 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>eagle25</managingEditor>
    <image>
      <title>Eagle&amp;rsquo;s dev insights</title>
      <url>https://tistory1.daumcdn.net/tistory/5790752/attach/9b4d39594dee4e9b8cc75cb072c30d23</url>
      <link>https://eagle25.tistory.com</link>
    </image>
    <item>
      <title>커뮤니케이션을 줄이는 코드리뷰 팁 6가지</title>
      <link>https://eagle25.tistory.com/16</link>
      <description>&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rAo4p/btsM0vsPyMb/BYLLsosobEKwNs5nC3uXsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rAo4p/btsM0vsPyMb/BYLLsosobEKwNs5nC3uXsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rAo4p/btsM0vsPyMb/BYLLsosobEKwNs5nC3uXsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrAo4p%2FbtsM0vsPyMb%2FBYLLsosobEKwNs5nC3uXsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;이번 글은 커뮤니케이션을 줄이는 코드리뷰 팁 6가지를 소개합니다.&lt;br&gt;&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;글을 읽으시면 코드리뷰에서 커뮤니케이션을 줄이는 구체적인 팁들을 얻어가실 수 있고, 복잡한 내용을 쉽게 설명하는 방법도 얻어가실 수 있습니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&amp;nbsp;&lt;br&gt;이번 글은 제게 조금 특별한데요, 제가 &lt;a href=&quot;https://github.com/datahub-project/datahub&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;오픈소스&lt;/span&gt;&lt;/a&gt;에 &lt;a href=&quot;https://github.com/datahub-project/datahub/commits/master/?author=eagle-25&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;기여&lt;/span&gt;&lt;/a&gt;한 실제 사례를 예시 자료로 사용했기 때문입니다. 실제 사례를 활용한 만큼, 여러분들이 내용을 이해하시는데 도움 되길 바라봅니다!&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&lt;/b&gt;&lt;b&gt;PR은 한 가지 목적만 포함하라&lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;: 리뷰어의 혼란 방지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. 부연 설명을 미리&amp;nbsp; 남겨라&lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;: 핑퐁 최소화&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&lt;b&gt;3. QnA 포맷으로&amp;nbsp; 설명하라&lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;: 장황한 줄글은 그만&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4. 테스트로 동작을 설명하라&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&lt;b&gt;5. 기존 로직을 재활용하라&lt;/b&gt;&lt;br&gt;&lt;b&gt;6. 사소한 문제를 피하라:&lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;발목 잡히지 말라&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;커뮤니케이션을 줄이는 것은 중요합니다. 특히 개발자라면 코드리뷰에서 다른 개발자와 소통하게 되는데요, 이때 커뮤니케이션이 많이 발생하면 일의 진행 속도를 느리게 하고, 나중에 원하는 정보를 찾기 어려워집니다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그렇다면 어떻게 코드리뷰에서 커뮤니케이션을 줄일 수 있을까요? 이어지는 내용에서 구체적인 방법을 소개합니다!&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. PR은 한 가지 목적만 포함하라&lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;: 리뷰어의 혼란 방지&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;Pull Request(PR)은 한 가지 목적만을 담는 게 좋습니다. &lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Fig.1은 서로 다른 성격의 작업이 3개 담겨있는 PR입니다. (기능 수정, 리팩터링, 기능 추가) 여러분이 리뷰어라면 어떻게 느껴지시나요?&amp;nbsp; 저는 무슨 목적으로 코드를 수정했는지 파악이 어렵고, 목적이 여러 개라 혼란스럽게 느껴집니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;509&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5bxl4/btsM0KwriII/zyws5vVzo3VZBKLHKklwJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5bxl4/btsM0KwriII/zyws5vVzo3VZBKLHKklwJk/img.png&quot; data-alt=&quot;Fig.1, 여러가지 목적이 혼재하는 코드 수정.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5bxl4/btsM0KwriII/zyws5vVzo3VZBKLHKklwJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5bxl4%2FbtsM0KwriII%2Fzyws5vVzo3VZBKLHKklwJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;509&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;509&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fig.1, 여러가지 목적이 혼재하는 코드 수정.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다른 관점에서 생각해 보겠습니다. eagle-25라는 동료가 여러분이 올린&amp;nbsp; PR에 질문을 남겼습니다. 무엇이 문제일까요?&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;문제는 바로, 대화가 여러 가지 주제로 산발적이게 이뤄진다는 것입니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;955&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfIoIs/btsM1MtwAj7/neoY9IjLRGpGkAyW8LuNkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfIoIs/btsM1MtwAj7/neoY9IjLRGpGkAyW8LuNkK/img.png&quot; data-alt=&quot;Fig.2, 산발적으로 대화로 이어지는 질문들.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfIoIs/btsM1MtwAj7/neoY9IjLRGpGkAyW8LuNkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfIoIs%2FbtsM1MtwAj7%2FneoY9IjLRGpGkAyW8LuNkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;617&quot; height=&quot;955&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;955&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fig.2, 산발적으로 대화로 이어지는 질문들.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;산발적으로 이뤄지는 대화가 왜 문제일까요?&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Fig.3에서 대화의 맥락을 보면 2, 3번 수정은 더 이상 리뷰가 필요하지 않습니다. 하지만 PR을 머지하려면 1번 수정이 문제없는지 확인이 끝나야 합니다. 다르게 말하면&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;u&gt;1번 때문에 2, 3번 수정이 배포되지 못하는 상황&lt;/u&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;인 것입니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;u&gt;&lt;br&gt;&lt;/u&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;또 다른 문제가 있는데, 코드리뷰를 어렵게 한다는 것입니다. 여러 가지 주제로 대화하다 보면, 이해 관계자가 많아지고, 리뷰 시 고려할 요소가 많아집니다. Fig.3을 보면 벌써 PR 하나에서 abc팀과, xyz팀이 등장하고 있습니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;919&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdJxC0/btsM22vEcxA/pkYXtG8Y2nTdKOGPFXeiCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdJxC0/btsM22vEcxA/pkYXtG8Y2nTdKOGPFXeiCk/img.png&quot; data-alt=&quot;Fig.3, 산발적으로 이뤄지는 대화.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdJxC0/btsM22vEcxA/pkYXtG8Y2nTdKOGPFXeiCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdJxC0%2FbtsM22vEcxA%2FpkYXtG8Y2nTdKOGPFXeiCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;541&quot; height=&quot;919&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;919&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fig.3, 산발적으로 이뤄지는 대화.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그렇다면 수정 1, 2, 3을 각각 별개의 PR로 분리했다면 어땠을까요? 먼저 2, 3번을 각각 다른 PR로 분리하고 살펴보겠습니다. (Fig.4)&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기존 PR과 비교했을 때, 두 가지&amp;nbsp;&amp;nbsp;장점이 드러납니다. &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;b&gt;수정 목적이 분명해집니다.&lt;/b&gt; 기존&amp;nbsp; PR의 커밋 메시지는 &quot;DB 코드 수정&quot;으로 두루뭉술했다면, 분리된 PR의 커밋 메시지에는 PR의 목적을 분명하게 드러내 리뷰어의 혼란을 줄여줍니다. (e.g., feat: DB().delete() 메서드 추가). &lt;br&gt;&lt;br&gt;&lt;b&gt;리뷰가 빠르게 진행됩니다&lt;/b&gt;. 분리된 PR은 기존 PR 대비 수정된 코드가 훨씬 적습니다. 이는 리뷰어가 각 PR에서 리뷰할 변경이 작아졌기 때문에, approve를 상대적으로 빠르게 주는 결과로 이어집니다.&lt;/p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ocYjI/btsM3jxbtSW/f5dImEil8TznrCg8pWFKL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ocYjI/btsM3jxbtSW/f5dImEil8TznrCg8pWFKL0/img.png&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;611&quot; style=&quot;width: 48.8338%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ocYjI/btsM3jxbtSW/f5dImEil8TznrCg8pWFKL0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FocYjI%2FbtsM3jxbtSW%2Ff5dImEil8TznrCg8pWFKL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;611&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5CwBs/btsM16SQtCO/i78a8kxjHQRSjhkKgGAoD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5CwBs/btsM16SQtCO/i78a8kxjHQRSjhkKgGAoD0/img.png&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;600&quot; style=&quot;width: 50.0034%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5CwBs/btsM16SQtCO/i78a8kxjHQRSjhkKgGAoD0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5CwBs%2FbtsM16SQtCO%2Fi78a8kxjHQRSjhkKgGAoD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;547&quot; height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Fig.4, PR 분리로 내용과 대화가 간소화된 예시.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;목적이 다른 수정은 번거롭더라도 PR을 분리하면, &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;수정 목적을 명확&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하게 해 리뷰어의 혼란을 줄이고&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;,&amp;nbsp; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;각 PR에서 리뷰할 내용이 작아져 리뷰가 빠르게 진행됩니다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다만, 아래처럼 예외를 둘 수도 있으니, 상황에 맞게 PR을 분리하시면 더 좋습니다.&lt;/span&gt;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Q: 목적에는 관계없지만, 아주 간단한 수정도 꼭 분리를 해야 하나요? (e.g., 한 줄짜리 typo 수정)&lt;br&gt;A: 추가적인 커뮤니케이션이 필요 없다면 분리하지 않아도 됩니다.&lt;/blockquote&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 부연 설명을 미리&amp;nbsp; 남겨라&lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;: 핑퐁 최소화&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;코드리뷰를 받다 보면 종종 이런 질문을 받습니다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 메서드는 왜 추가하셨죠?&amp;nbsp;&lt;br&gt;이 로직을 제거하신 이유는 무엇인가요?&amp;nbsp;&lt;br&gt;...&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이들 질문의 공통점은 추가적인 맥락이 없다면 의도를 알기 어려운 내용을 질문하고 있다는 것입니다. 때문에 리뷰를 요청하기 전, 부연설명을 미리 남겨두면 커뮤니케이션을 줄일 수 있습니다.&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;실제 예시를 통해 필요성을 살펴보겠습니다. 아래 사진(Fig.5)은 코드를 수정하면서 기존에 있던 정렬 로직(-L871)을 제거한 모습입니다. 코드를 본 적 있는 리뷰어라 하더라도, 멀쩡한 정렬 로직을 지우는 것으로 느껴질 수 있어 &quot;왜 지운거지?&quot; 하는 의문이 들 수 있습니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;뿐만 아니라, 리뷰어는 로직을 지운 이유에 대한 설명을 요청할 가능성이 높습니다. 이는 곧 커뮤니케이션 증가로 이어지기 때문에 부연 설명을 미리 추가하는 게 좋습니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SlHWP/btsLUpMxJvV/UH3siFWFZVpzsbn3QGq9Y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SlHWP/btsLUpMxJvV/UH3siFWFZVpzsbn3QGq9Y0/img.png&quot; data-alt=&quot;Fig.5, 설명이 없어, 정렬 로직을 알기 어려운 예시.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SlHWP/btsLUpMxJvV/UH3siFWFZVpzsbn3QGq9Y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSlHWP%2FbtsLUpMxJvV%2FUH3siFWFZVpzsbn3QGq9Y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;859&quot; height=&quot;333&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fig.5, 설명이 없어, 정렬 로직을 알기 어려운 예시.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br&gt;그렇다면 부&lt;span style=&quot;color: #333333;&quot;&gt;연 설명을 추가하고 코드를 다시 살펴보겠습니다. 사진에서 eagle-25(PR 작성자)는 정렬 로직을 의도적으로 지웠으며, 그 이유를 설명하고 있습니다. 덕분에 리뷰어는 로직을 지운 이유를 물어보는 대신, 제거를 동의하면서 다른 동료에게 corss-check를 요청하는 방향으로&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;일이 빠르게 진행되는 모습&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;을 보입니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0MDxX/btsLSAa97UN/k4zX7PkmBWJk7kQdJP2fnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0MDxX/btsLSAa97UN/k4zX7PkmBWJk7kQdJP2fnK/img.png&quot; data-alt=&quot;Fig.6, 정렬 로직을 지운 이유에 대한 설명이 남겨진 예시.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0MDxX/btsLSAa97UN/k4zX7PkmBWJk7kQdJP2fnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0MDxX%2FbtsLSAa97UN%2Fk4zX7PkmBWJk7kQdJP2fnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;878&quot; height=&quot;718&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fig.6, 정렬 로직을 지운 이유에 대한 설명이 남겨진 예시.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;질문을 받을 여지가 있거나, 맥락을 쉽게 알기 어려운 수정에는 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;부연설명을 미리 남겨놓는 게 좋습니다&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;. 리뷰어는 궁금한 내용에 대한 답이 이미 달려있기 때문에 질문할 필요가 없어져 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;리뷰가 빠르게 진행됩니다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;코드리뷰를 요청하기 전, 부연설명이 필요한 부분은 없는지 살펴보기를 바랍니다.&lt;/span&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. QnA 포맷으로&amp;nbsp; 설명하라&lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;: 장황한 줄글은 그만&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;타인에게 무언가를 설명할 때는 가능한 간결한 게 좋습니다. 하지만 설명할 내용이 복잡하다면 간결하게 글을 쓰기란 쉽지 않습니다. 저는 이때 QnA 포맷을 활용해 설명을 하곤 합니다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Fig.7은 어떤 코드를 추가하게 된 배경을 설명하기 위해 작성한 글의 첫 번째 버전입니다. 어떠신가요? 제가 글에서 무엇을 말하고자 하는지 한눈에 들어오시나요? 제가 쓴 글이지만 저마저도 무엇을 말하려는지 한눈에 들어오지 않습니다.  &lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ7g3T/btsLUVxAUvD/pUs04ixRAufpyxG4EyCDCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ7g3T/btsLUVxAUvD/pUs04ixRAufpyxG4EyCDCk/img.png&quot; data-alt=&quot;Fig.7, 복잡한 내용을 장황한 줄글로 설명하는 예시.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ7g3T/btsLUVxAUvD/pUs04ixRAufpyxG4EyCDCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ7g3T%2FbtsLUVxAUvD%2FpUs04ixRAufpyxG4EyCDCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;192&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fig.7, 복잡한 내용을 장황한 줄글로 설명하는 예시.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Fig.8은 Fig.7의 내용을 QnA 포맷으로 작성했습니다. Fig.8은 이전과 달리, QnA 포맷으로 작성되어 무엇을 말하려는지 쉽게 알 수 있습니다. 설명하려는 내용이 각각 Q로 분리되어 있기 때문에 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;독자들은 원하는 정보만 취사선택&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;할 수 있습니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Oa7SW/btsLUTNh6ds/vCRGZkN68CM51KxGoGW750/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Oa7SW/btsLUTNh6ds/vCRGZkN68CM51KxGoGW750/img.png&quot; data-alt=&quot;Fig.8, 줄글로 설명하던 내용을 QnA로 작성한 모습.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Oa7SW/btsLUTNh6ds/vCRGZkN68CM51KxGoGW750/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOa7SW%2FbtsLUTNh6ds%2FvCRGZkN68CM51KxGoGW750%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;795&quot; height=&quot;387&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fig.8, 줄글로 설명하던 내용을 QnA로 작성한 모습.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;복잡한 내용을 글로 설명하기란 쉽지 않습니다. 이때 QnA 포맷을 사용하면 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;글이 구조적인 형태를 띠게 되어 가독성이 향상&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;되고, 독자가 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;원하는 정보만 선택&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;해 글을 읽을&amp;nbsp;수 있도록 배려할 수 있습니다.&lt;/span&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4. 테스트로 동작을 설명하라&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;때로는 장황한 글보다 테스트 코드 몇 줄이 코드의 동작을 효과적으로 설명합니다. 테스트 코드 자체의 이점은 이미 인터넷에 잘 설명되어 있어서 다루지 않겠습니다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다만, 테스트 코드를 활용하면&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;기존 동작과, 변경된 동작을 쉽게 설명&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;할 수 있다는 걸 말씀드리고 싶습니다. &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Fig.9에서 assert 문에 바뀐 코드의 동작을, 댓글에는 assert문과 비슷한 형태로 기존 동작을 표현하고 있습니다. 오픈소스 리뷰어인 sgomezvillarmor는 동작의 차이를 쉽게 이해하고 `nice catch` 라는 댓글을 남겼습니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHdLYK/btsMWUZagNi/KF3zsy3JXS7eKi5DyZUHak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHdLYK/btsMWUZagNi/KF3zsy3JXS7eKi5DyZUHak/img.png&quot; data-alt=&quot;Fig.9 기존 동작/변경된 동작을 테스트코드로 설명하는 예시.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHdLYK/btsMWUZagNi/KF3zsy3JXS7eKi5DyZUHak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHdLYK%2FbtsMWUZagNi%2FKF3zsy3JXS7eKi5DyZUHak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;608&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fig.9 기존 동작/변경된 동작을 테스트코드로 설명하는 예시.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;테스트코드를 추가하면 &lt;b&gt;기존 동작과, 변경된 동작을 쉽게 설명&lt;/b&gt;할 수 있습니다.&amp;nbsp;장황한 글 대신 테스트 코드로 동작을 설명해 보시는 건 어떠신가요?&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 기존 로직을 재활용하라&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;새로운 로직을 추가하기 전, 이미 기존 코드에 있는 로직은 아닌지 살펴보는 게 좋습니다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;실제 사례를 살펴보겠습니다. (Fig.10)&lt;/span&gt;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;리뷰어인 treff7es는 추가한 메서드(_get_table_name)가 기존에 있는 메서드와 어떻게 다른지 질문합니다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;리뷰이인 eagle-25는 두 메서드가 다르지 않기 때문에, 추가한 메서드를 지우겠다고 합니다.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 사례는 추가하려는 로직이 이미 존재하는지 검토하지 않았을 때 발생하는 두 가지 문제를 보여줍니다. 바로 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;불&lt;/b&gt;&lt;/span&gt;&lt;b&gt;필요한 코드 작성에 시간을 사용&lt;/b&gt;하게 되고, &lt;b&gt;불필요한 커뮤니케이션이 추가&lt;/b&gt;된다는 것입니다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;723&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NvtcU/btsMXwwGbW4/wqSYbSpdLR5Lgk8hlS5Le0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NvtcU/btsMXwwGbW4/wqSYbSpdLR5Lgk8hlS5Le0/img.png&quot; data-alt=&quot;Fig.10, 리뷰어가 기존 로직과, 추가하는 로직이 어떻게 다른지 질문하는 예시.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NvtcU/btsMXwwGbW4/wqSYbSpdLR5Lgk8hlS5Le0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNvtcU%2FbtsMXwwGbW4%2FwqSYbSpdLR5Lgk8hlS5Le0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;723&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;723&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fig.10, 리뷰어가 기존 로직과, 추가하는 로직이 어떻게 다른지 질문하는 예시.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;새로운 로직을 추가하기 전,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;기존 로직을 재활용할 수 있는지 먼저 검토&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하시면, 소중한 시간과 커뮤니케이션 비용을 아낄 수 있습니다.&lt;/span&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 사소한 문제를 피하라:&lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt; 발목 잡히지 말라&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;리뷰를 요청하기 전, 사소한 문제가 없는지 한번 더 확인하는 게 좋습니다. 예시로는 &lt;/span&gt;lint 문제와, CI 실패(e.g., 테스트 실패)가 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래 PR들은 변경에 대한 승인은 받았지만, 사소한 문제 때문에 PR 병합이 지연된 모습을 보여줍니다.&lt;/span&gt;&lt;/p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mdqLU/btsMW8CSgwn/KODHkHa0mWFPyoOpxqUOr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mdqLU/btsMW8CSgwn/KODHkHa0mWFPyoOpxqUOr1/img.png&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;716&quot; style=&quot;width: 49.9854%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mdqLU/btsMW8CSgwn/KODHkHa0mWFPyoOpxqUOr1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmdqLU%2FbtsMW8CSgwn%2FKODHkHa0mWFPyoOpxqUOr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ULtk4/btsMWISaLyc/Blk0crQNxe4NnxiaMPc0Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ULtk4/btsMWISaLyc/Blk0crQNxe4NnxiaMPc0Sk/img.png&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;752&quot; style=&quot;width: 48.8519%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ULtk4/btsMWISaLyc/Blk0crQNxe4NnxiaMPc0Sk/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FULtk4%2FbtsMWISaLyc%2FBlk0crQNxe4NnxiaMPc0Sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Fig.11, 사소한 문제로 발목잡힌 예시. (좌): lint 문제, (우): test 문제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;test, lint 등의 사소한 문제를 미리 확인&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;한다면 발목 잡히는 일을 예방할 수 있습니다. 또한 리뷰를 다시 해야 하는 리뷰어와 리뷰를 다시 요청해야 하는 리뷰이의 모두의 시간을 아낄 수 있기 때문에 꼭 확인하시기를 바랍니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot;&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이번 글에서는 커뮤니케이션 최소화에 중점을 둔 코드리뷰 팁 &amp;nbsp;6가지를 살펴보았습니다. 실제로 제가 오픈소스에 기여하면서 있었던 사례들과 함께 코드리뷰 기술들의 효과를 설명했는데, 이해하시는데 도움 되었길 바랍니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;저는 소프트웨어 엔지니어로 커리어를 막 시작했을 때, 코드리뷰에서 많은 어려움을 겪었습니다. 과거의 저처럼 코드리뷰에 익숙하지 않은 분들이 이 글을 읽고, 동료와 함께 시너지를 내는데 도움 되길 바라며 글을 마칩니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;긴 글 읽어주셔서 감사합니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;b&gt;정리&lt;/b&gt;&lt;/h4&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;PR은 한 가지 목적을 담자. 산발적인 대화를 줄이고, 리뷰어의 혼란을 방지할 수 있다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;부연 설명을 미리 남겨두자. 질문받을 가능성이 높은 곳에 설명을 남겨두면 커뮤니케이션을 줄일 수 있다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;복잡한 내용을 설명할 때는 QnA 포맷을 활용해 보자. 독자가 원하는 정보를 취사선택해 읽도록 하도록 배려할 수 있다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기존 / 변경된 코드의 동작을 설명할 때는 테스트 코드를 활용하자. 줄글보다 쉽게 내용을 전달할 수 있다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;새로운 로직을 추가하기 전, 기존 로직을 재활용할 수 있는지 살펴보자. 소중한 시간과 커뮤니케이션 비용을 절약할 수 있다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;리뷰를 요청하기 전, 발목을 잡는 사소한 문제(e.g., test, lint)가 없는지 확인하자.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;</description>
      <category>Programming</category>
      <category>github</category>
      <category>PR</category>
      <category>코드리뷰</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/16</guid>
      <comments>https://eagle25.tistory.com/16#entry16comment</comments>
      <pubDate>Mon, 31 Mar 2025 01:08:58 +0900</pubDate>
    </item>
    <item>
      <title>DynamoDB 성능 문제 빠르게 해결하기</title>
      <link>https://eagle25.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 GSI 동작과 유사한 인덱스 테이블  추가로 조회 성능 문제를 빠르게 해결한 방법에 대해 소개하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;lt;3줄 요약&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스 없는 컬럼 스캔으로 상품 라이브 직후 장애 발생.&lt;/li&gt;
&lt;li&gt;GSI 추가는 10시간이 걸리는 문제가 있었음.&lt;/li&gt;
&lt;li&gt;보조테이블 추가하고 GSI 처럼 사용해 문제를 빠르게 해결함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 DynamoDB 를 사용하는 api의 조회 성능에 문제가 발생했다. 제품 라이브중에 발생한 이슈라 빠른 해결이 필요한 상황이었다. 인덱스 없는 컬럼을 스캔하다 발생한 문제라, 처음엔 단순하게 GSI 추가를 생각했다. 그러나 해당 api가 사용하는 테이블은 400만개의 row가 있어 GSI 추가에 오랜 시간이 걸리는 문제가 있었다. 장애 상황인지라 빠르게 문제를 해결해야 하는데 GSI 추가는 이런 상황과 상충되는 해결책이었다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;문제 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 상품 라이브 이후에 발생하기 시작했다. 회사의 DynamoDB 테이블은 광고주가 우리측 서버로 전송한 이벤트를 쌓게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;광고주 서버 --&amp;gt; 우리회사 서버 --&amp;gt; DynamoDB 테이블&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애가 발생된 API는 Dynamo테이블에서 인덱스가 없는 컬럼을 scan해 원하는 데이터를 찾도록 설계했다. 광고주가 보내는 데이터의 규모가 많지 않을것이라 판단했기 때문이다. 그리고 이 판단은 장애로 이어지는 시발점이 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2076&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uNlHU/btsFTewNcGw/eVrqGJn3kbO5gvV6QsRxI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uNlHU/btsFTewNcGw/eVrqGJn3kbO5gvV6QsRxI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uNlHU/btsFTewNcGw/eVrqGJn3kbO5gvV6QsRxI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuNlHU%2FbtsFTewNcGw%2FeVrqGJn3kbO5gvV6QsRxI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;518&quot; data-origin-width=&quot;2076&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[기존]&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 라이브 전에는 광고주가 보내는 데이터가 많지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;990&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGvDo1/btsFQtWhUtd/vkgRcTmTPM5xxwYjkIbHsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGvDo1/btsFQtWhUtd/vkgRcTmTPM5xxwYjkIbHsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGvDo1/btsFQtWhUtd/vkgRcTmTPM5xxwYjkIbHsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGvDo1%2FbtsFQtWhUtd%2FvkgRcTmTPM5xxwYjkIbHsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;274&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;990&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[라이브 이후]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;광고주 측에서 데이터를 많이 보내기 시작했다. 광고주의 앱 런칭 시점에 맞춰 제품 라이브를 시작했기 때문에, 사용자가 늘어남에 따라 광고주가 보내는 이벤트가 많아졌다. 데이터가 많은 상황에서 scan을 시도했기 때문에 성능 문제가 발생했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdwO3x/btsFS2J3Fxh/4dIrGNFkxuOkrgjGvutyI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdwO3x/btsFS2J3Fxh/4dIrGNFkxuOkrgjGvutyI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdwO3x/btsFS2J3Fxh/4dIrGNFkxuOkrgjGvutyI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdwO3x%2FbtsFS2J3Fxh%2F4dIrGNFkxuOkrgjGvutyI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;426&quot; height=&quot;1026&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;해결책&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 테이블과 동일한 구조의 테이블을 만들었다. 그리고 두 테이블에 동일한 데이터가 쌓이도록 했다. 이전에 쌓인 데이터를 사용해야 할 필요는 없었기 때문에 데이터 migration은 진행하지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cI9MR0/btsFUpR7VYp/ShRpgRF7Fnd0jKhxT81xW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cI9MR0/btsFUpR7VYp/ShRpgRF7Fnd0jKhxT81xW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cI9MR0/btsFUpR7VYp/ShRpgRF7Fnd0jKhxT81xW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI9MR0%2FbtsFUpR7VYp%2FShRpgRF7Fnd0jKhxT81xW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;332&quot; height=&quot;149&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로만든 테이블을 v2 테이블이라고 하자. v2 테이블의 hash key는 scan시 사용한 컬럼이 되도록 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2066&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BV0Vm/btsFQxYEK0z/u4sPOrRmscgZ3MorUxBzvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BV0Vm/btsFQxYEK0z/u4sPOrRmscgZ3MorUxBzvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BV0Vm/btsFQxYEK0z/u4sPOrRmscgZ3MorUxBzvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBV0Vm%2FbtsFQxYEK0z%2Fu4sPOrRmscgZ3MorUxBzvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;107&quot; data-origin-width=&quot;2066&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5D4J5/btsFR8wPpwR/fhr4K4sSRQ08yoYr0qAsSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5D4J5/btsFR8wPpwR/fhr4K4sSRQ08yoYr0qAsSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5D4J5/btsFR8wPpwR/fhr4K4sSRQ08yoYr0qAsSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5D4J5%2FbtsFR8wPpwR%2Ffhr4K4sSRQ08yoYr0qAsSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;124&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 코드에선 사용자_id를 사용해 검색해야 할때, 아닐때를 구분하여 적절한 테이블을 사용하도록 하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1710746538086&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class V1Table(Model):
   owner_id # hash_key
   user_id
   
class V2Table(Model):
   owner_id 
   user_id  # hash_key
   
   
class EventRepository():
	def get_events_by_owner_id(id: int):
       return V1Table.query(hash_key=id)
       
	def get_events_by_user_id(id: int):
       return V2Table.query(hash_key=id)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;고려사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 개의 테이블에 데이터를 쌓기 때문에 WCU를 두 배로 사용하게 되는데, 불필요한 비용이 발생하는것 아닌가? 라는 의구심이 들 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yuYVm/btsFS2wxyHc/Gev0fHJkqUrAqXPKObqg20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yuYVm/btsFS2wxyHc/Gev0fHJkqUrAqXPKObqg20/img.png&quot; data-alt=&quot;원래 테이블이 WCU 100개가 필요하다면 V2 테이블도 WCU 100개가 필요하다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yuYVm/btsFS2wxyHc/Gev0fHJkqUrAqXPKObqg20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyuYVm%2FbtsFS2wxyHc%2FGev0fHJkqUrAqXPKObqg20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;442&quot; height=&quot;176&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;원래 테이블이 WCU 100개가 필요하다면 V2 테이블도 WCU 100개가 필요하다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그러나 답은 그렇지 않다 이다&lt;/b&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 GSI 데이터 구조를 살펴보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱싱에 사용할 컬럼을 hash_key로 가진다.&lt;/li&gt;
&lt;li&gt;나머지 부수적인 값(광고주_id, 이벤트 정보)는 projected attributes라고 부르며 필요에 따라 추가하지 않을수있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2268&quot; data-origin-height=&quot;1296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTYrGZ/btsFUFOaXpl/xynGKyly9V0wjVulZ7lSm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTYrGZ/btsFUFOaXpl/xynGKyly9V0wjVulZ7lSm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTYrGZ/btsFUFOaXpl/xynGKyly9V0wjVulZ7lSm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTYrGZ%2FbtsFUFOaXpl%2FxynGKyly9V0wjVulZ7lSm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;1296&quot; data-origin-width=&quot;2268&quot; data-origin-height=&quot;1296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 추가한 인덱스 테이블과 상당히 유사하지 않은가?&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5D4J5/btsFR8wPpwR/fhr4K4sSRQ08yoYr0qAsSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5D4J5/btsFR8wPpwR/fhr4K4sSRQ08yoYr0qAsSK/img.png&quot; data-alt=&quot;인덱스 테이블&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5D4J5/btsFR8wPpwR/fhr4K4sSRQ08yoYr0qAsSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5D4J5%2FbtsFR8wPpwR%2Ffhr4K4sSRQ08yoYr0qAsSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;143&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;인덱스 테이블&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 WCU 할당에 대해 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GSI를 사용하려면 GSI 전용 rcu/wcu를 할당해줘야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The provisioned throughput settings of a global secondary index are separate from those of its base table.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS는 테이블이 할당받은 wcu 이상을 gsi에 할당해주기를 권장한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;To avoid potential throttling, the provisioned write capacity for a global secondary index should be equal or greater than the write capacity of the base table because new updates write to both the base table and global secondary index.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;결국 gsi가 사용하는 wcu는 최소 두 배가 되는데, 이는 인덱스 테이블에 데이터를 추가하기 위해 wcu를 할당하는것과 차이가 없다고 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;배운점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;scan operation 사용은 RCU를 지나치게 많이 사용할 수 있어 주의해야 한다.&lt;/li&gt;
&lt;li&gt;GSI 생성에 시간이 오래걸리는것은 기존 데이터를 gsi로 migration 하기 때문이다.&lt;/li&gt;
&lt;li&gt;Projected Attributes를 필요한 컬럼만 설정해 GSI에서 소모되는 WCU를 줄일 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.justeattakeaway.com/2014/03/19/using-dynamodb-global-secondary-indexes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Provisioned throughput considerations for Global Secondary Indexes - Tech Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.ThroughputConsiderations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Using DynamoDB Global Secondary Indexes - AWS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Trouble Shooting/DB</category>
      <category>AWS</category>
      <category>dynamodb</category>
      <category>gsi</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/14</guid>
      <comments>https://eagle25.tistory.com/14#entry14comment</comments>
      <pubDate>Mon, 18 Mar 2024 17:35:59 +0900</pubDate>
    </item>
    <item>
      <title>DynamoDB In 조건의 함정</title>
      <link>https://eagle25.tistory.com/13</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DynamoDB scan하는 로직에서 아래와 같은 에러가 발생했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;An error occurred (ValidationException) on request (XXXX) on table prod_XXXX ... 중략 ... The IN operator is provided with too many operands; number of operands: 101&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링 해본 결과&lt;b&gt; DynamoDB in operation에는 제약 조건이 있음을 알게 되었다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The IN operator is provided with too many operands; number of operands: 101&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html#limits-expression-parameters&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS 문서 내용&lt;/a&gt;에는 다음과 같이 작성되어 있다.&lt;b&gt; 즉, IN operation에는 값을 100개까지만 지정할 수 있다는 것이다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The maximum number of operands for the IN&amp;nbsp;comparator is 100.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scan 로직은 아래처럼 작성되 있는데, &lt;b&gt;values에 값이 101개가 지정되어 상기 에러가 발생했다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710064912053&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;filter_condition = Table1.attr1.is_in(values)

result = Table1.user_id_index.query(
    hash_key=viewer_id,
    range_key_condition=range_key_condition,
    filter_condition=filter_condition
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. in 조건에 지정되는 값 줄이기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 근본적인 방법이라 할 수 있겠다. in 조건에 들어가는 데이터를 줄이면 error도 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. 안전장치 추가하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직상 100개가 넘는 상황이 발생할수도 있다. 이 경우 쿼리는 가능하도록 하되, warning log가 남도록 처리했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 로직은 아래처럼 값들을 100개씩 쪼개 in expression으로 만드는것이다. 그러면 100개 제한 조건을 우회할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;values = (1... 400) --&amp;gt; where attr1 in (1 .. 100) or attr1 in (101..200) ...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in 조건 지정시에는 아래 코드처럼 특정한 메서드를 사용해 위 동작을 보장하게 하였다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1710065570109&quot; class=&quot;sql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;filter_condition = get_safe_in_expression(Table1.attr1, values)

result = Table1.user_id_index.query(
    hash_key=viewer_id,
    range_key_condition=range_key_condition,
    filter_condition=filter_condition
)

def get_safe_in_expression(attr: Attribute, values: tuple) -&amp;gt; Condition:
    &quot;&quot;&quot;
    in expression에 100개 이상의 값을 지정하는 filter expression을 생성한다.
    
    @param attr: in expression 을 지정할 attribute
    @param values: in expression 에 포함될 값들
    @return: values의 개수에 따라 in expression을 여러개의 or expression으로 나누어서 반환
    &quot;&quot;&quot;
    CHUNK_SIZE = 100

    chunks = [values[i : i + CHUNK_SIZE] for i in range(0, len(values), CHUNK_SIZE)]
    filter_expr: Condition = attr.is_in(*chunks[0])
    for chunk in chunks[1:]:
        filter_expr |= attr.is_in(*chunk)
    if len(chunks) &amp;gt; 0:
        logger.warning(f'[in_expression_warning]: values count exceeded 100. / actual values count: {len(values)}')
    return filter_expr&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;확인 해봐야 할 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;100개 제한을 Quota로 풀 수 있을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;없다. AWS service quota console에서 확인해봤는데 in 조건과 관련된 내용은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성능에 영향 없을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서는 괜히 100개 제한을 걸지 않았을 것이다. DynamoDB의 latency나 throughput 성능에 어떤 영향이 있는지 측정해 보아야 한다. 필자는 성능 이슈로 제한을 걸었을 확률이 크다고 보는데, 진짜 성능 문제가 없는지 확인해봐야 할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 실험 내용 추가한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실험 환경
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;1.5만개 데이터가 있는 dynamoDB 테이블&lt;/li&gt;
&lt;li&gt;1000개 RCU 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;실험 방법
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;특정 데이터를 full scan으로 찾는다.&lt;/li&gt;
&lt;li&gt;이때, in 조건에 지정되는 값을 100개 지정하고 쿼리를 10회 수행하며 소요 시간을 기록한다.&lt;/li&gt;
&lt;li&gt;값을 200개, 300개, 400개 추가하며 실행시간을 기록한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결과
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;아래 차트와 같이 성능이 측정되었다.&lt;/li&gt;
&lt;li&gt;in 조건에 value 갯수가 많이 추가되면 latency는 일부 증가하나, 치명적인 수준은 아님을 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2384&quot; data-origin-height=&quot;1444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mktar/btsFQtB4Ed7/cwygwlAUKZIM18JQFmgJZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mktar/btsFQtB4Ed7/cwygwlAUKZIM18JQFmgJZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mktar/btsFQtB4Ed7/cwygwlAUKZIM18JQFmgJZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMktar%2FbtsFQtB4Ed7%2FcwygwlAUKZIM18JQFmgJZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;766&quot; height=&quot;464&quot; data-origin-width=&quot;2384&quot; data-origin-height=&quot;1444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Trouble Shooting/DB</category>
      <category>AWS</category>
      <category>dynamodb</category>
      <category>In</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/13</guid>
      <comments>https://eagle25.tistory.com/13#entry13comment</comments>
      <pubDate>Sun, 10 Mar 2024 19:17:27 +0900</pubDate>
    </item>
    <item>
      <title>python itertools.gropuby() 의 함정</title>
      <link>https://eagle25.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 itertools.groupby 메서드의 함정에 대해 소개하고자 한다.&amp;nbsp;&lt;b&gt;python 에서 itertools를 사용한다면 이 글을 자세히 보는것을 추천한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론부터 말하자면 itertools.groupby를 쓸 땐, 정렬된 컬렉션을 사용해야 한다. 그렇지 않으면 집계 결과가 잘못될 수 있다.&lt;/b&gt; itertools.groupby는 흔히 아는 sql의 groupby처럼 동작하지 않기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;itertools.groupby()를 사용하는 로직에서&amp;nbsp; groupby만 하면 DB에서 가져온 값 일부가 사라지는 문제가 있었다. 원인을 파악하며 함정이 있는것을 알게 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사전 지식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Itertools&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파이썬 라이브러리로, 효율적인 알고리즘을 사용해 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;컬렉션 데이터를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;원하는 형태의 iterator를 만들어 주는 역할을 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 sample_data가 주어졌을 때, groupby를 하면 어떤 결과가 나올까?&lt;/p&gt;
&lt;pre id=&quot;code_1709887701818&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import itertools

sample_data = [&quot;A&quot;, &quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;A&quot;]
aggr_data = {k: len(list(v)) for k, v in itertools.groupby(sample_data, key=lambda x: x)}

print(aggr_data)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면, 아래와 같은 결과가 나오게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1709887890385&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{'A': 1, 'B': 1, 'C': 1}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상하다. 흔히 알고있는 groupby의 동작(sql의 groupby)이라면 아래와 같은 결과가 나와야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1709887997645&quot; class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;{'A': 3, 'B': 1, 'C': 1}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 gropuby를 사용하면서 A의 값이 제대로 집계되지 않은 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;원인 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공식 문서 조사&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;itertools의 공식 문서에 groupby()에는 다음과 같은 내용이 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The operation of groupby() is similar to the uniq filter in Unix. It generates a break or new group every time the value of the key function changes (which is why it is usually necessary to have sorted the data using the same key function). &lt;b&gt;That behavior differs from SQL&amp;rsquo;s GROUP BY which aggregates common elements regardless of their input order.&amp;nbsp;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 내용을 요약해보면 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;sql의 group by와 다르게 동작한다.&lt;/li&gt;
&lt;li&gt;key가 바뀔때마다 key에 대한 집계를 처음부터 새로 시작한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 상기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 겪고 있는 문제를 다시 상기시켜보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[&quot;A&quot;, &quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;A&quot;] 컬렉션이 주어졌을 때, itertools로 groupby를 하면 기대한대로값이 이 나오지 않는 문제가 있다.&lt;/li&gt;
&lt;li&gt;기대한 결과: &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;{'A': 3, 'B': 1, 'C': 1}&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;실제 결과: &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;({'A': 1, 'B': 1, 'C': 1})&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실험&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 컬랙션은 정렬되지 않은 상태이다. itertools.groupby는 key 가 바뀔때마다 해당 그룹의 집계를 처음부터 다시한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;It generates a break or new group every time the value of the key function changes (which is why it is usually necessary to have sorted the data using the same key function).&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 정렬된 값을 주어졌을때는 어떻게 될까? 기대한 값을 반환할 것이라는 가설을 세우고 검증해보겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1709888815085&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import itertools

sample_data = [&quot;A&quot;, &quot;A&quot;, &quot;A&quot;, &quot;B&quot;, &quot;C&quot;]
aggr_data = {k: len(list(v)) for k, v in itertools.groupby(sample_data, key=lambda x: x)}

print(aggr_data)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상기 코드의 실행 결과는 다음과 같다. 정렬된 컬렉션을 groupby하면 기대한대로 결과가 나오고 있음이 검증되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1709888871364&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{'A': 3, 'B': 1, 'C': 1}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬된 컬렉션을 제공했을 때 itertools.groupby() 값을 누락없이 집계함을 확인할 수 있었다. 반대로 정렬되지 않은 값은, 집계가 잘못 되는 문제가 있었다. 따라서 itertools.groupby를 사용할땐 정렬된 컬렉션을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;itertools.groupby()의 대안&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;itertools.groupby()를 꼭 사용할 필요가 없다면, defaultdict를 활용해 groupby를 구현하자. 가장 성능이 좋은 방법이다.&lt;/p&gt;
&lt;pre id=&quot;code_1709889223375&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import defaultdict

sample_data = [&quot;A&quot;, &quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;A&quot;]

aggr_data = defaultdict(str)
for key in sample_data.items():
    aggr_data[key] = value
    
print(aggr_data)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;defaultdict를 사용했을 때 성능이 얼마나 좋은걸까? itertools, defaultdict, pure python dict 3가지 로직의 실행 속도를 비교해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input 데이터는 아래처럼 A, B, C, D, E 알파벳들을 백만개씩 나열한 컬렉션이다. python3.10에서 이 데이터를 groupby 하는데 걸린 시간을 측정해 보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1709894219499&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;values = [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, &quot;E&quot;] 
sample_data = [random.choice(values) for _ in range(1000000)]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과는 다음과 같다. defaultdict가 제일 빠른 성능을 보여주고 python pure dict, itertools 가 그 뒤를 잇는다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 130px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;횟수&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;python pure dict&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;defaultdict&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;itertools.groupby()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;51.35&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;41.82&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;119.90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;51.02&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;41.68&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;116.09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;53.89&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;44.19&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;118.97&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;61.94&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;51.73&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;116.92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;57.28&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;47.96&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;117.18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;평균&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;56.03 ms&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;45.47 ms&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;117.81 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실험에 사용한 코드는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1709894603957&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import itertools
import random
from collections import defaultdict
import time


values = [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, &quot;E&quot;] 
sample_data = [random.choice(values) for _ in range(1000000)]

def measure_execution_time(method):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = method(*args, **kwargs)
        end_time = time.time()
        print(f&quot;{(end_time - start_time) * 1000:.3f}  ms&quot;)
        return result
    return wrapper

@measure_execution_time
def groupby_pure_python_dict(values):
    aggr_data = {}
    for key in values:
        aggr_data[key] = aggr_data.get(key, 0) + 1
    return aggr_data
    
@measure_execution_time
def groupby_defaultdict(values):
    aggr_data = defaultdict(int)
    for key in values:
        aggr_data[key] +=1
    return aggr_data

@measure_execution_time
def groupby_itertools(values):
    values = sorted(values)
    return {k: len(list(v)) for k, v in itertools.groupby(values, key=lambda x: x)}


_ = groupby_pure_python_dict(sample_data)
_ = groupby_defaultdict(sample_data)
_ = groupby_itertools(sample_data)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming/python</category>
      <category>defaultdict</category>
      <category>groupby</category>
      <category>itertools</category>
      <category>PYTHON</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/12</guid>
      <comments>https://eagle25.tistory.com/12#entry12comment</comments>
      <pubDate>Fri, 8 Mar 2024 19:44:44 +0900</pubDate>
    </item>
    <item>
      <title>DB locking  낙관적 vs 비관적</title>
      <link>https://eagle25.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DB locking은 DB의 동시성 제어 기법 중 하나이다. 다수의 사용자나 프로세스가 DB에 동시에 접근해 데이터를 읽거나 쓰면서 발생할 수 있는 충돌을 방지해 데이터의 무결성을 보장하는 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 DB locking의 기법 중 optimistic locking과 pessimistic locking에 대해 다루어 보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Optimistic locking&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에서 데이터를 읽거나 수정할 때 lock을 점유하지 않는 기법이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optimistic locking이 적용된 app은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;데이터의 수정 / 삭제 작업을 시도할 때 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;원본 데이터의 변경이 없을 것이라고 가정한다&lt;/span&gt;. 만약 원본 데이터에 변경이 발생한 경우 수정/삭제 작업은 실패하게 된다. 이 경우 app logic에서 fail에 대한 핸들링을 해 주어야 한다. (예, Retry; 원본 데이터 다시 읽은 후 작업 재시도)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;장점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;동시성이 향상(동시에 처리할 수 있는 작업이 많아진다)된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그러나 다음과 같은 단점을 수반하기도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Fail handling 로직 추가로 인해 application code의 복잡성 증가 여지 있음&lt;/li&gt;
&lt;li&gt;거짓 부정 혹은 거짓 긍정 문제 발생 가능성 있음.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;거짓 부정: 데이터가 변경되지 않았지만, 업데이트 시도가 거부되는 현상&lt;/li&gt;
&lt;li&gt;거짓 긍정: 데이터가 변경되었지만 이를 감지하지 못해 잘못된 데이터가 업데이트 되는 현상&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적합한 사용 예&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시성 성능 중요한 대규모 시스템&amp;nbsp;&lt;/li&gt;
&lt;li&gt;session 내에서 connection을 유지할 필요가 없는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pessimistic locking&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;optimistic locking과 상반되는 개념으로, 충돌이 예상되는 데이터에 미리 접근을 제한하는 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 시작될 때 미리 lock(shared or exclusive)을 hold해 접근을 제한하고 수정 / 삭제 등의 작업을 수행한다. 작업이 완료되면 lock을 해제한다. 트랜잭션을 유지해야 하기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 일관성과 무결성을 확실히 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드락 발생 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시 처리 시스템의 성능 저하 원인이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적합한 사용 예&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;은행 시스템과 같이 데이터 무결성이 중요한 환경.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS 지식/DB</category>
      <category>db</category>
      <category>lock</category>
      <category>Optimistic Locking</category>
      <category>Pessimistic locking</category>
      <category>transaction</category>
      <category>낙관적 락</category>
      <category>비관적 락</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/11</guid>
      <comments>https://eagle25.tistory.com/11#entry11comment</comments>
      <pubDate>Tue, 5 Mar 2024 14:04:41 +0900</pubDate>
    </item>
    <item>
      <title>CloudFront의 점진적 배포를 위한 Continuous Deployment</title>
      <link>https://eagle25.tistory.com/10</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Continous Deployment&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;continous deployment를 사용하면 CF의 변경을 안전하게 적용할 수 있다. 이 기능의 핵심 원리는 트래픽 분리인데, 원래 CF와 동일한 CF를 새로 만들고 여기에 일부 트래픽을 보내는 것이다. 이때 새로 만들어진 CF가 바로 staging distribtuion이 된다. (원래 CF는 primary distribution이 된다.)&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 기능을 통해 우리는 staging distribution CF에 변경된 config을 적용하고, 일부 트래픽만 흘려보내 변경 이후에도 CF가 정상적으로 동작하는지 검증할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;continuous deployment의 하위 개념은 2개 정도가 있다. (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아래 사진은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/continuous-deployment.html&quot;&gt;AWS 문서&lt;/a&gt;에서 발췌했다.)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Continuous deployment policy&lt;/li&gt;
&lt;li&gt;Staging distribution&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmGBgG/btsFg2ZgmEs/KbRe5KCRnF2hp4M2Sb94L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmGBgG/btsFg2ZgmEs/KbRe5KCRnF2hp4M2Sb94L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmGBgG/btsFg2ZgmEs/KbRe5KCRnF2hp4M2Sb94L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmGBgG%2FbtsFg2ZgmEs%2FKbRe5KCRnF2hp4M2Sb94L0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;453&quot; height=&quot;327&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Continuous deployment policy&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;primary로 들어오는 traffic을 어떻게&amp;nbsp; staging distribution으로 얼마나 흘려보낼지 결정하는 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Staging distribution&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;policy에 의해 받게되는 primary traffic 일부를 처리하는 CF이다. 이 CF에 변경된 설정을 먼저 적용하고 조금의 트래픽을 흘려보내 정상 여부를 검증한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Continuous deployment policy&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앞서 이 개념(이하 policy로 명명)은 traffic을 어떻게 얼마나 staging distribution으로 흘려보낼지 설정하는 정책이라고 설명했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 먼저, traffic을 어떻게 분리할 수 있는지 살펴보겠다. policy에는 두 가지 type이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Weight based&lt;/li&gt;
&lt;li&gt;Header based&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNH4wU/btsFlOyukCY/7zgkqBnPNRpZYQ2PYKAzSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNH4wU/btsFlOyukCY/7zgkqBnPNRpZYQ2PYKAzSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNH4wU/btsFlOyukCY/7zgkqBnPNRpZYQ2PYKAzSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNH4wU%2FbtsFlOyukCY%2F7zgkqBnPNRpZYQ2PYKAzSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;399&quot; height=&quot;308&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Weight based type&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전체 트래픽의 n%를 staging distribution으로 보내는 옵션이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AWS 콘솔의 설정창을 보면 몇 %의 트래픽을 보낼지 입력받는 weight 항목이 있다. 이 weight는 0 ~ 15%까지 설정할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;session stickiness도 설정할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpqjbi/btsFmjSxrlS/GMgXlUE0s8wbXNNFkyl9s0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpqjbi/btsFmjSxrlS/GMgXlUE0s8wbXNNFkyl9s0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpqjbi/btsFmjSxrlS/GMgXlUE0s8wbXNNFkyl9s0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbpqjbi%2FbtsFmjSxrlS%2FGMgXlUE0s8wbXNNFkyl9s0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;416&quot; height=&quot;304&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Header based type&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CloudFront에 HTTP요청을 보낼 때, 미리 지정한 헤더의 key와&amp;nbsp; value를 보내면 staging distribution으로 트래픽을 보내준다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;header의 key는 aws-cf-cd 로 시작해야 하며, 반드시 영문자 1글자 이상을 포함해야 한다. value는 자유롭게 지정하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왼쪽 사진처럼 header based policy를 설정했다면 http 요청시&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;헤더에 aws-cf-cd-xxxxx: 123을 보내면 staging distribution 으로 트래픽이 가게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;목적을 다한 staging distribution의 후처리 (feat. promotion)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;staging distribution은 변경된 config을 일부 traffic에만 적용해 안전성을 검증하는데 사용한다고 소개했다. 그렇다면 안전성이 검증된 이후 staging distribution은 어떻게 해야할까?&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;staging distribution 의 변경된 설정을 그대로 primary로 적용하고자 할 경우 promote를 하면된다. 아니라면 그냥 지우는 방법도 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Disable&lt;/li&gt;
&lt;li&gt;Promotio&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Staging distribution 생성 시 콘솔에서 보이는 모습&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 생성 안 했을 때&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1531&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9CNQm/btsFkGAHpoq/0jDkHQOddklwmGFKyazo10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9CNQm/btsFkGAHpoq/0jDkHQOddklwmGFKyazo10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9CNQm/btsFkGAHpoq/0jDkHQOddklwmGFKyazo10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9CNQm%2FbtsFkGAHpoq%2F0jDkHQOddklwmGFKyazo10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1531&quot; height=&quot;163&quot; data-origin-width=&quot;1531&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 생성했을때 (Weight type, header type도 비슷하다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1531&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beWp8p/btsFmg2Be0r/EyI6sJfVyGGAKOvDTRUJbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beWp8p/btsFmg2Be0r/EyI6sJfVyGGAKOvDTRUJbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beWp8p/btsFmg2Be0r/EyI6sJfVyGGAKOvDTRUJbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeWp8p%2FbtsFmg2Be0r%2FEyI6sJfVyGGAKOvDTRUJbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1531&quot; height=&quot;167&quot; data-origin-width=&quot;1531&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. staging distribution을 생성하면 Type이 Staging인 CF가 한 개 더 생긴다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V7YLf/btsFg3cQwPH/6VlqnkmofMxhbLVkhwoYn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V7YLf/btsFg3cQwPH/6VlqnkmofMxhbLVkhwoYn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V7YLf/btsFg3cQwPH/6VlqnkmofMxhbLVkhwoYn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV7YLf%2FbtsFg3cQwPH%2F6VlqnkmofMxhbLVkhwoYn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1772&quot; height=&quot;138&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주의사항&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;staging distribution을 사용할 때 아래 3 가지 사항을 고려해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쿼타(사용량)에 제한이 있다.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;계정당 최대 20개 이하로 staging distribution, continous deployment policy 만들 수 있다.&lt;/li&gt;
&lt;li&gt;weight based의 최대값은 15%이다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;session stickiness idle duration: 300&amp;ndash;3600 seconds&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP3 사용 지원하지 않음&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;staging distribution만 지원하지 않는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS 사정에 따라 Staging Distribution으로 요청이 가지 않을수도 있다&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특정한 경우 staging distribution으로 트래픽이 가지 않을 수 있다. 대표적인 예로 트래픽 스파이크 상황이 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AWS측 CloudFront의 리소스 사용량이 일정 수준 이상으로 튀면 모든 트래픽을 primary로 보내게 된다. 중요한 내용이므로 문서의 내용을 인용한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Cases when CloudFront sends all requests to the primary distribution&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;In certain cases, such as periods of high resource utilization, CloudFront might send all requests to the primary distribution regardless of what's specified in the continuous deployment policy.&lt;br /&gt;CloudFront sends all requests to the primary distribution during peak traffic hours, regardless of what's specified in the continuous deployment policy. Peak traffic refers to the traffic on the&amp;nbsp;CloudFront service, and not the traffic on your distribution.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Staging distribution 및 policy에 쿼타 제한을 둔 점, AWS CF의 리소스 상황에 따라 primary로만 트래픽을 보네는 정책이 있는 점을 미루어 봤을 때 staging distribution 기능을 운영하는데 필요한 리소스가 무시하기 어려운 수준이라고 추정된다.&lt;/p&gt;</description>
      <category>Infra/CDN</category>
      <category>AWS</category>
      <category>CDN</category>
      <category>cloudfront</category>
      <category>Continuous Deployment</category>
      <category>Staging Distribution</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/10</guid>
      <comments>https://eagle25.tistory.com/10#entry10comment</comments>
      <pubDate>Tue, 27 Feb 2024 00:17:15 +0900</pubDate>
    </item>
    <item>
      <title>CloudFront 안전하게 변경하기 #2, (Staging Distribution)</title>
      <link>https://eagle25.tistory.com/9</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 기존 버킷에 있던 파일들이 새로운 버킷에 모두 있는지 검증하는 과정에 대해 소개하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계는 새로운 버킷으로 전체 트래픽을 옮기기 전 선행되어야 한다. 새로운 버킷으로 migration이 누락된 파일이 있을 경우, 해당 리소스를 요청하는 모든 client는 에러를 경험하게 될 것이고, 이는 서비스 장애에 준하는 상황으로 이어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는&amp;nbsp; staging distribution 기능을 활용해 새로운 버킷으로만 전체 트래픽의 5% 요청을 보내보고, Popular Object 기능을 활용해 4xx, 5xx 관련 에러가 발생하지 않았는지 모니터링을 진행 했다. 그리고 이 과정에 대해 상세하게 다뤄보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;현재 상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 진행한 작업 덕분에 아래와 같은 상태가 되었다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CF에 새로운 버킷이 추가되었다.&lt;/li&gt;
&lt;li&gt;새로운 버킷에서 파일을 읽을 수 있는 상태이다. (권한 문제 등 이슈 없음)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;작업 목표&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 목표를 다시한번 정의해보자. 이번 단계의 작업 목표는 &lt;b&gt;기존 버킷에 있던 파일이 모두 새로운 버킷에도 있는지 검증하는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;작업 방향&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전체 트래픽의 5% 요청을 새로운 버킷으로 보내본다. &amp;lt;-- continous deployment (Staging Distribution) 기능 활용&lt;/li&gt;
&lt;li&gt;5% 요청 중 4xx, 5xx 에러가 발생한 요청이 있는지 확인한다. &amp;lt;-- &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Popular Object&lt;span&gt;&amp;nbsp;기능 활용&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Continous Deployment&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;continous deployment를 사용하면 CF의 변경을 안전하게 적용할 수 있다. 이 기능의 핵심 원리는 트래픽 분리인데, 원래 CF와 동일한 CF를 새로 만들고 여기에 일부 트래픽을 보내는 것이다. 이때 새로 만들어진 CF가 바로 staging distribtuion이 된다. (원래 CF는 primary distribution이 된다.) 이 기능을 통해 우리는 staging distribution CF에 변경된 config을 적용하고, 일부 트래픽만 흘려보내 변경 이후에도 CF가 정상적으로 동작하는지 검증할 수 있다. 보다 자세한 내용은 &lt;a href=&quot;https://eagle25.tistory.com/10&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 글&lt;/a&gt;을 참조하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Popular Object&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능을 통해 CF에 발생한 요청별 횟수, cache miss / hit, 2xx, 4xx, 5xx 횟수 등의 집계 정보를 얻을 수 있다. 이를 통해 staging distribution에 발생한 트래픽을 모니터링하면서 서빙에 문제 없는지 4xx, 5xx 에러의 비율 모니터링을 통해 검증하고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는&amp;nbsp; Popular objects의 실제 모습이다. /image.png 요청은 7일동안 99번 발생했는데 4xx 응답코드가 52번 나갔다. 즉 서빙이 원활하지 않았음을 확인할 수 있다. 마찬가지로 staging distribution을 이 기능으로 모니터링 했을 때, 4xx or 5xx 발생 비율이 높다면 정상적으로 서빙되지 않거나, 혹은 버킷에 파일이 누락되어있음을 찾을 수 있다. 보다 자세한 내용은 &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/popular-objects-report.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 문서&lt;/a&gt; 를 참조하도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1551&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bryFI2/btsFkWp0g0o/j8k8kFAbd9ojZhoPQMaaVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bryFI2/btsFkWp0g0o/j8k8kFAbd9ojZhoPQMaaVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bryFI2/btsFkWp0g0o/j8k8kFAbd9ojZhoPQMaaVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbryFI2%2FbtsFkWp0g0o%2Fj8k8kFAbd9ojZhoPQMaaVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1551&quot; height=&quot;447&quot; data-origin-width=&quot;1551&quot; data-origin-height=&quot;447&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Popular Object 기능은 콘솔 좌측 탭에서 Reports &amp;amp; analytics 에서 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQHlQg/btsFh6z7UGo/bKytsQhdqtlSsLCWKj7DnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQHlQg/btsFh6z7UGo/bKytsQhdqtlSsLCWKj7DnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQHlQg/btsFh6z7UGo/bKytsQhdqtlSsLCWKj7DnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQHlQg%2FbtsFh6z7UGo%2FbKytsQhdqtlSsLCWKj7DnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;441&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra/CDN</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/9</guid>
      <comments>https://eagle25.tistory.com/9#entry9comment</comments>
      <pubDate>Tue, 27 Feb 2024 00:17:08 +0900</pubDate>
    </item>
    <item>
      <title>CloudFront 안전하게 변경하기 #1, (CacheBehavior)</title>
      <link>https://eagle25.tistory.com/8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이 글에서는 Cache Behavior를 사용해 CF에 새로 추가한 버킷에서 파일을 읽을 수 있는지 검증하는 방법에 대해 소개한다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이전 글에서는 CloudFront의 S3를 교체하게 된 배경과, 어떤 작업 과정을 거쳐 버킷을 안전하게 변경할 것인지 소개했다. 자세한 내용은 &lt;/span&gt;&lt;a href=&quot;https://eagle25.tistory.com/7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이 글&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #555555;&quot;&gt;을 참조하자.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CacheBehavior&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 1편에서 다룰 핵심 기술이다. 소개에 앞서, 이 기술을 소개하는 AWS&amp;nbsp;&lt;a href=&quot;https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CacheBehavior.html&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #3d62ce;&quot;&gt;문서&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;의 핵심 내용을 인용해보았다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;CacheBehavior is a complex type that describes how CloudFront processes requests. Each cache behavior specifies the one origin from which you want CloudFront to get objects.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;간략하게 요약해 보자면, Cache Behavior는 CloudFront가 각 요청별로 리소스를 어떤 origin에서 가져올지를 설정하는 기능인 것이라 할 수 있다. 물론 이름이 Cache Behavior만큼 캐싱과 관련된 기능도 있지만, 이번 편의 핵심은 요청별로 origin을 다르게 사용하는, 즉 routing과 관련되어 있기 때문에 해당 내용은 생략한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;작업 목표&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이번편의 목표는 CloudFont가 새로운 버킷에서 파일을 읽을 수 있는 상태인지 검증하는 것이다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #555555;&quot;&gt;아래처럼 버킷을 바로 변경해버릴 경우, CF의 서빙에 문제가 발생될 수 있다. 이는 전체 장애로 이어지게 된다. 따라서, 새로운 버킷에서 파일을 잘 읽을 수 있는지 확인해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lbYe6/btsFhplAb8n/pV5gMNxt3K9mc3U9VNwxF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lbYe6/btsFhplAb8n/pV5gMNxt3K9mc3U9VNwxF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lbYe6/btsFhplAb8n/pV5gMNxt3K9mc3U9VNwxF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlbYe6%2FbtsFhplAb8n%2FpV5gMNxt3K9mc3U9VNwxF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1075&quot; height=&quot;358&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;작업 과정&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;선행 작업&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. CloudFront의 origin에 새로운 버킷을 추가한다.&amp;nbsp; AWS 문서에 따르면, 단순히 버킷만 추가하는것은 기존 동작에 영향을 주지 않는다고 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;If you have two origins and only the default cache behavior, the default cache behavior will cause CloudFront to get objects from one of the origins, but the other origin is never used.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;2. Legacy 버킷에 있는 파일 중, 서빙이 되지 않아도 문제 없는 파일을 1개 선택하고 새로운 버킷으로 복사한다. 필자는 예시로 /image.png(아무런 의미도 없는 파일) 를 사용했다&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cache Beavior 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 Cache Behavior는 &lt;span style=&quot;color: #555555;&quot;&gt;CloudFront가 각 요청별로 리소스를 어떤 origin에서 가져올지를 설정하는 기능이라고 소개했다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #555555;&quot;&gt;이제, 추가된 버킷에서 CF가 파일을 읽을 수 있도록 behavior를 추가해야 한다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;유의사항&lt;/b&gt;&lt;br /&gt;Cache Behavior를 추가할 때, 경로 설정에 유의해야 한다. 아직 새로운 버킷을 사용해도 문제 없는지 확실하지 않은 상태이다. 따라서 파일 한 개에 대해서만 새로운 버킷을 사용하도록 해야 점진적 변경이 가능하다. 이 파일은 읽기를 실패해도 문제 없는 만만만한 파일을 사용하면 된다. (예, 위에서 언급한 /image.png)&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Behavior 추가하기&lt;/b&gt;&lt;br /&gt;CloudFront Behavior는 기본적으로 아래처럼 설정되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F4rgG/btsFhR3gILA/2EG7qFiDGGBEjSa1kF6vfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F4rgG/btsFhR3gILA/2EG7qFiDGGBEjSa1kF6vfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F4rgG/btsFhR3gILA/2EG7qFiDGGBEjSa1kF6vfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF4rgG%2FbtsFhR3gILA%2F2EG7qFiDGGBEjSa1kF6vfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;241&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Create Behavior를 눌러 Behavior를 생성한다. 이 Behavior는 /image.png 요청은 새로운 버킷에서 가저오도록 설정하는 역할을 한다. Cache나 기타 설정은 본인 필요에 따라 자유롭게 설정하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGxlRL/btsFeFcdP9f/EGA9KsOPoSKdx7C4ZluB4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGxlRL/btsFeFcdP9f/EGA9KsOPoSKdx7C4ZluB4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGxlRL/btsFeFcdP9f/EGA9KsOPoSKdx7C4ZluB4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGxlRL%2FbtsFeFcdP9f%2FEGA9KsOPoSKdx7C4ZluB4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;777&quot; height=&quot;438&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Behavior 생성을 완료하면 /image.png의 priority가 0인것을 확인할 수 있다. 즉 우선순위가 제일 높다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mZj90/btsFfxycjPv/8oEAFoeWx0oVxNhDpMmzT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mZj90/btsFfxycjPv/8oEAFoeWx0oVxNhDpMmzT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mZj90/btsFfxycjPv/8oEAFoeWx0oVxNhDpMmzT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmZj90%2FbtsFfxycjPv%2F8oEAFoeWx0oVxNhDpMmzT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;264&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작업 결과&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cache Behavior까지 설정이 완료되었다면, 이제 CloudFront는 어떻게 동작하는지 다이어그램을 통해 설명해보겠다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아래 다이어그램에서 CF는 New, Legacy 총 두 개의&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;S3&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;origin을 가진다. Cache Behavior의 경우, /image.png는 new 버킷에서, 그 외 파일은 legacy 버킷에서 파일을 읽도록 셋팅되어 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;/image.png 패턴의 우선순위가 가장 높고, 해당 요청은 new 버킷에서 파일을 읽는다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCEaXS/btsFeUf597S/EZbpu7T4nDGYsM1xMVyTQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCEaXS/btsFeUf597S/EZbpu7T4nDGYsM1xMVyTQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCEaXS/btsFeUf597S/EZbpu7T4nDGYsM1xMVyTQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCEaXS%2FbtsFeUf597S%2FEZbpu7T4nDGYsM1xMVyTQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;355&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;/image.png 외 다른 요청은 * 패턴과 매칭되기 때문에 Legacy 버킷에서 파일을 읽는다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KJdgx/btsFgpzIwnb/H3ouKE6JNsCjX8xh1koKDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KJdgx/btsFgpzIwnb/H3ouKE6JNsCjX8xh1koKDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KJdgx/btsFgpzIwnb/H3ouKE6JNsCjX8xh1koKDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKJdgx%2FbtsFgpzIwnb%2FH3ouKE6JNsCjX8xh1koKDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;344&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;검증&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 버킷에서 파일을 읽는것이 문제 없는지 검증해 보겠다. 먼저 CF에서 /iamge.png 파일 읽기에 문제가 없다면, Legacy 버킷에서 /image.png를 지워본다. 파일을 지운 이후에도, /image.png 파일 읽기에 성공한다면 New 버킷에서 파일을 읽을 수 있는 상태가 된 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beTxyD/btsFjX9JGhz/BGipt9TWO70FyTywxMgPpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beTxyD/btsFjX9JGhz/BGipt9TWO70FyTywxMgPpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beTxyD/btsFjX9JGhz/BGipt9TWO70FyTywxMgPpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeTxyD%2FbtsFjX9JGhz%2FBGipt9TWO70FyTywxMgPpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;356&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 편에서는 Cloud Front의 Cache Behavior가 어떤 역할을 하는지 살펴 보았다. 이를 응용해 새로 추가된 s3에서 CF가 파일을 잘 읽어올 수 있는지 검증까지 진행했다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;다음 편에서는 새로운 버킷에서 전체 요청을 처리도록 하기 전, 기존 버킷에 있던 파일이 새로운 파일에 모두 있었는지 검증하도록 하겠다.&lt;/p&gt;</description>
      <category>Infra/CDN</category>
      <category>AWS</category>
      <category>Cache Behavior</category>
      <category>cloudfront</category>
      <category>S3</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/8</guid>
      <comments>https://eagle25.tistory.com/8#entry8comment</comments>
      <pubDate>Sun, 25 Feb 2024 17:14:54 +0900</pubDate>
    </item>
    <item>
      <title>CloudFront 안전하게 변경하기 #0, (에필로그)</title>
      <link>https://eagle25.tistory.com/7</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;에필로그&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;필자는 최근에 CF에서 S3를 안젼하게 교체하는 작업을 진행했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;작업시 아래와 같은 고려사항이 있었고, 각 작업별 사용한 CF의 기능들에 대해 &lt;b&gt;두 편에 나눠 소개하고자 한다.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://eagle25.tistory.com/8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;새로 추가한 버킷에서 파일을 읽을 수 있는지 검증한다. (Cache Behavior 사용)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eagle25.tistory.com/9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;새로 추가한 버킷에, 기존 버킷의 파일이 모두 있는지 확인한다. (Staging distirbution 사용)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;작업 목표&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 CF가 사용하는 origin 버킷을 legacy에서 new로 변경해야 했다. 한번에 버킷을 바꾸는 것은 리스크가 있기에, Cache Behavior와 Staging Distribution을 사용했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJa2tQ/btsFjILxNXc/75MNCKIhzkgFre82URlRn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJa2tQ/btsFjILxNXc/75MNCKIhzkgFre82URlRn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJa2tQ/btsFjILxNXc/75MNCKIhzkgFre82URlRn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJa2tQ%2FbtsFjILxNXc%2F75MNCKIhzkgFre82URlRn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;208&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjhBwd/btsFkIqZHRf/m1ORW0ZNZvWch3KsBJlIuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjhBwd/btsFkIqZHRf/m1ORW0ZNZvWch3KsBJlIuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjhBwd/btsFkIqZHRf/m1ORW0ZNZvWch3KsBJlIuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjhBwd%2FbtsFkIqZHRf%2Fm1ORW0ZNZvWch3KsBJlIuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;200&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;논외지만, 버킷을 바꾸는 배경은 간략히 소개하자면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;dev / prod 파일이 한 개의 버킷에서 관리되고 있었음. 작업자 실수로 인해 prod 파일에 문제가 생긴 사례가 있었고, dev / prod 버킷 분리가 필요해짐.&lt;/li&gt;
&lt;li&gt;서비스 공용 버킷이 있으나, 해당 CF 는 다른 버킷을 사용하고 있었음. 서비스 파편화를 줄이기 위해 공용 버킷을 사용하도록 해야했음.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Infra/CDN</category>
      <category>AWS</category>
      <category>Cache Behavior</category>
      <category>cloudfront</category>
      <category>S3</category>
      <category>Staging Distribution</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/7</guid>
      <comments>https://eagle25.tistory.com/7#entry7comment</comments>
      <pubDate>Sun, 25 Feb 2024 17:01:31 +0900</pubDate>
    </item>
    <item>
      <title>DynamoDB 응답 지연 시간 최소화 하기</title>
      <link>https://eagle25.tistory.com/5</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 면접 질문에서 DynamoDB의 응답 시간을 최소화 할 수 있는 방법에 대해 질문을 받았다. 나는 consistency 수준을 낮추는 것 외에 다른 방법에 대해서는 답변하지 못 했다. 다음번에 같은 상황이 오지 않도록 복기해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법을 찾아보던 중, AWS에서 작성한 &lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TroubleshootingLatency.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문서&lt;/a&gt;가 내용이 좋아서 가져왔다. 문서에서는 Latency를 줄일 수 있는 여러가지 방법을 제시하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;1. Adjust request timeout and retry behavior&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;AWS SDK는 network resiliency, tcp packet timeout 등의 상황에 대비하기 위해 retry와 timeout이 적절한 균형을 이루도록 설정되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;다만, 최소 latency가 주 고려사항이라면 SDK의 timeout, retry 동작에 대한 조정이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;조정은 클라이언트들이 일반적으로 겪는 latency를 기준으로 맞추면 된다. 평소보다 오래걸리는 request는 fail했을 확률이 높기 때문에, 실패로 처리하고 새로운 요청으로 fail fast 하는것이 결과적으로 latency를 줄일 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;2. Client와 DynamoDB간의 거리 최소화&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;network trip time 또한 latency에 포함된다. client와 dynamo의 endpoint간 물리적 거리가 길어질수록 network triptime 증가로 이어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;글로벌 트래픽이 발생하는 환경이라면, 물리적 거리로 인한 latency를 줄이기 위해 두 가지 전략을 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.907%;&quot;&gt;방법&lt;/td&gt;
&lt;td style=&quot;width: 67.093%;&quot;&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.907%;&quot;&gt;&lt;b&gt;Global Table&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 67.093%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;multi region replication 통해 client가 local region의 replication table에서 데이터 읽으면 응답 지연 시간이 크게 감소한다.&lt;/li&gt;
&lt;li&gt;활성화할 region은 따로 선택 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.907%;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://docs.aws.amazon.com/vpc/latest/privatelink/gateway-endpoints.html&quot;&gt;gateway VPC Endpoint&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 67.093%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VPC Endpoint를 활용해 서비스와 dynamoDB 데이터 교환이 AWS 내부망에서 이루어지도록 할 수 있다.&lt;/li&gt;
&lt;li&gt;IGW 통해 트래픽 이동하는 것 대비 물리적 trip 거리가 감소하여 latency 감소 기대 가능함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;3. 캐시 사용&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;읽기 요청이 heavy하게 발생한다면&amp;nbsp; cache를 두는것도 좋다. DAX나 Redis등의 In-Memory cache를 적극 사용하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #16191f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;문서에서는 &lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAX.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DAX&lt;/a&gt;를 사용할 경우, 최대 10배 이상의 성능 향상을 기대할 수 있다고 소개하고 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;4. 커넥션 재활용&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;DDB 요청은 기본적으로 HTTPS 통신에서 세션이 만들어진다. http connection 초기화 과정은 latency가 많이 발생한다. 그래서 일반적으로 첫번째 커넥션의 지연시간이 가장 크다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;따라서 새로운 커낵션을 매 번 만들기보다, 기존 커넥션을 잘 활용하는 것이 좋다. 커넥션이 항상 alive 하도록 유지하기 위해 30초마다 GetItem 등의 요청을 보낼 수 있다. 그러나 코드 복잡성을 야기할 수 있기 때문에 사용 전 고민해보는것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;5.&amp;nbsp; Use eventually consistent read&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;application이 strongly consistent reads를 요구하지 않는다면 eventually consistent 수준의 read를 사용하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;eventually consistent read의 장점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;1.&amp;nbsp; 저렴한 비용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;2. 일시적인 latency 증가를 경험할 가능성이 더 낮다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra/DB</category>
      <category>AWS</category>
      <category>dynamodb</category>
      <category>Latency</category>
      <author>eagle25</author>
      <guid isPermaLink="true">https://eagle25.tistory.com/5</guid>
      <comments>https://eagle25.tistory.com/5#entry5comment</comments>
      <pubDate>Tue, 20 Feb 2024 00:30:59 +0900</pubDate>
    </item>
  </channel>
</rss>