<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>백엔드 개발 블로그</title>
    <link>https://honeybomb.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 7 May 2026 04:58:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>베꺼</managingEditor>
    <item>
      <title>Google OAuth2 적용하기</title>
      <link>https://honeybomb.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 2025/12/09 기준&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;Google 공식 가이드: &lt;a href=&quot;https://developers.google.com/identity/openid-connect/openid-connect?hl=ko&quot;&gt;https://developers.google.com/identity/openid-connect/openid-connect?hl=ko&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구글 클라우드 프로젝트 생성 및 설정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SCR-20251209-kqrp.png&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9nbZj/dJMcaaRlmFn/cR4jJmigWd0D5cF2sGvh51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9nbZj/dJMcaaRlmFn/cR4jJmigWd0D5cF2sGvh51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9nbZj/dJMcaaRlmFn/cR4jJmigWd0D5cF2sGvh51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9nbZj%2FdJMcaaRlmFn%2FcR4jJmigWd0D5cF2sGvh51%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;849&quot; height=&quot;204&quot; data-filename=&quot;SCR-20251209-kqrp.png&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;/figure&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;a href=&quot;https://console.cloud.google.com/&quot;&gt;클라우드 콘솔&lt;/a&gt;에서 신규 생성&lt;/li&gt;
&lt;li&gt;프로젝트 생성에 시간이 소요될 수 있음&lt;/li&gt;
&lt;li&gt;BETA 프로젝트와 PROD 프로젝트를 각각 생성하였음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SCR-20251209-ksrz.png&quot; data-origin-width=&quot;325&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf8ZKV/dJMcabCH3dn/8MKvra9XQUWqWCrXbgagEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf8ZKV/dJMcabCH3dn/8MKvra9XQUWqWCrXbgagEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf8ZKV/dJMcabCH3dn/8MKvra9XQUWqWCrXbgagEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf8ZKV%2FdJMcabCH3dn%2F8MKvra9XQUWqWCrXbgagEK%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;325&quot; height=&quot;350&quot; data-filename=&quot;SCR-20251209-ksrz.png&quot; data-origin-width=&quot;325&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SCR-20251209-kssx.png&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FRvPI/dJMcabCH3do/G4tZHvQlXUyfCGkqtgbUwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FRvPI/dJMcabCH3do/G4tZHvQlXUyfCGkqtgbUwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FRvPI/dJMcabCH3do/G4tZHvQlXUyfCGkqtgbUwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFRvPI%2FdJMcabCH3do%2FG4tZHvQlXUyfCGkqtgbUwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;962&quot; data-filename=&quot;SCR-20251209-kssx.png&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;/figure&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;openid 및 email 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SCR-20251209-kueb.png&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvYPTB/dJMcagRx4bB/3t46OIApCsLU2Wdq9JhpcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvYPTB/dJMcagRx4bB/3t46OIApCsLU2Wdq9JhpcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvYPTB/dJMcagRx4bB/3t46OIApCsLU2Wdq9JhpcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvYPTB%2FdJMcagRx4bB%2F3t46OIApCsLU2Wdq9JhpcK%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;637&quot; height=&quot;1020&quot; data-filename=&quot;SCR-20251209-kueb.png&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;1020&quot;/&gt;&lt;/span&gt;&lt;/figure&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;웹 클라이언트인 경우 도메인과 redirection URI를 입력
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Security의 경우 기본값은 &lt;code&gt;{baseUrl}/login/oauth2/code/google&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클라이언트 생성 후 클라이언트 ID와 secret(보안 비밀번호) 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Google ID Token 포맷&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;iss&quot;: &quot;https://accounts.google.com&quot;,
  &quot;azp&quot;: &quot;1234987819200.apps.googleusercontent.com&quot;,
  &quot;aud&quot;: &quot;1234987819200.apps.googleusercontent.com&quot;,
  &quot;sub&quot;: &quot;10769150350006150715113082367&quot;,
  &quot;at_hash&quot;: &quot;HK6E_P6Dh8Y93mRNtsDB1Q&quot;,
  &quot;hd&quot;: &quot;example.com&quot;,
  &quot;email&quot;: &quot;jsmith@example.com&quot;,
  &quot;email_verified&quot;: &quot;true&quot;,
  &quot;iat&quot;: 1353601026,
  &quot;exp&quot;: 1353604926,
  &quot;nonce&quot;: &quot;0394852-3190485-2490358&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본으로 제공되는 데이터는 &lt;code&gt;aud&lt;/code&gt;, &lt;code&gt;exp&lt;/code&gt;, &lt;code&gt;iat&lt;/code&gt;, &lt;code&gt;iss&lt;/code&gt;, &lt;code&gt;sub&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;상세 설명은 &lt;a href=&quot;https://developers.google.com/identity/openid-connect/openid-connect?hl=ko&quot;&gt;문서&lt;/a&gt; 참고&lt;/li&gt;
&lt;li&gt;&lt;code&gt;email&lt;/code&gt;은 고유한 값이 아니며 변동될 수 있으므로 사용자 식별을 위한 고유키는 &lt;code&gt;sub&lt;/code&gt; 값을 사용해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Security 설정&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://www.baeldung.com/spring-security-openid-connect&quot;&gt;Baeldung&lt;/a&gt;에서 잘 설명되어 있음&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/guides/tutorials/spring-boot-oauth2&quot;&gt;Spring Security 공식 가이드&lt;/a&gt;도 참고&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>인증 &amp;amp; 보안</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/12</guid>
      <comments>https://honeybomb.tistory.com/12#entry12comment</comments>
      <pubDate>Tue, 9 Dec 2025 13:17:28 +0900</pubDate>
    </item>
    <item>
      <title>Java Stream 병렬로 실행하기</title>
      <link>https://honeybomb.tistory.com/11</link>
      <description>&lt;h2&gt;기본&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;List&amp;lt;MyResult&amp;gt; results = inputList.parallelStream()
    .map(this::doSomeProcessing)
    .collect(Collectors.toList());&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;parallelStream()&lt;/code&gt;을 중간에 하나 넣어주기만 하여도 자동으로 병렬 실행된다.&lt;/li&gt;
&lt;li&gt;이 때 실제 병렬 처리에 사용되는 pool은 전역으로 공유하는 공유 ForkJoinPool이다.&lt;/li&gt;
&lt;li&gt;기본적으로 pool size는 &lt;code&gt;CPU 코어 수 - 1&lt;/code&gt;이다.&lt;ul&gt;
&lt;li&gt;그렇다고 CPU 코어를 다 안쓰는 것은 아니다. 병렬 처리 시에 Main 쓰레드도 거든다. 따라서 전체 코어를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/geekculture/pitfalls-of-java-parallel-streams-731fe0c1eb5f&quot;&gt;참고 Medium 게시글&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;java.util.concurrent.ForkJoinPool.common.parallelism&lt;/code&gt; 환경 변수를 수정하여 기본 common pool의 사이즈를 변경할 수 있다.&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/30802463/how-many-threads-are-spawned-in-parallelstream-in-java-8&quot;&gt;참고 스택오버플로우&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ForkJoinPool을 이용해 pool size 커스터마이징&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;ForkJoinPool forkJoinPool = new ForkJoinPool(poolSize);
List&amp;lt;MyResult&amp;gt; results = forkJoinPool.submit(() -&amp;gt; inputList.parallelStream()
    .map(this::doSomeProcessing)
    .collect(Collectors.toList())).get();&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;따로 만든 ForkJoinPool에 병렬 스트림 관련 runnable / function을 submit하여 실행하면 원하는 pool size가 적용된다.&lt;/li&gt;
&lt;li&gt;Task를 따로 나누어서 submit한 것도 아니고 통짜 function을 넣었는데도 어떻게 작동하는 건지 궁금했는데&lt;/li&gt;
&lt;li&gt;ForkJoinPool의 작동 방식 때문이라고 한다. (ForkJoinPool task 내에서 ForkJoinPool이 사용될 때에는 원래 pool을 그대로 사용한다.)&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/a/22269778&quot;&gt;참고 스택오버플로우&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/11</guid>
      <comments>https://honeybomb.tistory.com/11#entry11comment</comments>
      <pubDate>Mon, 19 May 2025 21:50:55 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] utf8mb4_general_ci vs utf8mb4_unicode_ci vs utf8mb4_0900_ai_ci</title>
      <link>https://honeybomb.tistory.com/10</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; Character Set&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`utf8`는 전체 유니코드를 지원하지 않는다. 한글자에 최대 3바이트를 할당한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이모지를 저장하고자 한다면 `utf8mb4`를 사용하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Collation&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Collation은 어떻게 문자열을 비교하고 정렬할지에 대한 규칙이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`utf8mb4_bin`&lt;/b&gt; : 바이트 순서대로 정렬
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(ex: 'A' &amp;lt; 'B' &amp;lt; 'a' &amp;lt; 'b' )&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`utf8mb4_general_ci`&lt;/b&gt; : Case Insensitive, 대소문자 구분 없이 비교 및 정렬
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(ex: 'A' = 'a' &amp;lt; 'B' = 'b')&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`utf8mb4_unicode_ci`&lt;/b&gt; :&amp;nbsp; general에 더해서 유럽쪽 문자 비교 로직이 추가됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;(ex: '&amp;szlig;' = 'ss')&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;유럽쪽 다국어 지원이 필요하지 않다면 굳이?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`utf8mb4_0900_ai_ci`&lt;/b&gt; : MySQL 8 디폴트 값.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0900: 유니코드 collation algorithm 9.0.0 을 뜻함&lt;/li&gt;
&lt;li&gt;Accent Insensitive : 외국어에서 사용하는 악센트 기호가 있을 때 구분 없이 동일한 글자로 취급&lt;/li&gt;
&lt;li&gt;Case Insensitive : 대소문자 구분 없음&lt;/li&gt;
&lt;li&gt;큰 문제가 있는데... &quot;가&quot; 와 &quot;ㄱㅏ&quot;를 동일한 문자열로 취급함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국에서는 웬만하면 &lt;b&gt;utf8mb4, utf8mb4_general_ci&lt;/b&gt;를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 8에서 유니코드가 디폴트가 되었다고 마음 놓지 말고 collation을 챙겨주자.&lt;/p&gt;</description>
      <category>Database</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/10</guid>
      <comments>https://honeybomb.tistory.com/10#entry10comment</comments>
      <pubDate>Fri, 5 Jan 2024 11:26:59 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Java 가상 스레드 (Virtual Thread)</title>
      <link>https://honeybomb.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Java 가상 스레드는 JDK 19부터 Preview, LTS 버전인 JDK 21부터 정식 탑재된 기능으로서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS가 제공해주는 스레드를 그대로 사용하지 않고 가상의 경량 스레드를 사용하는 기능이다.&lt;span style=&quot;color: #9d9d9d;&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;개인적으로는 JDK 8 이후로 Java 언어에는 이렇다 할 큰 변화는 없었다고 생각하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project Loom(Java 언어에 경량화된 비동기 기능을 Java에 추가하기 위한 프로젝트)이 추진되면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만의 큰 혁신이 다가왔다는 생각이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 컨셉&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존의 코드, 로깅, 디버깅 툴들을 최대한 호환시키면서 경량화된 스레드를 지원한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자는 비즈니스 로직에만 집중하면서 Blocking IO에 대한 성능을 비약적으로 향상시킬 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기존 스레드는 스레드 수의 제약이 컸던 반면 가상 스레드는 백만개까지도 큰 문제가 없다고 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;물론 다소의 오버헤드는 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;용어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플랫폼 스레드(Platform Thread): 기존에 사용하던 스레드로서 실제 OS에서 제공하는 스레드를 이용&lt;/li&gt;
&lt;li&gt;가상 스레드(Virtual Thread): 신규로 추가된 경량화된 가상 스레드&lt;/li&gt;
&lt;li&gt;캐리어 스레드(Carrier Thread): 가상 스레드 작업이 실제로 수행되는 플랫폼 스레드.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서 얼마나 좋아졌는데요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 테스트가 잘 정리되어 있는 블로그가 있어 링크를 첨부한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://findstar.pe.kr/2023/07/02/java-virtual-threads-2/&quot;&gt;https://findstar.pe.kr/2023/07/02/java-virtual-threads-2/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1699693904880&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Virtual Thread란 무엇일까? (2)&quot; data-og-description=&quot;Software Developer, I love code.&quot; data-og-host=&quot;findstar.pe.kr&quot; data-og-source-url=&quot;https://findstar.pe.kr/2023/07/02/java-virtual-threads-2/&quot; data-og-url=&quot;https://findstar.pe.kr/2023/07/02/java-virtual-threads-2/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/r5JGp/hyUu3ArDef/3G3z0qXHwc0aap9MjDszQ1/img.png?width=1060&amp;amp;height=702&amp;amp;face=0_0_1060_702,https://scrap.kakaocdn.net/dn/baseDz/hyUuX737en/epPPj3yQf870yB8TQf8yB0/img.png?width=1060&amp;amp;height=702&amp;amp;face=0_0_1060_702&quot;&gt;&lt;a href=&quot;https://findstar.pe.kr/2023/07/02/java-virtual-threads-2/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://findstar.pe.kr/2023/07/02/java-virtual-threads-2/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/r5JGp/hyUu3ArDef/3G3z0qXHwc0aap9MjDszQ1/img.png?width=1060&amp;amp;height=702&amp;amp;face=0_0_1060_702,https://scrap.kakaocdn.net/dn/baseDz/hyUuX737en/epPPj3yQf870yB8TQf8yB0/img.png?width=1060&amp;amp;height=702&amp;amp;face=0_0_1060_702');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Virtual Thread란 무엇일까? (2)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Software Developer, I love code.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;findstar.pe.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 수행하는 로직에 따라서 천차만별이기 때문에 정량적인 수치를 딱 짚기는 어렵지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 API 호출 등 Blocking IO가 많고 그 소요시간이 병목인 로직에서는 시스템 자원을 효율적으로 활용할 수 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 코드 수정 없이도 매우 큰 성능 향상을 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의점&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. ThreadLocal&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 플랫폼 스레드(OS 스레드)를 사용할 경우 ThreadLocal에 저장되는 객체의 개수가 스레드 개수만큼 제한되며 자주 재사용되는 반면&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;따라서 ThreadLocal에 너무 무거운 객체를 담지 않도록 주의해야하며 사용 후에는 반드시 remove()로 정리해서 빠르게 GC될 수 있도록 해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/FasterXML/jackson-core/issues/919&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Jackson&lt;/a&gt;이나 &lt;a href=&quot;https://github.com/netty/netty/issues/13191&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Netty&lt;/a&gt;와 같은 유명 라이브러리에서도 비용이 큰 버퍼와 캐시 등을 ThreadLocal에 저장해두고 있기 때문에&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;a href=&quot;https://openjdk.org/jeps/446&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;'Scoped Values'란 신규 기능이 JDK 21에 Preview로 탑재되었다.&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JEP 문두부터 &quot;많은 수의 가상스레드를 사용하는 경우 ThreadLocal 대신 이거 쓰세요&quot;라고 소개하는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;가볍게 훑어보았을 때에는 기존 ThreadLocal에서 mutability와 lifetime을 제한함으로써&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;좀 더 효율적으로 사용하는 컨셉인 것으로 보이는데 나중에 좀 더 공부해봐야겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Pinning&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Thread-safety 를 위해 synchronized block을 사용하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 혹은 native 메서드나 foreign 함수를 실행할 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두가지 케이스에서 가상 스레드는 캐리어 스레드에 고정(pinned)되어 blocking이 있다고 해도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 가상 스레드로의 switching이 불가능하여 잠재적인 성능 이슈가 있다.&lt;/p&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://openjdk.org/jeps/444&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JEP-444&lt;/a&gt; 에서는 pinning이 너무 길어지지 않도록 synchronized block을 손보고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;native 메서드나 foreign 함수의 경우 ReentrantLock을 이용해서 함수 실행 전후에 Guard를 추가하도록 가이드하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 스레드 독점 현상 (Monopolization)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blocking 없이 CPU를 오래 점유하는 가상 스레드가 다수 있을 경우&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;이는 가상 스레드에 아직 선점 스케줄링(preemptive scheduling)이 구현되지 않았기 떄문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해서 block이 없으면 현재 돌고 있는 가상 스레드가 아무리 오래 실행되었다고 해도 다른 스레드로 강제로 전환시키지 않는다.&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;4. DB 커넥션 풀 등 외부의 한정된 자원&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 스레드가 아무리 효율적이라고 해도 DB 커넥션 풀 사이즈가 적다면 오히려 무수한 connection timeout을 맞이할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 MSA 환경에서는 내 서버가 아무리 빨라도 저쪽 서버가 받아주지 못하면 더 큰 문제를 야기할 수 있으며 합법적 디도스 공격이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 스레드 이전의 OS 스레드를 사용할 때에는 최대 스레드 개수가 천연 rate limiter의 역할을 했다면&lt;br /&gt;가상 스레드를 사용할 경우 리미터가 해제된 것과 다름이 없기 때문에 이를 유념해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 가상 스레드 오버헤드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 스레드는 플랫폼 스레드에 비해서 다소의 오버헤드가 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blocking이 없는 로직의 경우 오히려 더 성능이 느릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 가상 스레드를 도입하기 전 blocking 로직이 병목인 게 맞는지 검토할 필요는 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reactive Webflux는 이제 한물갔나요??&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 스레드는 저수준의 OS 스레드를 경량화한 기능으로서 결국 '스레드'일 뿐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webflux의 정확한 대체재는 아니다.&lt;/p&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 API 호출 및 DB 요청과 같은 blocking IO가 많지만&lt;/li&gt;
&lt;li&gt;복잡한 비동기성까지는 필요 없고 선형적인 로직이 주를 이루면서&lt;/li&gt;
&lt;li&gt;단순히 blocking IO에 대한 성능 이점 때문에 Mono.map만 주구장창 연결하는 WebFlux를 사용했다면&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;-&amp;gt;&lt;/code&gt; 가상 스레드가 더 적절한 대안이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;/li&gt;
&lt;li&gt;BackPressure 등 Reactive가 제공하는 정교한 기능이 필요하다면&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;-&amp;gt;&lt;/code&gt;  WebFlux가 적합할 것이다.&lt;/p&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;극히 개인적인 의견으로는 예전에 WebFlux를 적용해보면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MDC는 복잡하고 스택트레이스도 제대로 안나오고 디버깅도 복잡하고 예외 처리도 번거롭고...&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;a href=&quot;https://openjdk.org/jeps/453&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Structured Concurrency&lt;/a&gt;라는 새로운 Java의 concurrency 기능도 논의가 되고 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK 21 현재 Preview 기능으로 도입되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 스레드 + Structured Concurrency가 합쳐지고 Project Loom이 완성된다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비로소 Reactive WebFlux, 코틀린 Coroutine과의 싸움이 되지 않을까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle의 개발자이자 Project Loom의 주요 개발자인 브라이언 게츠(Brian Goetz)는 &lt;a href=&quot;https://www.youtube.com/watch?v=9si7gK94gLo&amp;amp;t=1156s&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2021년 팟캐스트&lt;/a&gt;에서 아래와 같이 언급하는데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SCR-20231112-mezv.png&quot; data-origin-width=&quot;2438&quot; data-origin-height=&quot;1464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Shx1F/btsz9wRbh5Z/exmRwnNFpdiKqjF0UQAfb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Shx1F/btsz9wRbh5Z/exmRwnNFpdiKqjF0UQAfb0/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=9si7gK94gLo&amp;amp;amp;t=1156s&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Shx1F/btsz9wRbh5Z/exmRwnNFpdiKqjF0UQAfb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FShx1F%2Fbtsz9wRbh5Z%2FexmRwnNFpdiKqjF0UQAfb0%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;600&quot; height=&quot;360&quot; data-filename=&quot;SCR-20231112-mezv.png&quot; data-origin-width=&quot;2438&quot; data-origin-height=&quot;1464&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=9si7gK94gLo&amp;amp;t=1156s&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Loom이 Reactive Programming을 죽일 것이라고 생각한다.&lt;br /&gt;&lt;br /&gt;Reactive Programming은 과도기적 기술이었으며&lt;br /&gt;우리가 가진 문제에 대한 반작용에 불과했다는 것을&lt;br /&gt;머지 않은 미래에 모두가 깨달을 것이라고 생각한다.&lt;br /&gt;&lt;br /&gt;만약&amp;nbsp;그&amp;nbsp;문제가&amp;nbsp;사라지게&amp;nbsp;된다면&lt;br /&gt;Reactive는&amp;nbsp;우리가&amp;nbsp;원하던&amp;nbsp;해결책이&amp;nbsp;아니라는&amp;nbsp;것이&amp;nbsp;분명하게&amp;nbsp;드러날&amp;nbsp;거다.&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java가 가진 한계점을 극복하기 위해 라이브러리(Reactive)와 개량 언어(Kotlin Coroutine)가 인기를 끌고 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Java가 이런 문제점을 해결한다면 그들의 위치가 애매해진다는 점에는 공감이 간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오라클에서 Reactive를 죽이려는 한편 코틀린 진영에서는 가상 스레드 기능을 코루틴에 포함시켜 확장하는 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java로 밥벌어먹는 사람 입장에서는 그 미래가 매우 궁금하긴 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(아직 Reactive도 제대로 못써보긴 했지만)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring에서는 언제 사용할 수 있나요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 3.2에서는 &lt;code&gt;spring.threads.virtual.enabled=true&lt;/code&gt; 옵션을 통해 정식으로 지원하며&lt;br /&gt;심지어 Spring Boot 2에서도 Embedded Tomcat의 요청을 처리할 executor를 명시적으로 지정해줌으로써 적용이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Spring Boot 2에서 제대로 최적화가 되었는지는 좀더 확인이 필요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699686012144&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public TomcatProtocolHandlerCustomizer&amp;lt;?&amp;gt; protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -&amp;gt; protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;총평&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 스레드는 확실히 Java 생태계의 패러다임을 바꿀 수 있는 기능이라고 생각하지만&lt;br /&gt;흔들기만 하면 성능 좋은 서버가 탄생하는 마법봉은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕션에 적용하기 전 위 주의점에서 설명했던 사항들을 점검해보고 테스트와 시행착오를 거쳐야할 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ThreadLocal에 무거운 객체를 담지 말고 사용 후 remove()로 정리할 것&lt;/li&gt;
&lt;li&gt;사용하는 라이브러리가 가상 스레드 환경에서 잘 작동하는지, CPU와 메모리 사용량에 문제가 없는지 확인&lt;/li&gt;
&lt;li&gt;Pinning과 스레드 독점 현상이 문제가 되진 않을지 검토&lt;/li&gt;
&lt;li&gt;DB 커넥션 풀 사이즈 및 외부 API 등 여러 한정된 자원과의 상호작용을 고려&lt;/li&gt;
&lt;li&gt;정말 가상 스레드가 필요한지, Blocking 로직이 병목이 맞는지 검토&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕션 코드는 아직 Spring Boot 3, JDK 17로도 전환을 못했는데 21은 언제 할 수 있을지 모르겠다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://openjdk.org/jeps/444&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JEP 444: Virtual Threads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openjdk.org/jeps/446&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JEP 446: Scoped Values (Preview)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://findstar.pe.kr/2023/07/02/java-virtual-threads-2/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://findstar.pe.kr/2023/07/02/java-virtual-threads-2/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://quarkus.io/blog/virtual-thread-1/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://quarkus.io/blog/virtual-thread-1/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/blog/2022/10/11/embracing-virtual-threads&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://spring.io/blog/2022/10/11/embracing-virtual-threads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/blog/2023/09/09/all-together-now-spring-boot-3-2-graalvm-native-images-java-21-and-virtual&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://spring.io/blog/2023/09/09/all-together-now-spring-boot-3-2-graalvm-native-images-java-21-and-virtual&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/9</guid>
      <comments>https://honeybomb.tistory.com/9#entry9comment</comments>
      <pubDate>Sat, 11 Nov 2023 19:25:55 +0900</pubDate>
    </item>
    <item>
      <title>JWT의 구조 및 사용 예</title>
      <link>https://honeybomb.tistory.com/8</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSON Web Token&lt;/li&gt;
&lt;li&gt;RFC 7519 표준&lt;/li&gt;
&lt;li&gt;흔히 로그인 데이터 등을 주고 받는 데에 사용된다.&lt;/li&gt;
&lt;li&gt;JSON 형태의 데이터에 서명 정보를 추가하여 데이터의 위변조 여부를 검사할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서명에는 HMAC, RSA 등의 암호화 기법이 사용된다.&lt;/li&gt;
&lt;li&gt;HMAC 참고: &lt;a href=&quot;https://blog.honeybomb.kr/7&quot;&gt;https://blog.honeybomb.kr/7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1662967915430&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;HMAC 이란?&quot; data-og-description=&quot;API 통신 등을 할 때, 보안 등의 이유로 통신되는 메시지의 위변조를 막아야 할 경우가 있다. 이 때 어떠한 토큰을 사용하여 메시지가 위변조가 되지 않았는지 검증하는 기법이 주로 사용되는데, &quot; data-og-host=&quot;blog.honeybomb.kr&quot; data-og-source-url=&quot;https://blog.honeybomb.kr/7&quot; data-og-url=&quot;https://blog.honeybomb.kr/7&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pIElj/hyPJSSH46P/cuuW1s5wHGM4xyyKYkLr20/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/EoCra/hyPLoPZzgO/IE2epbvyekQKuPchcdQbl0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/MnIRX/hyPLeGB6of/vqf3zeFDJetuWPQK2vybh1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://blog.honeybomb.kr/7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.honeybomb.kr/7&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pIElj/hyPJSSH46P/cuuW1s5wHGM4xyyKYkLr20/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/EoCra/hyPLoPZzgO/IE2epbvyekQKuPchcdQbl0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/MnIRX/hyPLeGB6of/vqf3zeFDJetuWPQK2vybh1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;HMAC 이란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;API 통신 등을 할 때, 보안 등의 이유로 통신되는 메시지의 위변조를 막아야 할 경우가 있다. 이 때 어떠한 토큰을 사용하여 메시지가 위변조가 되지 않았는지 검증하는 기법이 주로 사용되는데,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.honeybomb.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-09-12 오후 4.22.45.png&quot; data-origin-width=&quot;2460&quot; data-origin-height=&quot;1344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkfYdy/btrLQnPOLPM/gjpZdmdnAC3PpxKv4rkpu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkfYdy/btrLQnPOLPM/gjpZdmdnAC3PpxKv4rkpu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkfYdy/btrLQnPOLPM/gjpZdmdnAC3PpxKv4rkpu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkfYdy%2FbtrLQnPOLPM%2FgjpZdmdnAC3PpxKv4rkpu0%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;2460&quot; height=&quot;1344&quot; data-filename=&quot;스크린샷 2022-09-12 오후 4.22.45.png&quot; data-origin-width=&quot;2460&quot; data-origin-height=&quot;1344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&quot;헤더.페이로드.서명&quot;&lt;/code&gt;의 형태이다.&lt;/li&gt;
&lt;li&gt;헤더에는 서명에 사용된 알고리즘과 타입을 담고 있는 JSON이 Base64 URL 형태로 인코딩된다.&lt;/li&gt;
&lt;li&gt;페이로드는 실제 전송되는 데이터를 담고 있으며 데이터 JSON이 Base64 URL 형태로 인코딩된다.&lt;/li&gt;
&lt;li&gt;서명은 헤더에 명시된 알고리즘을 사용하여 헤더 및 페이로드 데이터를 서명한 문자열이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클레임(Claims)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이로드에 사용되는 JSON은 정해진 데이터 포맷은 없으며 자유롭게 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;단, 여러 시스템 간의 호환성을 위하여 미리 정해진 필드명 등이 있는데, &quot;exp&quot;, &quot;email&quot;, &quot;profile&quot; 등이 있다. 이를 클레임(Claims)이라고 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;등록된 클레임과 공개 클레임 목록은 &lt;a href=&quot;https://www.iana.org/assignments/jwt/jwt.xhtml&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.iana.org/assignments/jwt/jwt.xhtml&lt;/a&gt;&amp;nbsp;링크를 참고한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 등록된 클레임 (Registered Claims)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필수는 아니지만 JWT 사용 시 흔히 사용되는 필드들로서 여러 시스템 간 호환성을 위해 구현하도록 권장된다.&lt;/li&gt;
&lt;li&gt;&quot;iss&quot;, &quot;aud&quot;, &quot;exp&quot; 등이 이에 해당된다.&lt;/li&gt;
&lt;li&gt;하지만 단순 웹서비스 인증 용도를 위해서는 &quot;exp&quot; 정도 외에는 사용할 일이 많이 없을 듯 하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;exp&quot; 필드는 초 단위의 unix timestamp 데이터로서 토큰의 만료기한을 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 공개 클레임 (Public Claims)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JWT에서 흔히 쓰이는 데이터를 위해 필드명을 미리 정해둔 것이다.&lt;/li&gt;
&lt;li&gt;&quot;email&quot;, &quot;profile&quot; 등이 이에 해당된다.&lt;/li&gt;
&lt;li&gt;공개 클레임으로 정해진 필드명이 있다면 되도록 규약에 따르는 것이 권장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 비공개 클레임 (Private Claims)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;등록된 클레임도, 공개 클레임도 아니기 때문에 자유롭게 사용할 수 있는 필드명들이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 사용 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jwt.png&quot; data-origin-width=&quot;1329&quot; data-origin-height=&quot;2056&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zh74q/btrLTBtbpij/UUc5QnpOJTS8iv6IqjTimK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zh74q/btrLTBtbpij/UUc5QnpOJTS8iv6IqjTimK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zh74q/btrLTBtbpij/UUc5QnpOJTS8iv6IqjTimK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZh74q%2FbtrLTBtbpij%2FUUc5QnpOJTS8iv6IqjTimK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;774&quot; data-filename=&quot;jwt.png&quot; data-origin-width=&quot;1329&quot; data-origin-height=&quot;2056&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 ID 및 패스워드를 사용해서 최초 인증을 수행한 뒤 권한 정보가 담긴 JWT 토큰을 획득한다.&lt;/li&gt;
&lt;li&gt;이후 기타 리소스에 접근하기 위한 API 요청을 보낼 때, &quot;Authorization: Bearer&quot; 헤더 등에 JWT 토큰을 함께 전송한다.&lt;/li&gt;
&lt;li&gt;서버는 클라이언트가 함께 전송한 JWT 토큰으로부터 리소스에 대한 접근 권한을 확인한다.&lt;/li&gt;
&lt;li&gt;JWT의 서명이 올바르고, 만료되지 않은 토큰이면서 리소스에 대한 접근 권한이 있다고 판단되면 리소스를 클라이언트에게 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT의 장점 (기존 세션 구조와의 차이)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션 방식의 인증을 사용할 경우 인증 정보를 별도의 인증 DB에 저장해야 한다. 또, 리소스 요청마다 DB를 조회해야 하는 부담이 있다.&lt;/li&gt;
&lt;li&gt;JWT는 무상태(stateless) 토큰으로서 인증 정보를 DB 등에 저장하지 않고 토큰에 포함시키기 때문에, 별도의 저장소가 필요 없다.&lt;/li&gt;
&lt;li&gt;따라서 JWT를 사용하면 DB 부하를 줄이고 요청 응답 시간을 빠르게 할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSA 구조 등에서 응답 시간을 빠르게 할 수 있고, 수천만 명의 유저가 동시 접속할 때에도 수평적 확장이 용이하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 사용 시 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JWT의 페이로드는 일반적으로 암호화되지 않기 때문에, 페이로드 데이터에 민감한 정보를 포함해서는 안된다. (개인정보, 패스워드 등)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평문은 아니어서 사람이 바로 읽을 수는 없지만, Base64 URL 형태로 인코딩된 것이기 때문에 디코딩이 아주 쉽다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT는 데이터에 서명을 할 뿐, (대부분의 경우) 암호화하는 것이 아니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세션을 사용할 경우 서버 측에서 강제로 로그아웃시킬 수 있지만, JWT를 사용할 경우 로그아웃이 힘들다.&lt;/li&gt;
&lt;li&gt;JWT는 토큰의 크기가 상대적으로 크기 때문에 트래픽 오버헤드가 생긴다. 따라서 페이로드 데이터는 되도록 간결해야 한다.&lt;/li&gt;
&lt;li&gt;토큰 유출의 문제 등으로 인해 Refresh Token과 Access Token을 분리하여 사용하기도 한다. 관련 자료는 나중에 정리할 예정.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&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://jwt.io/&quot;&gt;https://jwt.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/JSON_Web_Token&quot;&gt;https://en.wikipedia.org/wiki/JSON_Web_Token&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>인증 &amp;amp; 보안</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/8</guid>
      <comments>https://honeybomb.tistory.com/8#entry8comment</comments>
      <pubDate>Mon, 12 Sep 2022 23:23:17 +0900</pubDate>
    </item>
    <item>
      <title>HMAC의 구조 및 필요성</title>
      <link>https://honeybomb.tistory.com/7</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 통신 등을 할 때, 보안 등의 이유로 통신되는 메시지의 위변조를 막아야 할 경우가 있다.&lt;/li&gt;
&lt;li&gt;이 때 어떠한 토큰을 사용하여 메시지가 위변조가 되지 않았는지 검증하는 기법이 주로 사용되는데, 이 때 사용되는 토큰을 MAC(Message Authentication Code)라고 한다.&lt;/li&gt;
&lt;li&gt;그 중 HMAC(Hash-based MAC)은 SHA256 등의 해시 함수 기반으로 안전하게 MAC을 생성하는 것을 이른다.&lt;/li&gt;
&lt;li&gt;HTTPS, JWT 등 다양한 레벨의 보안 통신 프로토콜에서 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단순히 해시함수만을 사용할 때의 취약점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패스워드와 같이 원본 데이터는 알 수 없되 그 값의 동일성만 비교하는 경우, &lt;code&gt;H(password || salt || secretKey)&lt;/code&gt;와 같이 암호화 후에 DB에 저장하는 것이 보편적이다. (이러한 방식은 패스워드를 탈취하기 위한 &lt;a href=&quot;https://en.wikipedia.org/wiki/Rainbow_table&quot;&gt;Rainbow table 공격&lt;/a&gt;을 효과적으로 방어할 수 있다.)&lt;/li&gt;
&lt;li&gt;하지만 HMAC으로 사용하기에는 위와 같은 한번의 해싱으로는 부족하다. 공격자가 원본 데이터의 값을 알고 있기 때문이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저, &lt;code&gt;H(secretKey || message)&lt;/code&gt;의 경우, 공격자가 비밀키를 몰라도, 원본데이터의 길이와 해시 함수만 안다면 원본 데이터에 악성 데이터를 append하는 동시에 유효한 MAC을 생성할 수 있다고 알려져 있다. 이를 &lt;a href=&quot;https://en.wikipedia.org/wiki/Length_extension_attack&quot;&gt;Length extension 공격&lt;/a&gt;이라고 한다.&lt;/li&gt;
&lt;li&gt;반대로, &lt;code&gt;H(message || secretKey)&lt;/code&gt;와 같이 비밀키를 뒤에 두어 append를 막는다고 하자. 공격자가 비밀키를 몰라도, 대부분의 해시 함수는 &lt;code&gt;H(m1) = H(m2)&lt;/code&gt;라면 &lt;code&gt;H(m1 || key) = H(m2 || key)&lt;/code&gt;를 만족하므로 MD5와 같은 약한 해시함수를 사용할 경우 해시함수 conflict를 이용한 공격에 이론적으로 취약해진다.&lt;/li&gt;
&lt;li&gt;심지어 &lt;code&gt;H(k1 || message || k2) when k1 != k2&lt;/code&gt; 또한 취약점이 있다고 알려져 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;따라서 해싱 함수가 다소 취약하더라도 &lt;span&gt;복잡한 방법으로 해싱하여&lt;span&gt;&amp;nbsp;보안성을 높인 것이&lt;/span&gt;&lt;/span&gt;&amp;nbsp;HMAC이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HMAC 생성 로직&lt;/h2&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;ipad = 0x5c5c5c&amp;hellip;5c5c
opad = 0x363636&amp;hellip;3636
ikeypad = key XOR ipad
okeypad = key XOR opad
output = H(okeypad || H(ikeypad || message))&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위와 같이 두 번 해싱을 수행하면 안전하다고 한다.&lt;/li&gt;
&lt;li&gt;ipad와 opad의 값은 ikeypad와 okeypad 사이의 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%95%B4%EB%B0%8D_%EA%B1%B0%EB%A6%AC&quot;&gt;hamming distance&lt;/a&gt;를 최대화하기 위한 것이라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&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://en.wikipedia.org/wiki/Hash-based_message_authentication_code&quot;&gt;Hash-based message authentication code&lt;/a&gt;, Wikipedia&lt;/li&gt;
&lt;li&gt;Bellare, M., Canetti, R., &amp;amp; Krawczyk, H., &lt;a href=&quot;https://cseweb.ucsd.edu/~mihir/papers/kmd5.pdf&quot;&gt;Keying Hash Functions for Message Authentication&lt;/a&gt;, Annual International Cryptology Conference, 1996&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://crypto.stackexchange.com/questions/15551/why-does-hmac-need-a-fixed-length-padding&quot;&gt;Why does HMAC need a fixed length padding?&lt;/a&gt;, Stack Overflow&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>인증 &amp;amp; 보안</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/7</guid>
      <comments>https://honeybomb.tistory.com/7#entry7comment</comments>
      <pubDate>Wed, 31 Aug 2022 22:00:09 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 코틀린 launch, async 차이점</title>
      <link>https://honeybomb.tistory.com/6</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin 1.5 기준으로 작성된 글입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린 코루틴에서 새로운 경량 쓰레드에서 작업을 생성하기 위해선 &lt;code&gt;launch&lt;/code&gt; 혹은 &lt;code&gt;async&lt;/code&gt;를 사용한다.&lt;/li&gt;
&lt;li&gt;둘의 자세한 차이점 및 사용시 주의사항에 대해 알아보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;TL;DR&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;launch&lt;/code&gt;는 작업을 실행만 시키고 그 결과에 관심이 없으며, 각 job 마다의 오류를 처리할 필요가 없을 경우 사용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;launch&lt;/code&gt; 내에서 발생한 오류는 &lt;code&gt;join()&lt;/code&gt; 메소드 호출 시 catch할 수 없으며, 오류는 부모 job으로 전파된다.&lt;/li&gt;
&lt;li&gt;만약 &lt;code&gt;supervisorScope&lt;/code&gt; 등을 사용하여 부모 job으로 전파되지 않는 경우, 오류는 unhandled exception으로 취급되어 프로그램 실행에 영향을 끼칠 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;async&lt;/code&gt;는 작업 실행의 결과(반환값)를 얻어올 수 있으며 각 job 마다 발생한 오류를 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;작업 실행의 결과 포함 여부&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;code&gt;launch&lt;/code&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;launch&lt;/code&gt;는 &lt;code&gt;Job&lt;/code&gt; 객체를 반환한다.&lt;/li&gt;
&lt;li&gt;반환된 &lt;code&gt;Job&lt;/code&gt;이 완료되기까지 기다려야할 경우 &lt;code&gt;join&lt;/code&gt;을 사용하여 기다릴 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;val job = launch {
    log.info(&quot;Launch!&quot;)
    delay(1000)
}
job.join()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;joinAll()&lt;/code&gt;을 사용하여 여러 job의 완료를 기다릴 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;val job1 = launch {
    log.info(&quot;Launch1&quot;)
    delay(1000)
}
val job2 = launch {
    log.info(&quot;Launch2&quot;)
    delay(1000)
}
joinAll(job1, job2)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;하지만 &lt;code&gt;launch&lt;/code&gt;를 사용하여 반환된 &lt;code&gt;Job&lt;/code&gt;에서는 경량 쓰레드에서 수행된 결과를 직접 얻어올 수 없다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;val job = launch {
    doSomething()
}
job.join() // join()의 반환 타입은 Unit 타입이며, doSomething()에서 실행된 결과를 얻어올 수 없다.&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;code&gt;async&lt;/code&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;async&lt;/code&gt;는 &lt;code&gt;Deferred&lt;/code&gt; 객체를 반환한다.&lt;/li&gt;
&lt;li&gt;반환된 &lt;code&gt;Deferred&lt;/code&gt;가 완료되기까지 기다려야할 경우 &lt;code&gt;await&lt;/code&gt;을 사용하여 기다릴 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;val deferred = async {
    log.info(&quot;Async!&quot;)
    delay(1000)
}
deferred.await()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;awaitAll()&lt;/code&gt;을 사용하여 여러 job의 완료를 기다릴 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;puppet&quot;&gt;&lt;code&gt;val deferred1 = async {
    log.info(&quot;Async1&quot;)
    delay(1000)
}
val deferred2 = async {
    log.info(&quot;Async2&quot;)
    delay(1000)
}
awaitAll(deferred1, deferred2)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;async&lt;/code&gt;를 사용하여 반환된 &lt;code&gt;Deferred&lt;/code&gt;에는 경량 쓰레드에서 수행된 결과가 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;val deferred = async {
    doSomething()
}
deferred.await() // doSomething()에서 실행된 결과가 반환된다.&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;Deferred&lt;/code&gt;는 &lt;code&gt;Job&lt;/code&gt;을 상속하기 때문에, &lt;code&gt;join()&lt;/code&gt; 메소드나 &lt;code&gt;joinAll()&lt;/code&gt;을 사용할 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;오류 전파(Exception Propagation) 방식의 차이&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;code&gt;launch&lt;/code&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;launch&lt;/code&gt;를 사용한 경량 쓰레드에서 발생한 오류는 &lt;b&gt;&lt;code&gt;join()&lt;/code&gt; 메소드에서 catch가 불가능하다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;val job = launch {
    log.info(&quot;Launch!&quot;)
    delay(1000)
    throw RuntimeException(&quot;runtime exception&quot;)
}

try {
    job.join() // Exception이 발생하지 않아 catch에서 오류를 처리할 수 없다!
} catch (err: Exception) {
    log.error(&quot;Error catched from join&quot;, err)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;join()&lt;/code&gt;에서 오류를 던지는 대신, 발생한 오류는 부모 job으로 전파되어 외부 scope에서 catch해야만 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;join()&lt;/code&gt;에서 오류가 발생하는 경우는 &lt;code&gt;Job&lt;/code&gt;이 취소되는 경우 등이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;supervisorScope&lt;/code&gt; 등을 사용하여 부모 job으로 전파되지 않는 경우 경량 쓰레드 내에서 발생한 오류는 unhandled exception으로 취급된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 경우 안드로이드 애플리케이션이 강제 종료된다거나, 웹 애플리케이션에서는 로깅이 누락되는 등 문제가 있을 수 있으니 주의가 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q: 저는 &lt;code&gt;join()&lt;/code&gt; 호출할 때 오류 잘 나던데요?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 코루틴 스코프 내에서 한 경량 쓰레드에서 오류가 발생한 경우, 모든 자식 job은 취소되고 부모 job으로 exception이 전파된다.&lt;/li&gt;
&lt;li&gt;이 때 자식 job이 취소가 되면서 &lt;code&gt;join()&lt;/code&gt; 호출 시 &lt;code&gt;JobCancellationException&lt;/code&gt;가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;code&gt;async&lt;/code&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;async&lt;/code&gt;를 사용한 경우 &lt;code&gt;await()&lt;/code&gt; 메소드를 실행하면서 오류를 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;val deferred = async {
    log.info(&quot;Async!&quot;)
    delay(1000)
    throw RuntimeException(&quot;runtime exception&quot;)
}

try {
    deferred.await() // Exception이 발생하므로 내부 로직 내에서 에러를 처리할 수 있다.
} catch (err: Exception) {
    log.error(&quot;Error catched from await&quot;, err)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 &lt;code&gt;Deferred&lt;/code&gt;는 &lt;code&gt;Job&lt;/code&gt;을 상속하므로 &lt;code&gt;join()&lt;/code&gt; 메소드를 사용할 수 있다고 하였다. &lt;code&gt;await()&lt;/code&gt;이 아니라 &lt;code&gt;join()&lt;/code&gt;을 호출하는 경우 &lt;code&gt;launch&lt;/code&gt;와 마찬가지로 오류가 발생하지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 &lt;code&gt;launch&lt;/code&gt;와 다르게 unhandled exception이 발생하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q: &lt;code&gt;await()&lt;/code&gt; 호출 시 try catch 블럭으로 감쌌는데도 오류가 무시되지 않고 부모 job이 실패해요!&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 코루틴 스코프 내에서 한 경량 쓰레드에서 오류가 발생한 경우, 모든 자식 job은 취소되고 부모 job으로 exception이 전파된다.&lt;/li&gt;
&lt;li&gt;취소 처리를 막고 자식 job의 오류를 수동으로 처리하고 싶은 경우 &lt;code&gt;supervisorScope&lt;/code&gt;를 이용해보자.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Kotlin</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/6</guid>
      <comments>https://honeybomb.tistory.com/6#entry6comment</comments>
      <pubDate>Tue, 23 Aug 2022 23:30:22 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 코틀린의 널 안정성 (Null-Safety)</title>
      <link>https://honeybomb.tistory.com/5</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2022/08 기준이며, Java에 익숙하다는 가정 하에 작성된 자료입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kotlin의 장점으로는 간결성, JVM 호환성, 코루틴 등이 자주 언급되지만 그 중에서도 흔히 언급되는 것은 널 안정성이다.&lt;/li&gt;
&lt;li&gt;Java에서는 프로그래머가 주의 깊게 코드를 작성하지 않으면 NPE(NullPointerException)가 빈번하게 일어난다.&lt;/li&gt;
&lt;li&gt;Kotlin은 언어의 정적 타입 시스템에 null 관련 구조를 추가하였기 때문에 변수가 null일지 아닐지 고민하거나 수많은 Optional 등을 타이핑하지 않고도 효과적으로 NPE를 막을 수 있다.&lt;/li&gt;
&lt;li&gt;기존 Java와 비교하여 어떤 점이 다른지 알아보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 Java에서는?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;String 객체의 레퍼런스는 실제 값을 가질 수도 있고 null 값을 가질 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;String myString = null;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파라미터에 null이 들어올지 아닐지 모르기 때문에 null에 대한 예외 처리가 요구된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public void doSomething(String inputString) {
    if (inputString == null) {
        // ...
    }

    int stringLength = inputString.length();
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 등 라이브러리에선 이러한 예외 처리를 줄이기 위해 &lt;a href=&quot;https://www.baeldung.com/spring-null-safety-annotations&quot;&gt;@NonNull Annotation&lt;/a&gt; 등과 같은 방식으로 non-null 강제를 하고자 하였다.&lt;/li&gt;
&lt;li&gt;다만 Java 언어 자체에서 제공하는 기능은 아니기 때문에 IDE 등에서 경고는 해줄지 모르지만 주의해서 코드를 작성하지 않으면 여전히 NPE가 발생하기 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public void doSomething(@NonNull String inputString) {
    int stringLength = inputString.length();
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 8에서는 &lt;code&gt;Optional&lt;/code&gt;이 추가되어 null일 가능성이 있는 레퍼런스를 직접 사용하기보단 Wrapper로 한번 감싸서 NPE를 줄이고자 하였다.&lt;/li&gt;
&lt;li&gt;하지만 &lt;code&gt;Optional&lt;/code&gt;을 사용하고 말고는 언어 단에서 강제되지 않기 때문에 &lt;code&gt;Optional&lt;/code&gt;이 붙지 않았다고 해서 non-null이라고는 담보할 수 없었다.&lt;/li&gt;
&lt;li&gt;그리고 코드가 비교적 간결하지 못하다는 단점이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public void doSomething(Optional&amp;lt;String&amp;gt; inputStringOptional) {
    String inputString = inputStringOptional.orElse(&quot;default&quot;);
    int stringLength = inputString.length();
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위와 같이 Java 진영에서도 NPE를 막기 위한 여러 도구를 사용하고자 했지만 언어 자체에 녹여내기는 어려웠고 NPE는 여전히 골칫거리였다.&lt;/li&gt;
&lt;li&gt;Kotlin에서는 다소 다른 정적 타입 구조를 도입함으로써 NPE를 근본적으로 방지하고자 하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin의 nullable 타입&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kotlin에서 &lt;code&gt;String&lt;/code&gt;과 같이 타입을 선언한다면, 해당 타입의 레퍼런스에는 &lt;b&gt;null이 들어갈 수 없다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;NPE를 걱정하지 않고 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;물론 글 마지막에서 설명할 예외 케이스가 존재한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun doSomething(inputString: String) {
    val stringLength = inputString.length
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래와 같이 &lt;code&gt;String?&lt;/code&gt;와 같이 타입을 선언한다면, 해당 타입의 레퍼런스에는 null이 들어갈 수 있다.&lt;/li&gt;
&lt;li&gt;JDK 8의 &lt;code&gt;Optional&lt;/code&gt;과 유사하다고 할 수 있다.&lt;/li&gt;
&lt;li&gt;nullable한 타입의 변수에 대해 NPE가 발생할 수 있는 메소드 / 필드에 접근하고자 하면 컴파일 단계에서 오류가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun doSomething(inputString: String?) {
    // 오류 발생!!
    val stringLength = inputString.length
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Safe calls&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nullable한 변수의 메소드나 필드에 접근할 때 변수명에 &lt;code&gt;?&lt;/code&gt;을 붙인 것을 safe call 연산자라고 한다.&lt;/li&gt;
&lt;li&gt;아래 코드에서 &lt;code&gt;inputString?.length&lt;/code&gt;와 같이 접근했을 때, &lt;code&gt;inputString&lt;/code&gt;이 null이 아닌 경우라면 length의 값이 되지만, null이라면 &lt;code&gt;stringLength&lt;/code&gt;에도 null 값이 들어가게 된다.&lt;/li&gt;
&lt;li&gt;자연스럽게 &lt;code&gt;stringLength&lt;/code&gt;의 타입 또한 &lt;code&gt;Int&lt;/code&gt;가 아닌 nullable한 &lt;code&gt;Int?&lt;/code&gt;가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun doSomething(inputString: String?) {
    // stringLength 또한 nullable(Int?) 타입이 된다.
    val stringLength = inputString?.length
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이와 같은 Safe call은 위처럼 여러 겹으로 감싸져있는 필드에 접근할 때 매우 유용하다.&lt;/li&gt;
&lt;li&gt;만약 Java 코드에서 아래처럼 접근하려면 몇개의 if 문 혹은 삼항 연산자가 필요했을지 생각해보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;val memberStatus = response?.member?.status&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/let.html&quot;&gt;&lt;code&gt;let&lt;/code&gt; 함수&lt;/a&gt;를 호출하여 null이 아닐 경우에만 특정 코드를 실행할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun doSomething(inputString: String?) {
    val stringLength = inputString?.length

    // stringLength가 null이 아닐 경우에만 출력이 된다.
    stringLength?.let { println(it) }

    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래와 같이 좌변에서도 safe call을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;만약 좌변의 chain 중 어떤 값이 null일 경우 assignment는 수행되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;request?.member?.status = memberStatus&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;엘비스 연산자 (Elvis Operator, &lt;code&gt;?:&lt;/code&gt;)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java에서 null에 따른 예외 처리 / default 값을 사용하기 위해선 아래와 같이 if 문 혹은 삼항 연산자를 사용하거나...&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public void doSomething(String inputString) {
    int stringLength = inputString != null ? inputString.length() : -1;
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Optional 등을 이용하는 방법이 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public void doSomething(Optional&amp;lt;String&amp;gt; inputStringOptional) {
    int stringLength = inputStringOptional.map(String::length).orElse(-1);
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kotlin에서는 엘비스 연산자 (Elvis Operator, &lt;code&gt;?:&lt;/code&gt;)를 사용하면 위와 동일한 기능을 구현할 수 있다.&lt;/li&gt;
&lt;li&gt;아래 코드에서 &lt;code&gt;stringLength&lt;/code&gt;는 &lt;code&gt;Int?&lt;/code&gt;가 아닌 &lt;code&gt;Int&lt;/code&gt; 타입을 가지게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun doSomething(inputString: String?) {
    val stringLength = inputString?.length ?: -1
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kotlin에서는 &lt;code&gt;return&lt;/code&gt;과 &lt;code&gt;throw&lt;/code&gt; 또한 expression이기 때문에 중간에 null을 반환하거나...&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun doSomething(inputString: String?): String? {
    val stringLength = inputString?.length ?: return null
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오류를 throw할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun doSomething(inputString: String?): String? {
    val stringLength = inputString?.length ?: throw IllegalArgumentException(&quot;input expected&quot;)
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;code&gt;!!&lt;/code&gt; 연산자 (사용 지양)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;!!&lt;/code&gt; 연산자를 사용하면 nullable한 type을 강제로 non-null로 변환할 수 있다.&lt;/li&gt;
&lt;li&gt;아래 코드에서 &lt;code&gt;stringLength&lt;/code&gt;는 &lt;code&gt;Int?&lt;/code&gt;가 아닌 &lt;code&gt;Int&lt;/code&gt; 타입을 가지게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun doSomething(inputString: String?) {
    val stringLength = inputString!!.length
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 때 레퍼런스가 null의 값을 가지고 있다면 NPE가 발생한다.&lt;/li&gt;
&lt;li&gt;당신이 NPE 애호가가 아닌 이상 &lt;code&gt;!!&lt;/code&gt; 연산자는 지양하고 엘비스 연산자(&lt;code&gt;?:&lt;/code&gt;)를 사용하여 별도의 예외처리를 하는 것이 대부분의 경우 적절하다고 생각한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nullable collections&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;filterNotNull()&lt;/code&gt; 메소드를 이용하면 nullable한 collection을 non-null collection으로 안전하게 변경할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;val nullableList: List&amp;lt;Int?&amp;gt; = listOf(1, 2, null, 4)
val intList: List&amp;lt;Int&amp;gt; = nullableList.filterNotNull()&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정말 NPE가 발생하지 않나요?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 글에서 Kotlin이 NPE를 근본적으로 방지하고자 했다고 하지만 정말 NPE가 발생하지 않을까? 당연하겠지만 예외는 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;throw NullPointerException()&lt;/code&gt;으로 NPE를 직접 던지는 NPE 애호가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!!&lt;/code&gt; 연산자를 굳이 사용하는 NPE 애호가&lt;/li&gt;
&lt;li&gt;객체 초기화 시 아직 초기화되지 않은 객체에 잘못된 접근을 하는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 생성자에서는 객체 초기화가 완료되지 않은 &lt;code&gt;this&lt;/code&gt;에 접근할 수 있는데 이 때 외부에서 &lt;code&gt;this&lt;/code&gt;의 속성들에 접근할 경우 NPE가 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;부모 클래스의 생성자에서 자식 클래스의 아직 초기화되지 않은 open 멤버에 접근할 경우&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/null-safety.html#nullable-types-and-non-null-types&quot;&gt;공식 가이드&lt;/a&gt; 참고&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다른 언어의 코드와 함께 사용할 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 코드에서는 null을 미연에 방지할 수 없기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/null-safety.html&quot;&gt;Kotlin Null Safety 공식 가이드&lt;/a&gt;&lt;/p&gt;</description>
      <category>Kotlin</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/5</guid>
      <comments>https://honeybomb.tistory.com/5#entry5comment</comments>
      <pubDate>Sat, 6 Aug 2022 17:47:33 +0900</pubDate>
    </item>
    <item>
      <title>Git, Git Flow, Github Flow</title>
      <link>https://honeybomb.tistory.com/4</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Git&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 관리 시스템(VCS)는 실수 혹은 기타 이유로 인해 코드가 손상되었을 때 손쉽게 복구할 수 있으며, 여러 사람이 협업 시 누가 어떤 부분을 수정하고 이슈를 만들어냈는지 쉽게 파악이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 폴더나 파일을 날짜별로 만드는 것도 일종의 VCS라고 할 수 있지만, 사람이 실수할 여지가 많다. 이를 위해 RCS 등의 로컬 버전 관리 시스템이 개발되었다. 하지만 시스템 외부의 사람들과 함께 협업하기에는 로컬 버전 관리 시스템의 한계가 뚜렷했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 기능을 목적으로 Subversion과 같은 VCS가 만들어졌는데, 이는 중앙집중식 버전 관리 시스템(CVCS)로서 버전 데이터베이스는 오직 한 곳의 서버에 저장되고, 협업자들은 각각 코드를 checkout해서 작업이 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 CVCS 또한 중앙 서버가 다운되거나, 서버에 오류가 생기면 프로젝트에 차질이 생기는 것은 피할 수가 없었다. 프로젝트의 모든 이력이 한 곳에만 있을 경우 생기는 문제는 자명했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DVCS&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리하여 탄생한 것이 Git과 Mercurial과 같은 분산 버전 관리 시스템(DVCS)이다. 사용자가 서버에서 checkout을 한다는 것은 프로젝트의 이력을 로컬로 동기화 및 백업한다는 것이다. 서버가 다운되거나 문제가 생겨도 local의 이력을 복사하면 그만이다. 또 DVCS는 다수의 원격 저장소를 가질 수가 있기 때문에 유연한 프로젝트 관리가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://d2.naver.com/content/images/2015/06/helloworld-1011-3.png&quot; width=&quot;500px&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;DVCS는 분산 환경이라는 특성상, 변경은 시간축에 따른 선형 구조가 아닌 DAG 그래프와 같은 모양새를 갖는다. 이와 같은 구조는 여러 협업자가 다른 사람의 현재 작업 여부에 관계없이 자유로운 작업 후 손쉬운 병합을 가능케 한다. 다만 이러한 DVCS에 경험이 없는 사람이라면 직관적으로 life cycle을 이해하기 어려울 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 mercurial과 git의 차이가 드러나는데, mercurial은 최대한 쉽고 간편하게 쓸 수 있도록 DVCS임에도 기존 Subversion과 유사한 면모를 보인다. 반면에 git은 설계부터 많은 수에 브랜치를 사용하는 데에 중점을 두었으며 n-way merge 등 브랜치를 효과적으로 제어할 수 있다. 당연히 git의 경우 가파른 learning curve를 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 작동 방식에 있어서도 둘 DVCS의 차이가 있다. Git은 각 commit마다 전체 코드 형상을 저장하는 스냅샷 방식인 반면에, Mercurial은 patch 방식, 즉 달라진 부분만 저장하는 방식으로 변경 이력을 저장하기 때문에 일반적으로 git이 disk overhead가 더 크다고 생각할 수 있다. 하지만 git은 어느정도 시간이 흐른 후 garbage collection을 실행하여 최신의 snapshot을 제외한 예전의 이력은 patch 형태로 가공, gzip으로 압축하고 최근의 commit만을 snapshot으로 저장하기 때문에 최신의 commit을 살펴보는 데에는 빠르면서도 저장소의 크기를 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, Git은 변경 이력을 수많은 파일로 쪼개어 저장하는데, Windows를 사용하는 경우 그러한 파일 액세스가 느려 성능이 매우 떨어진다. Git for Windows와 같은 프로젝트에서는 리눅스와는 별도의 파일 캐시 기능을 이용하여 성능을 빠르게 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Git Flow&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://nvie.com/posts/a-successful-git-branching-model/&quot;&gt;http://nvie.com/posts/a-successful-git-branching-model/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://nvie.com/about/&quot;&gt;Vincent Driessen&lt;/a&gt;이 쓴 branching 모델을 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://nvie.com/img/git-model@2x.png&quot; width=&quot;400px&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 널리 사용되던 git의 브랜치 모델을 정리한 것으로서, git의 효율적인 브랜치 관리와 병합을 이용하여 프로젝트를 손쉽게 관리할 수 있다. Feature 개발과 release 및 버그 수정을 분리하여, 각 개발자는 repo의 release cycle을 기다릴 필요 없이 자신이 맡은 역할에 집중할 수 있다는 것이 장점이다. 각 브랜치의 병합 과정을 통해 프로젝트의 life cycle를 한눈에 볼 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 origin에 항상 존재하는 브랜치(main branch)는 &lt;code&gt;origin/master&lt;/code&gt;와 &lt;code&gt;origin/develop&lt;/code&gt; 두 가지가 있다. &lt;code&gt;master&lt;/code&gt;는 항상 production-ready 상태여야 한다. 프로덕션 서버에 훅을 걸어 master에 푸쉬되면 자동으로 실서버에 deploy해도 될 정도를 말한다. &lt;code&gt;develop&lt;/code&gt;은 다음 release를 위한 코드가 모이는 곳이다. 혹자는 'integration branch'라고도 하며 nightly build는 항상 develop을 기반으로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 두 main branch를 보조하는 브랜치, supporting branch에는 다음의 3가지가 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Feature Branches&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 릴리즈 혹은 먼 후의 릴리즈를 위해 특정 기능을 개발하는 브랜치이다. 기능이 완성되기 전까지는 &lt;code&gt;origin&lt;/code&gt;에 남아있다가 &lt;code&gt;develop&lt;/code&gt;에 merge 되거나 혹은 어떤 사정에 의해 버려질 수 있다. 항상 &lt;code&gt;develop&lt;/code&gt; 브랜치를 기반으로 해야하고 마지막엔 &lt;code&gt;develop&lt;/code&gt; 브랜치에 merge 되어야 한다.&lt;/p&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;code&gt;master&lt;/code&gt;, &lt;code&gt;develop&lt;/code&gt;, &lt;code&gt;release-*&lt;/code&gt;, &lt;code&gt;hotfix-*&lt;/code&gt; 를 제외한 그 어떤 이름이라도 좋다. Feature branch는 보통 origin에 없고 개발자의 레포에 존재한다. &lt;code&gt;develop&lt;/code&gt;에 병합할 때에는 반드시 &lt;code&gt;git merge --no-ff&lt;/code&gt;를 사용하자. Fast forward가 가능하면 merge commit이 남지 않는데, feature branch의 기록을 남기기 위해 fast forward를 막고 항상 commit을 남긴다.&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;code&gt;develop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;병합 브랜치: &lt;code&gt;develop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;네이밍 컨벤션: &lt;code&gt;master&lt;/code&gt;, &lt;code&gt;develop&lt;/code&gt;, &lt;code&gt;release-*&lt;/code&gt;, &lt;code&gt;hotfix-*&lt;/code&gt; 제외한 그 어떤 이름(&lt;code&gt;feature-*&lt;/code&gt; 또한 많이 쓰임)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ git checkout -b myfeature develop&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Release Branches&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 릴리즈를 위해 코드 메타데이터(버전 넘버 등)을 수정하고 마이너 버그를 고칠 수 있는 브랜치이다. 기존의 release candidate이라고 볼 수 있다.&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;code&gt;develop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;병합 브랜치: &lt;code&gt;develop&lt;/code&gt;, &lt;code&gt;master&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;네이밍 컨벤션: &lt;code&gt;release-*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ git checkout -b release-1.2 develop
Switched to a new branch &quot;release-1.2&quot;
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m &quot;Bumped version number to 1.2&quot;
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴리즈를 위한 메타 데이터를 수정하고 마이너 버그를 모두 고쳐 릴리즈할 준비가 되었을 때 &lt;code&gt;develop&lt;/code&gt;과 &lt;code&gt;master&lt;/code&gt;브랜치에 병합한다. &lt;code&gt;master&lt;/code&gt; 브랜치를 기반으로 태그를 생성한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 해당 브랜치를 삭제한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hotfix Branches&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hotfix 브랜치는 새로운 프로덕션 릴리즈를 준비하는 브랜치라는 점에서 release 브랜치와 유사하지만, 기존에 계획되지 않는다는 점이 다르다. 프로덕션 시스템이 잘 작동하지 않을 때 즉시 대응하기 위해 만들어진다. 기존 develop 브랜치는 hotfix의 진행상황을 신경쓰지 않고 계속 개발할 수 있다는 것이 장점이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 브랜치: &lt;code&gt;master&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;병합 브랜치: &lt;code&gt;develop&lt;/code&gt;, &lt;code&gt;master&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;네이밍 컨벤션: &lt;code&gt;hotfix-*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 브랜치가 master branch라는 점을 제외하면, 작업과 병합 과정은 release branch와 동일하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Git Flow Extension&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 git flow를 손쉽게 사용할 수 있도록 구현한 extension이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/petervanderdoes/gitflow-avh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/petervanderdoes/gitflow-avh&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1652176855236&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - petervanderdoes/gitflow-avh: AVH Edition of the git extensions to provide high-level repository operations for Vincent &quot; data-og-description=&quot;AVH Edition of the git extensions to provide high-level repository operations for Vincent Driessen's branching model - GitHub - petervanderdoes/gitflow-avh: AVH Edition of the git extensions to...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/petervanderdoes/gitflow-avh&quot; data-og-url=&quot;https://github.com/petervanderdoes/gitflow-avh&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SPOs7/hyOkSMQvt1/uji3OcjHCSgdHy1KOzLHWK/img.png?width=1200&amp;amp;height=600&amp;amp;face=982_121_1064_211&quot;&gt;&lt;a href=&quot;https://github.com/petervanderdoes/gitflow-avh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/petervanderdoes/gitflow-avh&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SPOs7/hyOkSMQvt1/uji3OcjHCSgdHy1KOzLHWK/img.png?width=1200&amp;amp;height=600&amp;amp;face=982_121_1064_211');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - petervanderdoes/gitflow-avh: AVH Edition of the git extensions to provide high-level repository operations for Vincent&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AVH Edition of the git extensions to provide high-level repository operations for Vincent Driessen's branching model - GitHub - petervanderdoes/gitflow-avh: AVH Edition of the git extensions to...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mac OS에서는 homebrew를 이용해 아래와 같이 설치할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ brew install git-flow-avh&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 기존 git repo에서 git flow에 맞게 브랜치 생성
$ git flow init
# develop에서 새 feature 브랜치를 생성 후 전환
$ git flow feature start MYFEATURE
# MYFEATURE 브랜치를 develop에 병합 후 삭제
$ git flow feature finish MYFEATURE      
# origin에 MYFEATURE를 게시하여 공동 작업자와 공유
$ git flow feature publish MYFEATURE
# origin에서 MYFEATURE를 pull
$ git flow feature pull MYFEATURE
# develop(혹은 BASE commit)에서 새 release 브랜치를 생성 후 전환
$ git flow release start RELEASE [BASE]
# release 공유
$ git flow release publish RELEASE
# release를 master에 병합 뒤 태그 생성. develop에 재병합 뒤 삭제
$ git flow release finish RELEASE
# 태그는 별도로 push해야 함
$ git push --tags
# hotfix start
$ git flow hotfix start VERSION [BASE]
# hotfix finish
$ git flow hotfix finish VERSION&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Github Flow&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Flow가 등장한 시기(2010)에는 유용한 브랜치 모델이고 실제로 많이 쓰이던 전략이었지만,&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 data-ke-size=&quot;size16&quot;&gt;Github의 Pull Request를 대중적으로 사용한다.&lt;/li&gt;
&lt;li data-ke-size=&quot;size16&quot;&gt;Web App이 유행하게 되면서 여러 버전을 동시에 관리하는 경우가 거의 없다.&lt;/li&gt;
&lt;li data-ke-size=&quot;size16&quot;&gt;Continuous Delivery를 통해 자동 배포를 수행한다.&lt;/li&gt;
&lt;li data-ke-size=&quot;size16&quot;&gt;복잡한 브랜치 기반의 롤백이 불필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 Github Flow를 많이 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;master 브랜치 하나만을 고정하여 사용하고 나머지 브랜치는 feature 브랜치와 같이 자유롭게 사용하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github의 PR 기능을 적극 활용하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.github.com/en/get-started/quickstart/github-flow&quot;&gt;https://docs.github.com/en/get-started/quickstart/github-flow&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1652182084605&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;GitHub flow - GitHub Docs&quot; data-og-description=&quot;Introduction GitHub flow is a lightweight, branch-based workflow. The GitHub flow is useful for everyone, not just developers. For example, here at GitHub, we use GitHub flow for our site policy, documentation, and roadmap. Prerequisites To follow GitHub f&quot; data-og-host=&quot;docs.github.com&quot; data-og-source-url=&quot;https://docs.github.com/en/get-started/quickstart/github-flow&quot; data-og-url=&quot;https://ghdocs-prod.azurewebsites.net/en/get-started/quickstart/github-flow&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BWfDd/hyOmtEI8zq/hy1kYFoMhWi29qBwKxZXUk/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/b3j7lo/hyOmyeYt22/OCjDyoXdDl2U01QkJXH841/img.png?width=1722&amp;amp;height=1232&amp;amp;face=0_0_1722_1232,https://scrap.kakaocdn.net/dn/bqkQfX/hyOmvvMh7p/cligH0tAcuJ7cTWkJhSRFk/img.png?width=1790&amp;amp;height=448&amp;amp;face=0_0_1790_448&quot;&gt;&lt;a href=&quot;https://docs.github.com/en/get-started/quickstart/github-flow&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.github.com/en/get-started/quickstart/github-flow&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BWfDd/hyOmtEI8zq/hy1kYFoMhWi29qBwKxZXUk/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/b3j7lo/hyOmyeYt22/OCjDyoXdDl2U01QkJXH841/img.png?width=1722&amp;amp;height=1232&amp;amp;face=0_0_1722_1232,https://scrap.kakaocdn.net/dn/bqkQfX/hyOmvvMh7p/cligH0tAcuJ7cTWkJhSRFk/img.png?width=1790&amp;amp;height=448&amp;amp;face=0_0_1790_448');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub flow - GitHub Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Introduction GitHub flow is a lightweight, branch-based workflow. The GitHub flow is useful for everyone, not just developers. For example, here at GitHub, we use GitHub flow for our site policy, documentation, and roadmap. Prerequisites To follow GitHub f&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github flow와 유사하지만 production 브랜치를 따로 두는 GitLab flow 또한 있다.&lt;/p&gt;</description>
      <category>개발 및 협업</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/4</guid>
      <comments>https://honeybomb.tistory.com/4#entry4comment</comments>
      <pubDate>Tue, 10 May 2022 19:01:40 +0900</pubDate>
    </item>
    <item>
      <title>React Hook을 이용하여 카카오 애드핏(Adfit) 연동하기</title>
      <link>https://honeybomb.tistory.com/3</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 17 버전을 기준으로 작성된 글입니다. (2022/04)&lt;/p&gt;
&lt;h1&gt;TL;DR&lt;/h1&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// KakaoAdfit.jsx
import {useEffect, useRef} from &quot;react&quot;;

function KakaoAdFit({unit, width, height, disabled}: any) {
    const scriptElementWrapper = useRef(null);

    useEffect(() =&amp;gt; {
        if (!disabled) {
            const script = document.createElement(&quot;script&quot;);
            script.setAttribute(&quot;src&quot;, &quot;https://t1.daumcdn.net/kas/static/ba.min.js&quot;);
            scriptElementWrapper.current.appendChild(script);

            return () =&amp;gt; {
                const globalAdfit = window.adfit;
                if (globalAdfit) globalAdfit.destroy(unit);
            }
        }
    }, [])

    return &amp;lt;div ref={scriptElementWrapper}&amp;gt;
        &amp;lt;ins className=&quot;kakao_ad_area&quot; style={{display: &quot;none&quot;}}
             data-ad-unit={unit}
             data-ad-width={width}
             data-ad-height={height}&amp;gt;&amp;lt;/ins&amp;gt;
    &amp;lt;/div&amp;gt;
}

export default KakaoAdFit;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 사용 예
&amp;lt;KakaoAdFit unit={&quot;DAN-MyExampleAdUnit&quot;} width={&quot;320&quot;} height={&quot;100&quot;} disabled={false}/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Try #1 : Naive HTML&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오 애드핏에서 광고 단위를 발급 받으면, 가이드에서는 다음과 같은 형태의 HTML을 원하는 광고 위치에 삽입하도록 안내하고 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;ins class=&quot;kakao_ad_area&quot; style=&quot;display:none;&quot;
 data-ad-unit    = &quot;DAN-MyExampleAdUnit&quot; 
 data-ad-width   = &quot;320&quot; 
 data-ad-height  = &quot;100&quot;&amp;gt;&amp;lt;/ins&amp;gt; 
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;//t1.daumcdn.net/kas/static/ba.min.js&quot; async&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적당히 wrapping만 해주면 되지 않을까? 생각해서 아래와 같이 컴포넌트를 만들어 봅니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function KakaoAdFit({unit, width, height}: any) {
    return &amp;lt;&amp;gt;
        &amp;lt;ins className=&quot;kakao_ad_area&quot; style={{display: &quot;none&quot;}}
             data-ad-unit={unit}
             data-ad-width={width}
             data-ad-height={height}&amp;gt;&amp;lt;/ins&amp;gt;
        &amp;lt;script type=&quot;text/javascript&quot; src=&quot;//t1.daumcdn.net/kas/static/ba.min.js&quot; async&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/&amp;gt;
}

export default KakaoAdFit;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 작동했다면 이 글을 쓸 필요가 없었겠죠???&lt;br /&gt;어느정도 예상했지만 광고가 노출되지 않습니다.&lt;/p&gt;
&lt;h1&gt;Try #2: &lt;code&gt;createElement&lt;/code&gt;를 사용한 스크립트 동적 생성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX 문법에서는 &lt;code&gt;script&lt;/code&gt; 태그를 사용할 수 없기 때문에, 위와 같이 &lt;code&gt;script&lt;/code&gt; 태그를 추가하여도 HTML에 반영이 되지 않습니다.&lt;br /&gt;HTML로 선언하는 것이 아닌, 동적으로 스크립트를 생성하는 방법으로 우회해봅시다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/hooks-reference.html#useref&quot;&gt;Ref&lt;/a&gt;를 사용하면 low-level HTML element에 접근할 수 있습니다.&lt;br /&gt;컴포넌트가 mount 된 후 최초 한 번 동적 &lt;code&gt;script&lt;/code&gt; element를 생성하여, 감싸고 있는 &lt;code&gt;div&lt;/code&gt;에 추가해봅시다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// KakaoAdfit.jsx
import {useEffect, useRef} from &quot;react&quot;;

function KakaoAdFit({unit, width, height}: any) {
    const scriptElementWrapper = useRef(null);

    useEffect(() =&amp;gt; {
        const script = document.createElement(&quot;script&quot;);
        script.setAttribute(&quot;src&quot;, &quot;https://t1.daumcdn.net/kas/static/ba.min.js&quot;);
        scriptElementWrapper.current.appendChild(script);
    }, [])

    return &amp;lt;div ref={scriptElementWrapper}&amp;gt;
        &amp;lt;ins className=&quot;kakao_ad_area&quot; style={{display: &quot;none&quot;}}
             data-ad-unit={unit}
             data-ad-width={width}
             data-ad-height={height}&amp;gt;&amp;lt;/ins&amp;gt;
    &amp;lt;/div&amp;gt;
}

export default KakaoAdFit;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는... 잘 나오는 것 같은데요.&lt;br /&gt;&lt;br /&gt;문제는 광고가 없는 다른 페이지로 갔다가 다시 원래의 페이지로 돌아오면 광고가 표시되지 않습니다.&lt;br /&gt;&lt;br /&gt;참고: 위와 같이 동적으로 script를 생성할 때, &lt;code&gt;async&lt;/code&gt; attribute는 필요 없는 것으로 보입니다.&lt;br /&gt;&lt;a href=&quot;https://stackoverflow.com/questions/41289602/add-defer-or-async-attribute-to-dynamically-generated-script-tags-via-javascript&quot;&gt;Stack Overflow: Add defer or async attribute to dynamically generated script tags via JavaScript&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Try #3: destroy 구현&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 한번 로딩이 된 광고 단위에 대해 스크립트를 다시 로딩한다고 하더라도 광고가 다시 갱신되지 않는 것이 원인으로 보입니다.&lt;br /&gt;리액트에서 페이지가 이동되어도 애드핏에서는 인지할 수 없으니까요.&lt;br /&gt;기존에 노출되었던 광고 단위를 삭제하거나 정리하는 기능이 있으면 좋을텐데요...&lt;br /&gt;&lt;br /&gt;애드핏 스크립트를 로딩하면 global에 &lt;code&gt;adfit&lt;/code&gt;이라는 인스턴스가 생성됩니다.&lt;br /&gt;디버거를 확인하면 해당 인스턴스에 &lt;code&gt;destroy&lt;/code&gt; 메소드가 존재하는 것을 확인할 수 있습니다.&lt;br /&gt;&lt;br /&gt;해당 &lt;code&gt;destroy&lt;/code&gt; 함수를 호출해서 컴포넌트가 unmount될 때 기존 광고 단위를 정리해주도록 합시다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// KakaoAdfit.jsx
import {useEffect, useRef} from &quot;react&quot;;

function KakaoAdFit({unit, width, height, disabled}: any) {
    const scriptElementWrapper = useRef(null);

    useEffect(() =&amp;gt; {
        if (!disabled) {
            const script = document.createElement(&quot;script&quot;);
            script.setAttribute(&quot;src&quot;, &quot;https://t1.daumcdn.net/kas/static/ba.min.js&quot;);
            scriptElementWrapper.current.appendChild(script);

            return () =&amp;gt; {
                const globalAdfit = window.adfit;
                if (globalAdfit) globalAdfit.destroy(unit);
            }
        }
    }, [])

    return &amp;lt;div ref={scriptElementWrapper}&amp;gt;
        &amp;lt;ins className=&quot;kakao_ad_area&quot; style={{display: &quot;none&quot;}}
             data-ad-unit={unit}
             data-ad-width={width}
             data-ad-height={height}&amp;gt;&amp;lt;/ins&amp;gt;
    &amp;lt;/div&amp;gt;
}

export default KakaoAdFit;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이제 페이지를 이동하여도 광고가 잘 갱신되는 걸 확인할 수 있습니다.&lt;/p&gt;</description>
      <category>React</category>
      <author>베꺼</author>
      <guid isPermaLink="true">https://honeybomb.tistory.com/3</guid>
      <comments>https://honeybomb.tistory.com/3#entry3comment</comments>
      <pubDate>Sun, 17 Apr 2022 18:10:55 +0900</pubDate>
    </item>
  </channel>
</rss>