<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Stay hungry, Stay foolish</title>
    <link>https://jake2.tistory.com/</link>
    <description>Frangz - AI와 일본어학습 쉽고 재밌게!
문의 이메일 : frangz.official.13@gmail.com</description>
    <language>ko</language>
    <pubDate>Fri, 26 Jun 2026 13:54:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Jake2</managingEditor>
    <image>
      <title>Stay hungry, Stay foolish</title>
      <url>https://tistory1.daumcdn.net/tistory/4554724/attach/1719534a4512466c9f646c4635510d60</url>
      <link>https://jake2.tistory.com</link>
    </image>
    <item>
      <title>encoding, UTF-8, base-64 가 뭐고 왜 쓰는 것일까?</title>
      <link>https://jake2.tistory.com/entry/encoding-%EC%9D%B4%EB%AD%90%EC%A7%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발을 하다가 한글이 이렇게 깨지는 것을 본적이 있으신가요?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-14 오후 5.13.18.png&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFG6SF/btrRdaKtxO1/aVVgXwhkFi9QuXjb2DDRj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFG6SF/btrRdaKtxO1/aVVgXwhkFi9QuXjb2DDRj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFG6SF/btrRdaKtxO1/aVVgXwhkFi9QuXjb2DDRj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFG6SF%2FbtrRdaKtxO1%2FaVVgXwhkFi9QuXjb2DDRj1%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;989&quot; height=&quot;157&quot; data-filename=&quot;스크린샷 2022-11-14 오후 5.13.18.png&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 encoding 설정이 잘못되어있기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 encoding이 무엇인지 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;encoding 이란&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터는 기본적으로 정보를 0, 1 Binary로 정보를 읽고 쓰고 하는데 우리 휴먼들은 01011101110000111 이걸 가지고 읽어보라고 하면.. 대략 난감할텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 숫자, 문자를 아래와 같이 binary로 치환된 문자열 set 을 가지고 컴퓨터는 정보를 읽고 쓰는거라고 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-14 오후 5.20.08.png&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/veBp1/btrRb0aODfD/fBpx01JlpfMH8sQvB0KcT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/veBp1/btrRb0aODfD/fBpx01JlpfMH8sQvB0KcT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/veBp1/btrRb0aODfD/fBpx01JlpfMH8sQvB0KcT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FveBp1%2FbtrRb0aODfD%2FfBpx01JlpfMH8sQvB0KcT1%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;535&quot; height=&quot;358&quot; data-filename=&quot;스크린샷 2022-11-14 오후 5.20.08.png&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 문자를 컴퓨터가 알아볼 수 있는 binary 로 바꿔주는 것을 &lt;b&gt;문자 encoding &lt;/b&gt;이라고 합니다&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇다면 문자가 깨지는 이유는 뭘까?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-14 오후 5.23.19.png&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;1275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NGsR8/btrQ9bjy182/OsQ9KhJiZgR9ik3ccnigS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NGsR8/btrQ9bjy182/OsQ9KhJiZgR9ik3ccnigS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NGsR8/btrQ9bjy182/OsQ9KhJiZgR9ik3ccnigS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNGsR8%2FbtrQ9bjy182%2FOsQ9KhJiZgR9ik3ccnigS1%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;477&quot; height=&quot;564&quot; data-filename=&quot;스크린샷 2022-11-14 오후 5.23.19.png&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;1275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위키에서 문자 인코딩을 찾아보면 종류가 엄청 많군요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초창기에는 미국에서 만든 ASCII 문자열 set이 표준이었는데 이 문자열 set으로는 한글이나 여타 외국어들을 encoding 할 수 없었는데요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 각 언어별 문자셋 춘추전국시대가 생긴것입니다.&lt;br /&gt;하지만 이 춘추전국시대를 통일할 표준 문자셋이 필요했고 이를 위해 Unicode 가 나오게 된것입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;UTF-8은 뭘까요?&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-14 오후 5.30.56.png&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWGPnP/btrRaDGU481/KHl01RJdgucdcYmtA1JaNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWGPnP/btrRaDGU481/KHl01RJdgucdcYmtA1JaNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWGPnP/btrRaDGU481/KHl01RJdgucdcYmtA1JaNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWGPnP%2FbtrRaDGU481%2FKHl01RJdgucdcYmtA1JaNK%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;403&quot; data-filename=&quot;스크린샷 2022-11-14 오후 5.30.56.png&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unicode는 위의 이미지같은 문자셋이고, 이 Unicode를 컴퓨터에 Binary로 어떻게 치환해서 메모리를 할당하는지 encoding하는 방법중 하나가 UTF-8입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한글을 사용할 수 있는 표준 encoding에는 UCS-2, UCS-4, UTF-32, UTF-16, UTF-8 등 다양한 방식이 있는데 그 중 UTF-8이 다른 encoding 방식들 보다 평균적으로 적은 메모리를 사용하고 호환문제도 가장 덜한 UTF-8이&amp;nbsp; 전세계적으로 가장 많이 사용되는것입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Base64&amp;nbsp;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Base64 라는 것도 들어보셨나요? base64 란 Binary Data를 역으로 ASCII 영역의 문자열로 인코딩하는 방식을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;base 64 라는걸 글자 그대로 보면 64진법 이라는 뜻을 가지는데 이는 2의 6제곱 64개의 ASCII 문자로 표현함을 뜻합니다. 이 64개의 character set은 A-Z a-z 0-9 의 62개 character 와 나머지 2개의 기호로 이루어져 있으며 이 2개의 기호에 따라 종류별 차이가 나뉩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Base64에서 알아야 할것은 인코딩된 텍스트에 ~~= 와 같이 &quot;=&quot; 기호로 끝나게 되는 문자열들을 볼 수 있는데 이는 부족한 bit수를 =로 채워 나타내는 padding 문자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 왜 bit 수가 부족한 것일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변환과정을 살펴보자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 64개의 character set으로 모든 문자열을 표현하자면 2의 6제곱 즉 6bit 가 필요합니다. 하지만 일반적으로 ASCII 문자는 8bit로 구성되어 있습니다. 그렇기 때문에 Base64 인코딩을 위해 binary data를 6bit 씩 묶고자 6과 8의 최소공배수인 24 bit 씩 끊어 묶도록 하기 때문에 변환과정에서 공백이 발생하게 되는것입니다. 사실 여기까지 봐도 무슨 말인가 싶죠.&lt;/p&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://en.wikipedia.org/wiki/Base64&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;Man이라는 단어를 base64 인코딩 하면 아래와 같습니다.&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: 142.344px;&quot;&gt;Text content&lt;/td&gt;
&lt;td style=&quot;width: 216.188px;&quot; colspan=&quot;8&quot;&gt;&lt;b&gt;M&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 219.094px;&quot; colspan=&quot;8&quot;&gt;&lt;b&gt;a&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 213.375px;&quot; colspan=&quot;8&quot;&gt;&lt;b&gt;n&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 142.344px;&quot;&gt;ASCII&lt;/td&gt;
&lt;td style=&quot;width: 216.188px;&quot; colspan=&quot;8&quot;&gt;77&lt;/td&gt;
&lt;td style=&quot;width: 219.094px;&quot; colspan=&quot;8&quot;&gt;97&lt;/td&gt;
&lt;td style=&quot;width: 213.375px;&quot; colspan=&quot;8&quot;&gt;110&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 142.344px;&quot;&gt;Bit pattern&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 13.6016px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 10.6953px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 13.6953px;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 142.344px;&quot;&gt;Index&lt;/td&gt;
&lt;td style=&quot;width: 157.891px;&quot; colspan=&quot;6&quot;&gt;19&lt;/td&gt;
&lt;td style=&quot;width: 157.891px;&quot; colspan=&quot;6&quot;&gt;22&lt;/td&gt;
&lt;td style=&quot;width: 160.797px;&quot; colspan=&quot;6&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 155.078px;&quot; colspan=&quot;6&quot;&gt;46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 142.344px;&quot;&gt;Base64-Encoded&lt;/td&gt;
&lt;td style=&quot;width: 157.891px;&quot; colspan=&quot;6&quot;&gt;&lt;b&gt;T&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 157.891px;&quot; colspan=&quot;6&quot;&gt;&lt;b&gt;W&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 160.797px;&quot; colspan=&quot;6&quot;&gt;&lt;b&gt;F&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 155.078px;&quot; colspan=&quot;6&quot;&gt;&lt;b&gt;u&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Man의 ASCII binary 값이 010011 / 010110 / 000101 / 101110 과 같이 6bit 씩 쪼개어져서 Base64로 인코딩 되는 과정을 거치죠&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;하지만 Ma 만 본다면요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8bit x 2 로 16 bit이고 6bit씩 자르면 4bit 가 남게됩니다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%;&quot;&gt;Text content&lt;/td&gt;
&lt;td style=&quot;width: 27.4419%;&quot; colspan=&quot;8&quot;&gt;&lt;b&gt;M&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7907%;&quot; colspan=&quot;8&quot;&gt;&lt;b&gt;a&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.093%;&quot; colspan=&quot;8&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%;&quot;&gt;ASCII&lt;/td&gt;
&lt;td style=&quot;width: 27.4419%;&quot; colspan=&quot;8&quot;&gt;77&lt;/td&gt;
&lt;td style=&quot;width: 27.7907%;&quot; colspan=&quot;8&quot;&gt;97&lt;/td&gt;
&lt;td style=&quot;width: 27.093%;&quot; colspan=&quot;8&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%;&quot;&gt;Bit pattern&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 4.4186%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 2.09302%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 3.25581%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 3.60465%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%;&quot;&gt;Index&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%;&quot; colspan=&quot;6&quot;&gt;19&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%;&quot; colspan=&quot;6&quot;&gt;22&lt;/td&gt;
&lt;td style=&quot;width: 20.9302%;&quot; colspan=&quot;6&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 20.2326%;&quot; colspan=&quot;6&quot;&gt;padding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%;&quot;&gt;Base64-Encoded&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%;&quot; colspan=&quot;6&quot;&gt;&lt;b&gt;T&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.5814%;&quot; colspan=&quot;6&quot;&gt;&lt;b&gt;W&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.9302%;&quot; colspan=&quot;6&quot;&gt;&lt;b&gt;E&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.2326%;&quot; colspan=&quot;6&quot;&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;p data-ke-size=&quot;size16&quot;&gt;이렇게 4bit 가 벙 떠버렸네요 이렇게 남는 4bit의 자리는 0으로 채우게 되고 index 4에 해당하는 문자열 E 로 인코딩 되는것이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 마지막에 24bit 중 남은 6bit 를 위해 padding 문자인 &quot;=&quot; 로 채워주게 되는겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이러한 변환과정 때문에 base64로 인코딩한 파일은 원본보다 사이즈가 커지게 됩니다.&lt;/p&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;그렇다면 왜 굳이 base64를 사용 하는것일까요&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;ASCII 로 인코딩하여 전송한다면 ASCII는 7bits encoding으로 나머지 1bit를 처리하는 방식이 시스템별로 상이하며 일부 제어문자도 시스템별로 다른 코드값을 갖게 됩니다. 하지만 Base64의 경우에는 ASCII 중 제어문자와 일부 특수문자를 제외한 53개의 안전한 출력 문자만 이용하므로 데이터 전달과정에 있어 누락없이 데이터를 안전하게 전송할 수 있게되는것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Base64&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://en.wikipedia.org/wiki/Base64&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%B2%A0%EC%9D%B4%EC%8A%A464&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.wikipedia.org/wiki/%EB%B2%A0%EC%9D%B4%EC%8A%A464&lt;/a&gt;&lt;/p&gt;</description>
      <category>CS 지식</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/63</guid>
      <comments>https://jake2.tistory.com/entry/encoding-%EC%9D%B4%EB%AD%90%EC%A7%80#entry63comment</comments>
      <pubDate>Thu, 10 Nov 2022 14:46:35 +0900</pubDate>
    </item>
    <item>
      <title>[codility] MaxCounters</title>
      <link>https://jake2.tistory.com/entry/codility-MaxCounters</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 코테를 봐야 할 일들이 많이 생겨서 부랴부랴 코테 공부를 하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복적으로 풀다보면 깨닫은 것 중 하나가 for loop 중 최댓값 or 최솟값 등을 체크 해야하는 문제들이 더러 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;loop 안에서 arr 전체에 max or min 메서드를 사용하면 O(n**2) 이 되어 버립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런경우는 이 전 max or min_cnt 등의 변수에 저장시킨 값과 현재 loop를 돌며 나온 cnt의 값을 비교시키는 방법을 사용하면 효율을 개선시킬 수 있습니다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제랑 코드 보겠습니다.&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You are given N counters, initially set to 0, and you have two possible operations on them:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;increase(X)&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;minus; counter X is increased by 1,&lt;/li&gt;
&lt;li&gt;&lt;i&gt;max counter&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;minus; all counters are set to the maximum value of any counter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A non-empty array A of M integers is given. This array represents consecutive operations:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;if A[K] = X, such that 1 &amp;le; X &amp;le; N, then operation K is increase(X),&lt;/li&gt;
&lt;li&gt;if A[K] = N + 1 then operation K is max counter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example, given integer N = 5 and array A such that:&lt;/p&gt;
A[0] = 3 A[1] = 4 A[2] = 4 A[3] = 6 A[4] = 1 A[5] = 4 A[6] = 4
&lt;p data-ke-size=&quot;size16&quot;&gt;the values of the counters after each consecutive operation will be:&lt;/p&gt;
(0, 0, 1, 0, 0) (0, 0, 1, 1, 0) (0, 0, 1, 2, 0) (2, 2, 2, 2, 2) (3, 2, 2, 2, 2) (3, 2, 2, 3, 2) (3, 2, 2, 4, 2)
&lt;p data-ke-size=&quot;size16&quot;&gt;The goal is to calculate the value of every counter after all operations.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write a function:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;def solution(N, A)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;that, given an integer N and a non-empty array A consisting of M integers, returns a sequence of integers representing the values of the counters.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Result array should be returned as an array of integers.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example, given:&lt;/p&gt;
A[0] = 3 A[1] = 4 A[2] = 4 A[3] = 6 A[4] = 1 A[5] = 4 A[6] = 4
&lt;p data-ke-size=&quot;size16&quot;&gt;the function should return [3, 2, 2, 4, 2], as explained above.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write an&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;b&gt;efficient&lt;/b&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;algorithm for the following assumptions:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;N and M are integers within the range [&lt;span&gt;1&lt;/span&gt;..&lt;span&gt;100,000&lt;/span&gt;];&lt;/li&gt;
&lt;li&gt;each element of array A is an integer within the range [&lt;span&gt;1&lt;/span&gt;..&lt;span&gt;N + 1&lt;/span&gt;].&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1666095808441&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# O(n**2)

def solution(N, A):
    counters = [0 for _ in range(N)]
    

    for i in A:
        if i &amp;lt;= N:
            counters[i-1] += 1
        else:
            max_counter = max(counters)
            counters = [max_counter for _ in counters]

    return counters&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 맨 처음 의식의 흐름대로 써내려간 풀이&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1666097934143&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(N, A):

    counters = N * [0]
    next_max_counter =  max_counter = 0

    for i in A:
        if i &amp;lt;= N:
            current_counter = counters[i-1] = max(counters[i-1] +1, max_counter+1)
            next_max_counter = max(current_counter, next_max_counter)
        else:
            max_counter = next_max_counter

    return [c if c &amp;gt; max_counter else max_counter for c in counters]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 다른 사람의 풀이&amp;nbsp; .....  &amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘, 자료구조</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/58</guid>
      <comments>https://jake2.tistory.com/entry/codility-MaxCounters#entry58comment</comments>
      <pubDate>Tue, 18 Oct 2022 22:02:10 +0900</pubDate>
    </item>
    <item>
      <title>Python - GIL(Global Interpreter Lock)</title>
      <link>https://jake2.tistory.com/entry/%EC%8A%88%EB%B0%94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어느날 파이썬으로 프로그래밍을 하게된 날, 개발 공부를 python으로 시작했다고 말씀드리니 CTO 님께서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GIL에 대해 아는지 여쭤보시더군요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;?? GIL ??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I don'know what is it..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;But, That word is rings a bell...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GIL 이라는 단어 파이썬 공부를 했다면 뭔가 설명은 못하더라도 들어본것 같기는 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 GIL 모르면 파이썬 모르는거다 라고 까지 말씀을 해주시니 자세히 알아봐야겠다라고 생각했는데,&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 GIL은 Global Intrepreter Lock의 약자인데 Lock은 여러 워커가 동시에 동일한 자원에 접근을 할 때 Violation 이 발생 할 수 있는상황을 예방 하기 위해 통제를 하는것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 여러 쓰레드(혹은 프로세스) 에서 동시에 접근 하는 영역을 Critical Section 이라고 표현을 하구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Critical Section에 해당되는 자원에 Lock / Thread Safe 한 작업을 해줘야 합니다.&lt;/p&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&gt;Python&lt;/span&gt;이&lt;span&gt; gabage collection&amp;nbsp;&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;위해서&lt;span&gt; &lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;방식은&lt;span&gt; Reference Counting &lt;/span&gt;이라는&lt;span&gt; &lt;/span&gt;방식을&lt;span&gt; &lt;/span&gt;사용하는데&lt;span&gt; &lt;/span&gt;이게&lt;span&gt; &lt;/span&gt;뭐냐면&lt;span&gt; &lt;/span&gt;변수를&lt;span&gt; &lt;/span&gt;참조하는&lt;span&gt; &lt;/span&gt;변수의&lt;span&gt; &lt;/span&gt;개수를&lt;span&gt; &lt;/span&gt;세고&lt;span&gt; &lt;/span&gt;이것이&lt;span&gt; 0&lt;/span&gt;이&lt;span&gt; &lt;/span&gt;될때&lt;span&gt; &lt;/span&gt;메모리가&lt;span&gt; &lt;/span&gt;정리되는 방식입니다.&lt;br /&gt;&lt;span&gt;(GC - gabage&amp;nbsp;collection&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;은&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;사용되지&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;않는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;메모리를&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;정리해주는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;기능입니다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.09.18.png&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciVVyb/btrNvZGyuC2/d2nCLseqr9WRqcOQKuHlL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciVVyb/btrNvZGyuC2/d2nCLseqr9WRqcOQKuHlL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciVVyb/btrNvZGyuC2/d2nCLseqr9WRqcOQKuHlL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciVVyb%2FbtrNvZGyuC2%2Fd2nCLseqr9WRqcOQKuHlL1%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;1446&quot; height=&quot;868&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.09.18.png&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;근데&lt;span&gt; &lt;/span&gt;이 ref_cnt 자체도 변수입니다. 그리고 이 변수가 Critical Section 이 되고 Multi Thread 환경에서는 충분히 Violation 이 일어날 수 있게됩니다. 그렇게 되면 메모리 릭이 발생하고 참조하는 변수가 없음에도 ref_cnt 가 0이 되지 않을 수 있다!&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Multi-core program에서 가장 자주 발생하는 Violation은 Race Condition 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그러면 이 Race Condition 이 어떻게 발생하게 되는지 깊게 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Python 에서 변수 x += 1 을 해준다고 했을때&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.32.20.png&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l6DyO/btrNvL2N0bn/0YdL0IV6G19aXnYgDT9Wk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l6DyO/btrNvL2N0bn/0YdL0IV6G19aXnYgDT9Wk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l6DyO/btrNvL2N0bn/0YdL0IV6G19aXnYgDT9Wk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl6DyO%2FbtrNvL2N0bn%2F0YdL0IV6G19aXnYgDT9Wk1%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;414&quot; height=&quot;181&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.32.20.png&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 어셈블리어 처럼 3가지의 최소한의 동작으로 변수에 1을 더해주는 작업이 진행되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1. num 을 계산하기 위해 산술 레지스터의 eax를 불러오고(&lt;b&gt;LOAD&lt;/b&gt;)&lt;br /&gt;2. 산술레지스터에 1을 더하도록(&lt;b&gt;INC&lt;/b&gt;) 명령하고&lt;br /&gt;3. 계산된 결과를 다시 변수(num)으로 저장(&lt;b&gt;STORE&lt;/b&gt;)시키는 과정을 거친다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.38.34.png&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/naDUP/btrNvM1Ns33/Vun9Q2LiBVK4l6vdJC8020/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/naDUP/btrNvM1Ns33/Vun9Q2LiBVK4l6vdJC8020/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/naDUP/btrNvM1Ns33/Vun9Q2LiBVK4l6vdJC8020/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnaDUP%2FbtrNvM1Ns33%2FVun9Q2LiBVK4l6vdJC8020%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;505&quot; height=&quot;714&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.38.34.png&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 두 thread 에서 Critical Section에 동시 접근 한다고 했을때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 이미지처럼 두 thread T1과 T2에서 동시성 보장을 위해 context switching이 발생하며 결과적으로 2가 되어야할 ref_cnt 가 1로 저장되는 결과가 발생하는 것입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 MultiThread 환경에서 발생할 수 있는 &lt;b&gt;문제를 대응하기 위한 대표적인 방법&lt;/b&gt;으로는 &lt;b&gt;Mutex Lock&lt;/b&gt; 이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 &lt;b&gt;Mutex Lock&lt;/b&gt; 은 공통된 자원에 &lt;b&gt;하나의 thread 만 점유가 가능토록 lock&lt;/b&gt;을 걸어주는건데 이 Thread 가 변수에 대한 작업이 종료되면 lock을 해제시켜 다른 thread 에서도 해당 변수에 접근 가능토록 시키고 이렇게 함으로써 data 동기화에 대한 문제를 해결 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.47.41.png&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sYt3e/btrNzFmxPPG/PR4bIKJ9xeVDsky8q28AEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sYt3e/btrNzFmxPPG/PR4bIKJ9xeVDsky8q28AEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sYt3e/btrNzFmxPPG/PR4bIKJ9xeVDsky8q28AEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsYt3e%2FbtrNzFmxPPG%2FPR4bIKJ9xeVDsky8q28AEk%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;489&quot; height=&quot;914&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.47.41.png&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python의 대표적인 인터프리터인 CPython에서&amp;nbsp; 실제로 gil이 돌아가는 Mutex lock의 코드를 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock이 해제 될때까지 while 문을 통해 무기한 기다리고 있는것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.59.40.png&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnM5a/btrNvrQ4JeY/pJSKZWgY8bTB1lKfmqwZW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnM5a/btrNvrQ4JeY/pJSKZWgY8bTB1lKfmqwZW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnM5a/btrNvrQ4JeY/pJSKZWgY8bTB1lKfmqwZW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtnM5a%2FbtrNvrQ4JeY%2FpJSKZWgY8bTB1lKfmqwZW0%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;436&quot; height=&quot;252&quot; data-filename=&quot;스크린샷 2022-10-01 오후 1.59.40.png&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 GIL 은 사실 파이썬 인터프리터 안에서 존재한다고 볼 수 있는데 이 인터프리터 안에 변수, 함수 정보들이 존재하고 인터프리터 내에서 상호작용하며 코드가 실행됩니다. 그렇기 때문에 글로벌 환경에서 mutex lock 이 작용이 되는건데 race condition 에 대한 대응책은 되지만 다른 thread 가 대기하게 되면서 성능에서의 낭비가 있을 수 있다는게 GIL의 단점이라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 GIL 은 global interpreter lock 이라는 이름그대로 python 인터프리터에서 제공하는 기능으로 python이 멀티 쓰레드 환경에서 동일한 자원에 접근할때 전역 global 로 mutex lock을 지원하는 기능이다! &lt;br /&gt;이 덕분에 python의 여러 쓰레드에서 동시 접근할 수 있는 gc의 ref_cnt 변수도 제대로 된 결과 값을 보장받을 수 있는것이다. &lt;br /&gt;하지만 다른 thread가 해당 자원에 대한 lock을 해제할때까지 또다른 thread는 while 문 속에서 무기한 대기를 해야하므로 성능에서 낭비가 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽어주셔서 감사합니다!&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://youtu.be/jXRyC-xKzxc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Pycon 2022 GIL 톺아보기&lt;/a&gt;&lt;/p&gt;</description>
      <category>Python</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/56</guid>
      <comments>https://jake2.tistory.com/entry/%EC%8A%88%EB%B0%94#entry56comment</comments>
      <pubDate>Sat, 1 Oct 2022 12:58:43 +0900</pubDate>
    </item>
    <item>
      <title>결제 개발 하기 (Redis를 이용한 race condition 대응 까지)</title>
      <link>https://jake2.tistory.com/entry/%EB%A7%8C%EB%93%A4%EA%B3%A0-%EC%8B%B6%EC%9D%80%EA%B1%B0-%EB%A6%AC%EC%8A%A4%ED%8A%B8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.iamport.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Iamport&lt;/a&gt; 를 활용한 결제 시스템 개발 과정을 이야기해보려 합니다. Iamport 적용 방법, 매뉴얼 등은 공식 &lt;a href=&quot;https://chai-iamport.gitbook.io/iamport/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;document&lt;/a&gt; 에 상세히 나와있습니다 :)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 서비스는 순수 B2B 만을 통해서 수익을 창출하고 있었습니다. 하지만 오랜 기간 서비스를 진행하며 제품에 대한 검증은 어느 정도 마쳤으니 B2C로 확대를 해보자!라는 게 올해의 목표였죠. 올해에만 현재까지 총 4명의.. 개발팀 퇴사를 겪으며.... 여름이 다가올 무렵 B2C 신규 프로젝트가 시작되었습니다.&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;일단 제품 판매를 하려면 결제가 되어야 하는데... Ruby on Rails 언어 지원이 안되는 PG 사들이 많아 시간이 오래 걸릴 수 도 있겠다 싶었습니다.&lt;br /&gt;&lt;br /&gt;그런데 찾아보니 2년전 MVP 단계에서 간단하게 구현된 결제 모듈이 존재하더군요..?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-04 오후 9.16.26.png&quot; data-origin-width=&quot;2414&quot; data-origin-height=&quot;1386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpuyEJ/btrNMHFRH7F/zjTwcidVnqBCPYi34KTBKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpuyEJ/btrNMHFRH7F/zjTwcidVnqBCPYi34KTBKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpuyEJ/btrNMHFRH7F/zjTwcidVnqBCPYi34KTBKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpuyEJ%2FbtrNMHFRH7F%2FzjTwcidVnqBCPYi34KTBKK%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;264&quot; data-filename=&quot;스크린샷 2022-10-04 오후 9.16.26.png&quot; data-origin-width=&quot;2414&quot; data-origin-height=&quot;1386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PG사 결제연동 설루션을 제공하는 Iamport 를 통해&amp;nbsp; Rest API 로 비인증 결제를 지원하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Iamport 에 대해 찾아보니 이미 다양한 고객사도 있고, RoR 로 구현된 서비스들에서도 사용하는 사례들이 보입니다. PG 사 마다 개발 공수를 들일 필요가 없어 보여 현재 상황에서 Iamport를 활용하는 것은 합리적인 선택이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;별다른 추가 보안 처리 없이 현재 코드 구현 내용을 설명드리며 그대로 사용해도 되는지 Iamport측에 문의해보니&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-04 오후 9.38.19.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LBsTE/btrNNAzrJhQ/VQCZ2FPE99uDdA6rtWlx2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LBsTE/btrNNAzrJhQ/VQCZ2FPE99uDdA6rtWlx2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LBsTE/btrNNAzrJhQ/VQCZ2FPE99uDdA6rtWlx2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLBsTE%2FbtrNNAzrJhQ%2FVQCZ2FPE99uDdA6rtWlx2k%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;398&quot; height=&quot;656&quot; data-filename=&quot;스크린샷 2022-10-04 오후 9.38.19.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 보안 처리만 확실히 되었다면 사용해도 된다고 합니다   하지만 3번의 필수는 아닌 보안요소가 걸리긴 합니다. &lt;br /&gt;일단은 해당 rest api를 통한 결제를 유지하고 MVP 출시 후에 토스페이먼츠나 나이스 페이먼츠의 인증결제로 변경하기로 결정했습니다.&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;style2&quot; /&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20220805-015349.png&quot; data-origin-width=&quot;6048&quot; data-origin-height=&quot;2632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9GcED/btrNNYNACTL/kjbvlPaisJgnMGLpYm9GD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9GcED/btrNNYNACTL/kjbvlPaisJgnMGLpYm9GD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9GcED/btrNNYNACTL/kjbvlPaisJgnMGLpYm9GD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9GcED%2FbtrNNYNACTL%2FkjbvlPaisJgnMGLpYm9GD1%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;6048&quot; height=&quot;2632&quot; data-filename=&quot;image-20220805-015349.png&quot; data-origin-width=&quot;6048&quot; data-origin-height=&quot;2632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 설계에서 구현을 진행하며 살을 붙여 완성된 프로세스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아임 포트 공식문서에 워낙 꼼꼼히 쓰여 있어서 그대로만 구현하면 될 줄 알았습니다..  &lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;구현 과정&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 결제의 State는 &lt;b&gt;waiting, paid, canceled&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;1.&lt;/b&gt; 유저가 결제 페이지에 접근하면 서버에서는 waiting 상태의 결제 데이터를 하나 생성합니다. 그리고 해당 데이터에 추후 PG사에 결제를 요청하고 검증을 진행할 uniq id를 저장해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-09-06 오후 4.41.12.png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;1394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GAqko/btrNKVEAgqB/H5NmmxV4JFABaMLkW9FOsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GAqko/btrNKVEAgqB/H5NmmxV4JFABaMLkW9FOsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GAqko/btrNKVEAgqB/H5NmmxV4JFABaMLkW9FOsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGAqko%2FbtrNKVEAgqB%2FH5NmmxV4JFABaMLkW9FOsK%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;284&quot; height=&quot;1394&quot; data-filename=&quot;스크린샷 2022-09-06 오후 4.41.12.png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;1394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.a [웹]&lt;/b&gt; Iamport sdk 를 통해 간편 페이 모듈(토스페이 or 카카오페이) 을&lt;span&gt;&amp;nbsp;&lt;/span&gt;호출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-09-13 오전 10.22.04.png&quot; data-origin-width=&quot;2012&quot; data-origin-height=&quot;1476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y5iii/btrNKWKbqnr/XQj4nNxMPaCCnvmrGnRyJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y5iii/btrNKWKbqnr/XQj4nNxMPaCCnvmrGnRyJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y5iii/btrNKWKbqnr/XQj4nNxMPaCCnvmrGnRyJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY5iii%2FbtrNKWKbqnr%2FXQj4nNxMPaCCnvmrGnRyJk%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;333&quot; height=&quot;244&quot; data-filename=&quot;스크린샷 2022-09-13 오전 10.22.04.png&quot; data-origin-width=&quot;2012&quot; data-origin-height=&quot;1476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서부터는 모바일인지 웹인지에 따라 결제가 다른 노선으로 진행됩니다. 웹에서는 iamport sdk 가 모듈을 호출하고 결제 완료를 누르면 결제 결과를 받아 구현해놓은 Rails 서버의 검증 -&amp;gt; 결제 함수를 동작합니다. 검증에 이상 없고 결제 state 변경이 완료되면 결제가 완료됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.b [모바일]&lt;/b&gt; 웹에서는 잇다의 페이지가 유지된 상태로 모달 창이 뜨고 콜백에 의해서 결제가 진행되는데 모바일은 잇다 페이지를 아예 벗어납니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-09-13 오전 10.23.40.png&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TGze4/btrNOTys18m/GpqgAsrMBLIPhhpHK49V4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TGze4/btrNOTys18m/GpqgAsrMBLIPhhpHK49V4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TGze4/btrNOTys18m/GpqgAsrMBLIPhhpHK49V4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTGze4%2FbtrNOTys18m%2FGpqgAsrMBLIPhhpHK49V4K%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;195&quot; height=&quot;343&quot; data-filename=&quot;스크린샷 2022-09-13 오전 10.23.40.png&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 결제가 완료되면 sdk에 미리 지정해놓은 url 로 redirect 되는 방법입니다. 그러면 그 과정에서 저희는 저장해놓은 데이터와 결제 결과 데이터의 검증, 결제 state 업데이트 등이 이루어져야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카카오페이 결제창 -&amp;gt; 브릿지페이지 -&amp;gt; 최종 결제완료 페이지&lt;/b&gt;의 단계로 구성해서 간편 페이 결제 완료 후 브릿지 페이지에 redirect 시키고 해당 페이지에서 검증, 결제 state 변경 처리를 진행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면_기록_2022-07-29_오후_3_53_46__1__AdobeExpress.gif&quot; data-origin-width=&quot;130&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eqzYcw/btrNNNssdmB/viKASxW25WA9q6RL9mrKrK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eqzYcw/btrNNNssdmB/viKASxW25WA9q6RL9mrKrK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eqzYcw/btrNNNssdmB/viKASxW25WA9q6RL9mrKrK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/eqzYcw/btrNNNssdmB/viKASxW25WA9q6RL9mrKrK/img.gif&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;158&quot; height=&quot;321&quot; data-filename=&quot;화면_기록_2022-07-29_오후_3_53_46__1__AdobeExpress.gif&quot; data-origin-width=&quot;130&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;결제 중 브릿지 페이지입니다. 나름 미적 감각 없는 저로써 만들고 귀엽게 잘 만들었다고 뿌듯했답니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 페이지에서 기존에 구현해 놓은 검증, 결제 state 변경 모듈이 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 결제가 완료되면 웹과 동일하게 결제 완료 페이지로 redirect 시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 결제가 끝입니다!   와 결제 구현 엄청 간단하네 라고 잠시 생각했습니다... &lt;br /&gt;문제가 될 수 있는 경우가 생각났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 브릿지 페이지로 이동하기 전에 데이터가 끊긴다거나 창을 꺼버린다던지 해서 PG 사 결제는 진행이 됐지만 저희 서버에서는 결제정보가 업데이트되지 않을 수 있는 상황이 생기게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 Iamport 측에서 웹훅 설정을 안내하고 있습니다. 웹훅은 Iamport 측에 결제가 완료되면 저희 쪽으로 결제 결과를 담아서 request를 날려주는 기능입니다. 쉽게 생각해서 우리가 핸드폰 문자가 왔는지 5분마다 확인하는게 아닌 문자가 오면 &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;위 프로세스 다이어그램에서 웹훅 통신인 3번과 저희쪽 검증, 결제 모듈이 작동하는 4번 의 과정은 거의 동시에 일어납니다. 순서에 보장이 없습니다. 웹훅 컨트롤러에서도 동일하게 저희 db 자원을 통해 검증, 결제 데이터 업데이트를 해줘야 하고 기존 컨트롤러에서도 해당 과정이 동일하게 이뤄지는데 이렇게 되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 서버 3개를 통해 서비스가 운영되고 있는 와중에 어느 서버로 3번 과정이 진행되고 4번 요청이 들어올지 &lt;b&gt;순서에 대한 보장&lt;/b&gt;이 이뤄지지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동일한 자원에 서로 다른 프로세스가 접근을 해서&lt;/b&gt; 자원을 업데이트시키는 일에 대한 대응이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 CTO님과 논의 후 Redis를 통해 해당 자원에 Lock을 걸어 대응하기로 결정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;살펴보니 redis에서는&lt;b&gt; trasaction을 보장해주는 multi&lt;/b&gt; 라는 커맨드가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 간단하게 살펴보자면&amp;nbsp;&lt;br /&gt;( 참고로 저희 redis gem 은 4.6 버전입니다, 항상 기술 적용 전 버전 업데이트 간 변경된 내용들도 확인합시다! )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-05 오후 2.59.38.png&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOOutD/btrNOfC8hH3/GkO3PgioZqlyVddebzEMLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOOutD/btrNOfC8hH3/GkO3PgioZqlyVddebzEMLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOOutD/btrNOfC8hH3/GkO3PgioZqlyVddebzEMLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOOutD%2FbtrNOfC8hH3%2FGkO3PgioZqlyVddebzEMLK%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;565&quot; height=&quot;165&quot; data-filename=&quot;스크린샷 2022-10-05 오후 2.59.38.png&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 위와 같이 multi 커맨드로 해당 블럭에서의 트랜잭션을 보장받고 블럭에서의 실행 결과를 array 로 돌려받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이용해 아래와 같이 블럭 안에서 redis에서 unique 키값을 조회하고 세팅하는 함수를 작성합니다&lt;/p&gt;
&lt;pre id=&quot;code_1664950421826&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def check_redis(u_id, value)
  redis.multi do |transaction|
    transaction.get(u_id)
    transaction.set(u_id, value)
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹훅 컨트롤러나 결제 컨트롤러의 첫 단계에서 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;unless check_redis(u_id).first.present?&lt;/span&gt; 인지 체크 후 나머지 로직을 실행해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레디스는 기본적으로 &lt;b&gt;single thread&lt;/b&gt; 이기 때문에 해당 트랜젝션이 보장받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 웹훅 컨트롤러와 기존 결제 컨트롤러에서 동시에 해당 check_redis 에 요청을 한다 해도, 아래 그림처럼&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-05 오후 3.25.41.png&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;1034&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuBwJW/btrNOTUeiQD/78aXAAmUR5VpkWDqxLGUP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuBwJW/btrNOTUeiQD/78aXAAmUR5VpkWDqxLGUP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuBwJW/btrNOTUeiQD/78aXAAmUR5VpkWDqxLGUP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuBwJW%2FbtrNOTUeiQD%2F78aXAAmUR5VpkWDqxLGUP1%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;422&quot; height=&quot;343&quot; data-filename=&quot;스크린샷 2022-10-05 오후 3.25.41.png&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;1034&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 요청이 들어온 쪽에서 첫 번째 인자를 무조건 nil로, 두번째 요청의 첫번째 인자는 위의 trasaction block에서 설정한 value값이 return 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이로써 결제를 진행하며 발생할 수 있는 Race condition 까지 해결했습니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 해당 결제 개발은 B2C를 위한 개발의 시작으로 추후 티켓의 개념 등 제품의 설계가 변하며 더욱 결제 기능이 확장되긴 했습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 개발을 하면서 느낀 것은 설계 단계에서 어느 정도 예외사항까지 고려를 해야 할지 결제에서 trasaction 은 어떻게 보장받아야 할지 경쟁상태는 어떻게 처리해야 할지 주니어로써 많은걸 고민해볼 수 있었던 좋은 기회였던 것 같아 너무 즐거웠습니다  &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;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.honeybadger.io/blog/avoid-race-condition-in-rails/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Avoid race condition in Rails&lt;/a&gt;&lt;br /&gt;- &lt;a href=&quot;https://github1s.com/redis/redis-rb/blob/HEAD/lib/redis/commands/transactions.rb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;redis-rb&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://redis.io/commands/multi/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Redis Multi&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://sabarada.tistory.com/177&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;redis transaction&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 이야기</category>
      <category>iamport</category>
      <category>Race Condition</category>
      <category>redis</category>
      <category>redis transaction</category>
      <category>transaction</category>
      <category>아임포트</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/51</guid>
      <comments>https://jake2.tistory.com/entry/%EB%A7%8C%EB%93%A4%EA%B3%A0-%EC%8B%B6%EC%9D%80%EA%B1%B0-%EB%A6%AC%EC%8A%A4%ED%8A%B8#entry51comment</comments>
      <pubDate>Thu, 15 Sep 2022 00:18:40 +0900</pubDate>
    </item>
    <item>
      <title>0.1 + 1.1 != 1.2 / Ruby에서 Float 계산하기</title>
      <link>https://jake2.tistory.com/entry/01-11-12</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-08-16 오후 11.58.17.png&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nPAYG/btrJRtxIKRD/aCkd2uqRyjS0PyfkCVVKik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nPAYG/btrJRtxIKRD/aCkd2uqRyjS0PyfkCVVKik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nPAYG/btrJRtxIKRD/aCkd2uqRyjS0PyfkCVVKik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnPAYG%2FbtrJRtxIKRD%2FaCkd2uqRyjS0PyfkCVVKik%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;586&quot; height=&quot;149&quot; data-filename=&quot;스크린샷 2022-08-16 오후 11.58.17.png&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;decimal 로 설정하기 싫었으나 어찌 표현할 방법이 없어서 decimal 로 저장한 column 이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력해보니 0.5 =&amp;gt; 0.5e0 같이 지수로 저장되어 있더라..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에서 decimal 로 연산하면 100% 정확한 값을 얻기는 어렵다. 예를 들어 0.1 + 1.1 == 1.2 -&amp;gt; false 가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-08-17 오전 12.17.00.png&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdjnCV/btrJNjPUc65/uAYuYMKFnENnKIQa2MDLy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdjnCV/btrJNjPUc65/uAYuYMKFnENnKIQa2MDLy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdjnCV/btrJNjPUc65/uAYuYMKFnENnKIQa2MDLy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdjnCV%2FbtrJNjPUc65%2FuAYuYMKFnENnKIQa2MDLy0%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;428&quot; height=&quot;72&quot; data-filename=&quot;스크린샷 2022-08-17 오전 12.17.00.png&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇다고 한다... 왜일까&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실수의 저장&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터는 모두 2진수로 데이터를 저장하고 통신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수의 경우에는 2 -&amp;gt; 00000010, 20 -&amp;gt; 00001100 이런식으로 2진수로 저장하는데 무리가 없다. 하지만 실수의 경우에는?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2의-1승 은 0.5 -2승은 0.25 -3승은 0.125 -4승은 0.0625 ...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10진수 0.125 는 2진수 0.001 로 표현하면 된다. 하지만 10진수 0.1 은 0.000110011 .... 0.1 을 정확하게 표현할 수가 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-08-17 오전 12.34.50.png&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbOB7D/btrJQ21wdlf/olSx6lQrtgfgofebFoDOT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbOB7D/btrJQ21wdlf/olSx6lQrtgfgofebFoDOT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbOB7D/btrJQ21wdlf/olSx6lQrtgfgofebFoDOT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbOB7D%2FbtrJQ21wdlf%2FolSx6lQrtgfgofebFoDOT0%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;503&quot; height=&quot;360&quot; data-filename=&quot;스크린샷 2022-08-17 오전 12.34.50.png&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시는 메모리에 32-bit 를 할당한 예시인데 저 저장되고 더 저장할 수 없이 버려지는 부분의 수치들 때문에 미세한 오차가 발생하는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Ruby에서는?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;15.2.9&amp;nbsp;Float&lt;br /&gt;15.2.9.1 General description&lt;br /&gt;Instances of the class Float represent floating-point numbers. The precision of the value of an instance of the class Float is implementation-defined; however, if the underlying system of a conforming processor supports IEC 60559, the representation of an instance of the class Float shall be the 64-bit double format as specified in IEC 60559, 3.2.2.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ruby 에서는 64-bit double format을 이용한다 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-08-17 오전 12.42.05.png&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WQlue/btrJSnp1wag/Stxvn8DwweGTHWUyR7pfi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WQlue/btrJSnp1wag/Stxvn8DwweGTHWUyR7pfi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WQlue/btrJSnp1wag/Stxvn8DwweGTHWUyR7pfi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWQlue%2FbtrJSnp1wag%2FStxvn8DwweGTHWUyR7pfi1%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;410&quot; height=&quot;70&quot; data-filename=&quot;스크린샷 2022-08-17 오전 12.42.05.png&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ruby에서는 정확한 값을 구하기 위해 정수형으로 만들어 계산 해주거나 BigDecimal 을 이용하면 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://ruby-doc.org/stdlib-2.5.1/libdoc/bigdecimal/rdoc/BigDecimal.html&quot;&gt;BigDecimal&lt;/a&gt; provides similar support for very large or very accurate floating point numbers.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.tcpschool.com/cpp/cpp_datatype_floatingPointNumber&quot;&gt;http://www.tcpschool.com/cpp/cpp_datatype_floatingPointNumber&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;https://www.youtube.com/watch?v=-GsrYvZoAdA&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://ruby-doc.org/stdlib-2.5.1/libdoc/bigdecimal/rdoc/BigDecimal.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ruby-doc.org/stdlib-2.5.1/libdoc/bigdecimal/rdoc/BigDecimal.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>Ruby on Rails</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/48</guid>
      <comments>https://jake2.tistory.com/entry/01-11-12#entry48comment</comments>
      <pubDate>Wed, 17 Aug 2022 00:17:31 +0900</pubDate>
    </item>
    <item>
      <title>index 페이지 로딩 속도 개선기 (Rails Cache)</title>
      <link>https://jake2.tistory.com/entry/index-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A1%9C%EB%94%A9-%EC%86%8D%EB%8F%84-%EA%B0%9C%EC%84%A0%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-03 오후 3.00.06.png&quot; data-origin-width=&quot;2644&quot; data-origin-height=&quot;1526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cghXKZ/btrNEvEKDgB/9F8KLfzP9FpLZ9Xby9q7j1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cghXKZ/btrNEvEKDgB/9F8KLfzP9FpLZ9Xby9q7j1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cghXKZ/btrNEvEKDgB/9F8KLfzP9FpLZ9Xby9q7j1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcghXKZ%2FbtrNEvEKDgB%2F9F8KLfzP9FpLZ9Xby9q7j1%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;312&quot; data-filename=&quot;스크린샷 2022-10-03 오후 3.00.06.png&quot; data-origin-width=&quot;2644&quot; data-origin-height=&quot;1526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 운영하고 있는 서비스의 인덱스 페이지가 많이 아팠습니다. 페이지를 렌더링 하는 시간이 프로덕션 기준 7s (로컬에서는 10s 를 넘겼습니다 ㅠㅠ) 를 넘어가는 끔찍한 상태였습니다. &lt;br /&gt;저 정도 로딩 속도면 유저분들 다 도망갔어도 할 말 없습니다... 근본적으로 개선을 하긴 해야겠다 마음먹었습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;음.. 근데, 어디가 문제지...?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결과정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;route53, nginx loadbalancer 부터 다 뜯어봐야 하는 건가... 싶어 절망할 뻔했으나 일단 눈에 보이는 것부터 체크하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무식하지만 정직하게 체크했습니다. 컴포넌트를 하나씩 빼서 그 부분만 렌더링 시키며 속도를 확인해 보는 방식으로 말이죠.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-03 오후 3.01.20.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czM7ZF/btrNyF2NvIA/ylZ23LD6dx78IZNWCykakk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czM7ZF/btrNyF2NvIA/ylZ23LD6dx78IZNWCykakk/img.png&quot; data-alt=&quot;클래스 카테고리 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czM7ZF/btrNyF2NvIA/ylZ23LD6dx78IZNWCykakk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczM7ZF%2FbtrNyF2NvIA%2FylZ23LD6dx78IZNWCykakk%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;629&quot; height=&quot;221&quot; data-filename=&quot;스크린샷 2022-10-03 오후 3.01.20.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1012&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;클래스 카테고리 목록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-03 오후 3.01.33.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b91xQs/btrNHTSL0Ho/ykEkLfgd5sxkhte7ko00BK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b91xQs/btrNHTSL0Ho/ykEkLfgd5sxkhte7ko00BK/img.png&quot; data-alt=&quot;클래스 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b91xQs/btrNHTSL0Ho/ykEkLfgd5sxkhte7ko00BK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb91xQs%2FbtrNHTSL0Ho%2FykEkLfgd5sxkhte7ko00BK%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;623&quot; height=&quot;258&quot; data-filename=&quot;스크린샷 2022-10-03 오후 3.01.33.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;클래스 목록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-03 오후 3.01.53.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bltk7l/btrNB5GoRfx/hJWMIhk3lvKNC7QNwjXqE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bltk7l/btrNB5GoRfx/hJWMIhk3lvKNC7QNwjXqE0/img.png&quot; data-alt=&quot;멘토 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bltk7l/btrNB5GoRfx/hJWMIhk3lvKNC7QNwjXqE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbltk7l%2FbtrNB5GoRfx%2FhJWMIhk3lvKNC7QNwjXqE0%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;643&quot; height=&quot;201&quot; data-filename=&quot;스크린샷 2022-10-03 오후 3.01.53.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;902&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;이런 식으로 컴포넌트를 하나씩 꺼내면서 속도가 유독 느리다 싶은 부분을 확인하다 보니 문제는 비교적 가까운 곳에 단순하게 있더군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 문제는 크게 세 가지였습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 불필요한 쿼리 산재&lt;/b&gt;&lt;br /&gt;먼저 불필요한 쿼리는 개발 당시에는 필요하던 쿼리였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 맨 위 이미지인 job category 를 조회하는 컴포넌트만 꺼내서 페이지를 로딩하는데 4초가 넘게 걸리더군요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 job category 가 만들어질 당시에는 전체 클래스 숫자가 적어서, 대다수의 job category 에 클래스가 존재하지 않았습니다. 그렇다 보니 유저 입장에서 빈 필터를 체크해서 어 클래스가 하나도 없네? 하는 것보다는 클래스가 존재하는 job category 만 보여주도록 쿼리가 존재했던 거고요. 하지만 현재에 와서는 모든 job category에 클래스들이 존재했고 굳이 해당 쿼리를 살려둘 필요가 없었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 쿼리는 날리고 20개 남짓한 job category 는 &lt;span&gt;단순하게&lt;/span&gt; 전부 호출시켰습니다. &lt;br /&gt;이렇게만 해줬는데도 해당 컴포넌트의 렌더링 시간이 4.7s -&amp;gt; 0.5s 로 대폭 감소했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. caching 미적용&lt;/b&gt;&lt;br /&gt;살펴보니 현재 제품에 inmemory db 를 이용한 cache 는 sidekiq message queue 작업을 위한 것뿐이었습니다.&lt;br /&gt;memcached 를 환경설정까지는 해놓으셨지만 현재는 사용되는 부분이 없더라고요.&lt;br /&gt;그래서 index페이지에 존재하는 정적인 partial 페이지들은 &lt;a href=&quot;https://guides.rubyonrails.org/caching_with_rails.html#collection-caching&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;rails collection caching&lt;/a&gt; 을 사용해 memcached에 올리기로 했습니다.&lt;br /&gt;( 사실 cache 를 적용시키고 추후 새로운 문제들을 만들어 내면서, 다음부터는 무조건 제대로 알 고쓰자...라는 마인드가 생기는 계기가 되었습니다.)&lt;br /&gt;rails caching에 대해 제대로 공부하지 않고 적용시킨 대가는 아래에서 다시 말씀드리겠습니다...  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌거나 위의 1, 2번 문제만 개선했는데도 70%가 넘게 속도가 개선되는 걸 볼 수 있었습니다 ^ㅇ^&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 로그 모니터링에 지속적으로 보이던 N+1 쿼리들&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-03 오후 6.29.18.png&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCznkP/btrNHS7rwZr/W0rkc9A8hIRHVDn0bdKuSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCznkP/btrNHS7rwZr/W0rkc9A8hIRHVDn0bdKuSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCznkP/btrNHS7rwZr/W0rkc9A8hIRHVDn0bdKuSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCznkP%2FbtrNHS7rwZr%2FW0rkc9A8hIRHVDn0bdKuSk%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;1682&quot; height=&quot;260&quot; data-filename=&quot;스크린샷 2022-10-03 오후 6.29.18.png&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 보이던 N+1중에 하나입니다. 사실 보면서 알고 있었음에도 개선해야 될게 하도 많아 엄두가 안 나고 있었던 와중에 index 페이지를 개선하며 같이 개선했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N+1 쿼리도 이곳저곳 산재해 있었지만 가장 자주 보이던 위의 경우를 살펴보면,&lt;br /&gt;클래스 카드 컴포넌트에서 review 존재 유무를 체크하고, review count를 하는 과정에서 불필요하게 N+1 쿼리가 호출되는 경우였습니다. 왜인지는 모르겠으나 review 존재 유무 체크 후 review count를 위한 helper 호출 함수 속에서 한 번 더 review 존재 체크, review.sum, review.count 그리고 reviews_count helper method를 따로 한번 더 호출해서 동일한 쿼리를 5번 씩 호출하고 있던 상황이었죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 근본적으로 만들어놓은 helper 함수 한개만 사용하도록 코드를 수정하고 해당 쿼리는 pre_load 시켜서 반복적으로 쿼리가 호출되지 않게 만들어줬습니다.&lt;br /&gt;(-&amp;gt; &lt;a href=&quot;https://medium.com/idopterlabs/solving-the-n-1-problem-in-rails-b5ea677e78e0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;rails N+1 쿼리 해결법&lt;/a&gt; )&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어려운 작업은 없었지만 결과는 드라마틱하긴 했습니다. &lt;br /&gt;(드라마틱하게 또 다른 문제를 만들었다는것만 빼면 말이죠...)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-03 오후 2.59.26.png&quot; data-origin-width=&quot;2858&quot; data-origin-height=&quot;1508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qo5nD/btrNzF2BwfE/Eck9GeFZglD1pwnocu9Y00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qo5nD/btrNzF2BwfE/Eck9GeFZglD1pwnocu9Y00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qo5nD/btrNzF2BwfE/Eck9GeFZglD1pwnocu9Y00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqo5nD%2FbtrNzF2BwfE%2FEck9GeFZglD1pwnocu9Y00%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;2858&quot; height=&quot;1508&quot; data-filename=&quot;스크린샷 2022-10-03 오후 2.59.26.png&quot; data-origin-width=&quot;2858&quot; data-origin-height=&quot;1508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;61&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로컬 서버 기준 10.x s -&amp;gt; 2.x s 까지 80% 가까이 속도가 개선 되었습니다.&lt;br /&gt;프로덕션에서도 7.6 s -&amp;gt; 1.7s 까지 속도가 개선된 걸 확인할 수 있었습니다.&lt;/b&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;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;또다른 문제 발생...&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-21 오후 4.54.56.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnW3gA/btrNKWPeam6/0U9M1ju2KZ4B6Bv3Bb7Hgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnW3gA/btrNKWPeam6/0U9M1ju2KZ4B6Bv3Bb7Hgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnW3gA/btrNKWPeam6/0U9M1ju2KZ4B6Bv3Bb7Hgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnW3gA%2FbtrNKWPeam6%2F0U9M1ju2KZ4B6Bv3Bb7Hgk%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;642&quot; height=&quot;300&quot; data-filename=&quot;스크린샷 2022-07-21 오후 4.54.56.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선 몇일 후 cs 한 건이 들어왔습니다. 멘토님이 클래스 프로필 카드를 변경했는데 적용이 안된다는것...&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;역시나 cache를 적용시키지 않은 다른 페이지에서는 이미지가 정상 반영되었고, chrome inspect를 키고 확인해보니 변경 전 이미지를 호출하고 있는게 보이더군요. rails 공식문서와 다른 자료들을 꼼꼼히 다시 확인해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 핵심은 제가 적용시켜놓은 cache가 정확하게 뭘 memcached에 올려놓았는지, 해당 cache가 expired 되고 fetch 가 언제 되는지 명확하게 이해하지 않고 냅다 적용시켜놔서 생긴 문제였죠  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 제가 이 기회에 제대로 학습할 수 있었던 Rails 의 Cache 에 대해 같이 학습하며 마무리 해보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Rails Cache&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 제가 사용한 Rails 의 collection cache는&lt;/p&gt;
&lt;pre id=&quot;code_1664807293220&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;%= render partial: &quot;classes_card&quot;,
  collection: @meetings, as: :meeting,
  layout: &quot;layouts/grid&quot;,
  locals: {
    ref: &quot;classes_list&quot;,
    grid_column: &quot;col-50 desktop-20&quot;,
  },
  cached: true 
%&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성 할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 풀어서 보면 아래와 같다고 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664807334190&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;% @meetings.each do |meeting| %&amp;gt;
  &amp;lt;% cache meeting do %&amp;gt;
    &amp;lt;%= render partial: &quot;classes_card&quot;,
      layout: &quot;layouts/grid&quot;,
      locals: {
        ref: &quot;classes_list&quot;,
        grid_column: &quot;col-50 desktop-20&quot;,
      }
    %&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;% end %&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이건 rails 공식문서에서 설명하고 있는 &lt;a href=&quot;https://guides.rubyonrails.org/caching_with_rails.html#russian-doll-caching&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Russiandoll caching&lt;/a&gt; 과 같다고 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664807449300&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;% @users.each do |user| %&amp;gt;
  &amp;lt;% cache user do %&amp;gt;
    ...
    &amp;lt;%= render user.posts %&amp;gt;
    ...
  &amp;lt;% end %&amp;gt;
&amp;lt;% end %&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 이렇게 cache를 거는 user 객체를 상속받는 다른 객체 (post)가 존재할때 그 객체인 post 가 변경 된다 해서 올려놓은 cache 가 변하지는 않습니다. 해당 cache 는 user 객체에 걸려있기 때문에 &lt;b&gt;user 객체에 변경&lt;/b&gt;이 있을 때에만 기존 cache 가 expired 되고 fetch 된다고 볼 수 있죠.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1100&quot; data-ke-size=&quot;size16&quot;&gt;따라서 위와 같은 문제를 해결해 주기 위해&lt;/p&gt;
&lt;pre id=&quot;code_1664807550527&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User &amp;lt; ApplicationRecord
  has_many :post
end

class Post &amp;lt; ApplicationRecord
  belongs_to :User, touch: true
end&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;post 변경시 User 모델에도 변경이 생기도록 위의 코드처럼 touch 를 모델에 추가하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;post 객체를 업데이트 하는 로직안에 User에 time stamp 를 찍어주는 코드를 추가하던지 해야합니다.&lt;/p&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: #172b4d;&quot;&gt;일단 해당 이슈를 해결하기 위해 필요했던 핵심은&lt;/span&gt;&lt;br /&gt;&lt;b&gt;view cache 가 expired 되는 시점. &lt;/b&gt;&lt;br /&gt;&lt;b&gt; &amp;rarr; collection cache 에서 collection 을 걸어 놓은 객체들에 변경이 있을 때 cache 가 fetch&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt; 된다.&lt;/span&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;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;그리고 한가지 더 cs가 들어오기전에 확인 된것이 있었는데, 클래스 카드의 마감시간이 변경되지 않는 것이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-21 오후 4.53.57.png&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boVGmV/btrNB5s7yf3/scpALncuEGDVGnTDKgQEj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boVGmV/btrNB5s7yf3/scpALncuEGDVGnTDKgQEj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boVGmV/btrNB5s7yf3/scpALncuEGDVGnTDKgQEj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboVGmV%2FbtrNB5s7yf3%2FscpALncuEGDVGnTDKgQEj1%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;469&quot; height=&quot;418&quot; data-filename=&quot;스크린샷 2022-07-21 오후 4.53.57.png&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; &lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;클래스 카드의 마감까지 남은 시간은 모델과는 연관 없는 static한 요소이므로 cache의 변경이 적용 되지 않았던 것이었어요.&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: #172b4d;&quot;&gt;Rails에서는 &lt;a href=&quot;https://guides.rubyonrails.org/caching_with_rails.html#fragment-caching&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;cache 에 조건문&lt;/a&gt;을 줄 수 있는 방법이 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;따라서 아래 코드와 같이 클래스 상태가 종료인 클래스에 한해서만 cache를 적용하도록 해주었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1664808019236&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;% @meetings.each do |meeting| %&amp;gt;
  &amp;lt;% cache_if meeting.ended?, meeting do %&amp;gt;
    &amp;lt;%= render partial: &quot;classes_card&quot;,
      layout: &quot;layouts/grid&quot;,
      locals: {
        ref: &quot;classes_list&quot;,
        grid_column: &quot;col-50 desktop-20&quot;,
      }
    %&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;% end %&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이번 이슈로 얻게 된 교훈...&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;참고자료&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.honeybadger.io/blog/ruby-rails-view-caching/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Rails View caching&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://guides.rubyonrails.org/caching_with_rails.html#fragment-caching&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Rails Guide - caching&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://blog.appsignal.com/2018/08/14/rails-collection-caching.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Rails Collection Caching&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://medium.com/idopterlabs/solving-the-n-1-problem-in-rails-b5ea677e78e0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Solving N+1 problem in Rails&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://tadhao.medium.com/joins-vs-preload-vs-includes-vs-eager-load-in-rails-5f721c44b3a9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Joins vs Preload vs Includes vs Eager load in Rails&lt;/a&gt;&lt;/p&gt;</description>
      <category>트러블슈팅</category>
      <category>Cache</category>
      <category>memcached</category>
      <category>Rails</category>
      <category>rails cache</category>
      <category>trouble shooting</category>
      <category>레일즈</category>
      <category>레일즈 캐시</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/47</guid>
      <comments>https://jake2.tistory.com/entry/index-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A1%9C%EB%94%A9-%EC%86%8D%EB%8F%84-%EA%B0%9C%EC%84%A0%EA%B8%B0#entry47comment</comments>
      <pubDate>Sun, 17 Jul 2022 16:01:27 +0900</pubDate>
    </item>
    <item>
      <title>브라우저 작동원리와 SSR / CSR</title>
      <link>https://jake2.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9E%91%EB%8F%99%EC%9B%90%EB%A6%AC</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;브라우저의 기본 구조&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 인터페이스 - 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분이다.&lt;/li&gt;
&lt;li&gt;브라우저 엔진 - 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.&lt;/li&gt;
&lt;li&gt;렌더링 엔진 - 요청한 콘텐츠를 표시. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함.&lt;/li&gt;
&lt;li&gt;통신 - HTTP 요청과 같은 네트워크 호출에 사용됨. 이것은 플랫폼 독립적인 인터페이스이고 각 플랫폼 하부에서 실행됨.&lt;/li&gt;
&lt;li&gt;UI 백엔드 - 콤보 박스와 창 같은 기본적인 장치를 그림. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서, OS 사용자 인터페이스 체계를 사용.&lt;/li&gt;
&lt;li&gt;자바스크립트 해석기 - 자바스크립트 코드를 해석하고 실행.&lt;/li&gt;
&lt;li&gt;자료 저장소 - 이 부분은 자료를 저장하는 계층이다. 쿠키를 저장하는 것과 같이 모든 종류의 자원을 하드 디스크에 저장할 필요가 있다. HTML5 명세에는 브라우저가 지원하는 '&lt;a href=&quot;http://www.html5rocks.com/en/features/storage&quot;&gt;웹&lt;/a&gt;&lt;a href=&quot;http://www.html5rocks.com/en/features/storage&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;http://www.html5rocks.com/en/features/storage&quot;&gt;데이터&lt;/a&gt;&lt;a href=&quot;http://www.html5rocks.com/en/features/storage&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;http://www.html5rocks.com/en/features/storage&quot;&gt;베이스&lt;/a&gt;'가 정의되어 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B55At/btrEDRQnZ7Z/LThgntyHu0DFRbkfdULPa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B55At/btrEDRQnZ7Z/LThgntyHu0DFRbkfdULPa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B55At/btrEDRQnZ7Z/LThgntyHu0DFRbkfdULPa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB55At%2FbtrEDRQnZ7Z%2FLThgntyHu0DFRbkfdULPa0%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;392&quot; height=&quot;266&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 구조에서 브라우저가 화면을 그리는 핵심인 렌더링 엔진을 살펴봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 엔진은 요청 받은 내용을 화면에 출력해주는 일을 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;브라우저 렌더링 엔진의 동작 과정&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brFi32/btrECAg2RfU/wzdBlB8oISRT7h35UsTTr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brFi32/btrECAg2RfU/wzdBlB8oISRT7h35UsTTr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brFi32/btrECAg2RfU/wzdBlB8oISRT7h35UsTTr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrFi32%2FbtrECAg2RfU%2FwzdBlB8oISRT7h35UsTTr1%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;66&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;66&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 렌더링 엔진은 HTML 문서를 parsing 하고 DOM 트리를 구축&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. css를 parsing 해서 CSSOM 트리(style rules)를 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 위 두가지를 합쳐서 웹의 청사진과 같은 Render Tree를 만들어냄 , 이걸 가지고 실제 화면을 그리게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이 요소를 가지고 요소들이 브라우저의 어느 위치에 배치 될지 Layout을 그림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. painting -&amp;gt; display&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parsing 은 렌더링 엔진에서 중요한 부분, 문서 파싱은 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것을 의미함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파싱은 어휘 분석과 구문 분석을 진행, 그리고 CSS와 JS 를 파싱하는 데 사용됨.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-12 오후 11.45.47.png&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S19mt/btrEw7Hr2fb/wixAnGhPp8GK3CGNkTW8d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S19mt/btrEw7Hr2fb/wixAnGhPp8GK3CGNkTW8d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S19mt/btrEw7Hr2fb/wixAnGhPp8GK3CGNkTW8d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS19mt%2FbtrEw7Hr2fb%2FwixAnGhPp8GK3CGNkTW8d1%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;705&quot; height=&quot;423&quot; data-filename=&quot;스크린샷 2022-06-12 오후 11.45.47.png&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 display 까지 다 끝마치고 DOM 에 새로운 변화가 생긴다면? ex) 새로운 요소의 추가, 삭제, 위치 변경, 크기변경 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Layout을 그리는 과정이 다시 반복됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃 변경이 없는 color, background-color 같은 것은 painting 만 다시함.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-16 오후 4.33.35.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xAJ9w/btrHrOjmtyy/eCUdqzxV8d6cx66OOrCknK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xAJ9w/btrHrOjmtyy/eCUdqzxV8d6cx66OOrCknK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xAJ9w/btrHrOjmtyy/eCUdqzxV8d6cx66OOrCknK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxAJ9w%2FbtrHrOjmtyy%2FeCUdqzxV8d6cx66OOrCknK%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;1060&quot; height=&quot;487&quot; data-filename=&quot;스크린샷 2022-07-16 오후 4.33.35.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 이미지를 통해 SPA 와 MPA 의 차이를 직관적으로 이해하기 좋습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CSR(Client Side Rendering)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SPA 는 말그대로 Single page 단 하나의 HTML 파일을 유지하는 웹서버를 뜻합니다. 예를 들어 위 이미에서 가장 좌측의 전체가 파란 컴포넌트로 채워진 최초의 페이지를 렌더링 한 이후에는 페이지의 이동이 있을 때 변경이 없는 컴포넌트들은 유지한 채로 분홍색, 초록색의 부분만 JS 를 이용해서 구성요소를 렌더링 하는 방식이 CSR입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSR(Server Side Rendering)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반면 SSR 은 페이지가 여러개입니다. 이렇게 되어 있으면 페이지 이동시 마다 새로운 페이지의 HTML을 전부 다시 그려서 렌더링 해주어야 하기 때문에 속도가 느린 단점이 있습니다.&lt;/p&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;브라우저 구동 원리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/59361&quot;&gt;https://d2.naver.com/helloworld/59361&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/js-browser&quot;&gt;https://poiemaweb.com/js-browser&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/js-hello-world&quot;&gt;https://poiemaweb.com/js-hello-world&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=mfzRXKUQCvY&quot;&gt;https://www.youtube.com/watch?v=mfzRXKUQCvY&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velopert.com/3236&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velopert.com/3236&lt;/a&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;chrome 에서 디버깅하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/console/&quot;&gt;https://developer.chrome.com/docs/devtools/console/&lt;/a&gt;&lt;/p&gt;</description>
      <category>CS 지식</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/46</guid>
      <comments>https://jake2.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9E%91%EB%8F%99%EC%9B%90%EB%A6%AC#entry46comment</comments>
      <pubDate>Mon, 13 Jun 2022 00:16:44 +0900</pubDate>
    </item>
    <item>
      <title>Zoom ThirdParty API 트러블 슈팅과 회고</title>
      <link>https://jake2.tistory.com/entry/Zoom-ThirdParty-API-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85%EA%B3%BC-%ED%9A%8C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;내가 재직 중인 회사는 실시간 멘토링 서비스를 위해 Zoom의 thirdparty api를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 멘토링 클래스에 참여한 참여자들의 참석시간을 실시간으로 관리하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 admin 및 고객 측에 제공하는 자료로 활용 중인데&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;아래 그림은 해당 기능에 관련된 flow 차트이다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2528&quot; data-origin-height=&quot;1924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2f7R6/btrB4P3vT0l/T62bl4n2Rm2jSWntdhmNTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2f7R6/btrB4P3vT0l/T62bl4n2Rm2jSWntdhmNTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2f7R6/btrB4P3vT0l/T62bl4n2Rm2jSWntdhmNTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2f7R6%2FbtrB4P3vT0l%2FT62bl4n2Rm2jSWntdhmNTk%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;632&quot; height=&quot;481&quot; data-origin-width=&quot;2528&quot; data-origin-height=&quot;1924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 flow 간 문제를 탐색하는 과정은 데이터가 보이지 않는 View file code에서부터 역산하며 탐색을 진행했다.&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: #5733b1;&quot;&gt;&lt;b&gt;Flow 간 문제 탐색 과정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣. DB(registrant) &amp;larr; Contoller(zoom_webhook)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;View file에서 보이지 않는 zoom 참석시간의 데이터는 registrant 테이블의 joined_at column을 통해 조회한다.&lt;/li&gt;
&lt;li&gt;joined_at 이 업데이트되는 코드는 webhooks_controller.rb의 join_live 함수에서 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;398&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 2️⃣. Controller &amp;harr;︎ Third Party API (Zoom Webhook)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 1번 join_live 함수에서 객체 정보 update를 위한 객체 setting 의 기준은 zoom webhook에서 request 보내는 참석자의 registrant_id를 통해 db에서 해당 properties[&amp;ldquo;registrant_id&amp;ldquo;] 를 갖는 registrant 데이터를 조회하여 객체로 세팅한다.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;그러나 해당 버그 발생 클래스들의 경우 properties 들이 업데이트되어 있지 않았음을 확인했다.&lt;br /&gt;&lt;br /&gt;그렇다면 다시 Properties 가 저장되는 시점을 탐색해봐야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;700&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 3️⃣. DB &amp;larr; Cron &amp;lt;-&amp;gt; Third Party API (Zoom)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;properties 가 업데이트되는 시점을 다시 역산해서 찾으면&lt;/li&gt;
&lt;li&gt;해당 시점은 cron 에 의해 event 들을 호출하는 부분이 있는데 이 내부의 함수 중에 존재하고 있다.&lt;/li&gt;
&lt;li&gt;그 내부 함수를 살펴보면 add_registrant라는 Zoom 의 third party api 를 통해 request를 보내고 response 받는 data 정보들을 객체에 저장하여 db 의 해당 registrant 테이블의 properties column 으로 저장시킴을 확인했다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;그리고 해당 third party api 가 실행되며 Zoom 서버에 참석자가 등록되면 기존에 만들어 놓은 Webhook 서버에 트리거로 작동하고 해당 Webhook 서버에서 관련된 data 들을 payload 에 담아 우리 제품 서버에 request 날린다.&lt;/li&gt;
&lt;li&gt;그러면 Webhook 서버에서 request 날린 data 와 우리 db에 저장된 정보를 바탕으로 객체를 세팅하는 것이다.&lt;/li&gt;
&lt;li&gt;하지만 이슈 된 경우의 로그를 보니 Cron에서 add_registrant 함수 자체가 실행되지 않았음을 확인했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-renderer-start-pos=&quot;1063&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;문제 원인&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;근본적인 원인은 add_registrant 함수의 윗줄에 존재하는 add_speaker 함수 내부에서 실행되는 third party API가 404를 뱉으며 종료되어 버려 정작 필요한 콜백 함수는 실행되지 않았다.&lt;/li&gt;
&lt;li&gt;이 문제 자체는 처음 제품을 설계할 당시 담당자들의 의도와는 다르게 제품이 사용되었기 때문인데 Zoom 에서 제공하는 meeting 의 타입에는 meeting, webinar 두 가지가 있다. 간단히 설명하면 webinar는 유희열의 스케치북(일부 스피커와 운영자 위주로 해당 기능이 진행), 미팅은 우리가 평소에 일상에서 활용하고 있는 온라인 미팅 정도로 생각할 수 있다.&lt;br /&gt;근데 불가피한 사유로 최초의 의도와 다르게 webinar 를 위해 만들어 놓은 제품의 기능을 활용하는 경우가 생겼고 이 경우에는 meeting_type이 meeting 임에도 webinar api가 실행되어 에러가 발생한 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-renderer-start-pos=&quot;1063&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;해결 과정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 third party api 와 관련된 코드 페이지가 워낙 방대하고 작성 당시의 개발자가 회사 내부에 아무도 없기에 근본적인 원인을 탐색하는데 시간이 오래 걸릴 것이라 판단됐다.&amp;nbsp;&lt;br /&gt;그래서 Zoom의 api를 통해 강제로 data 동기화시키는 기능을 내부 admin 페이지 내부에 추가해 급하게 핫픽스 배포하여 고객으로부터 클레임이 들어오는 것을 미연에 대응하였다.&lt;/li&gt;
&lt;li&gt;이후 위의 flow 탐색과정을 세팅하고 flow 별 탐색 후 근본적인 문제 원인을 발견 한 뒤에는 meeting_type이 webinar가 아니면 탈출하는 코드 한 줄을 추가해주어 근본적인 문제를 해결했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-renderer-start-pos=&quot;1063&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;회고&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에 입사할 때부터 해당 제품을 설계하고 만들어놓은 개발자분들은 회사 내부에 존재하지 않았고, 회사 입사 후 8개월 동안 두 번의 사수님들이 퇴사하는 이벤트를 겪었다. 사실 나도 이 과정에서 이직을 너무 많이 고민했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 회사로 해당 회사를 선택할 때에 Front 부터 server 까지 전부 ruby on rails 로 작성하지만, 프론트 개발 경험을 이 0년 차 1년 차가 아니면 언제 또 해볼 수 있을까 싶나 하는 생각 때문이었으나 단순히 프론트를 개발하는 경험 외에도 문제를 해결하며 많은 배움이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 이슈를 해결하며 느낀 것은 API 명세에 대한, 개발 문서화의 중요성.. Flow를 간단하게 작성했지만 실제로 코드의 복잡도는 더 높다. 이에 대한 문서화가 전혀 없어, 당시의 github history에 의존하며 문제 원인을 추론해 나가는 과정이 순탄치는 않았다. 개발은 유지보수가 80%라고 하지 않던가, 이를 위해 지금부터라도 내손에서 개발 문서화를 우리 회사에 뿌리내리게 만들고자 다짐했던 순간이었다.&lt;/p&gt;</description>
      <category>트러블슈팅</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/44</guid>
      <comments>https://jake2.tistory.com/entry/Zoom-ThirdParty-API-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85%EA%B3%BC-%ED%9A%8C#entry44comment</comments>
      <pubDate>Sat, 14 May 2022 20:27:13 +0900</pubDate>
    </item>
    <item>
      <title>팀원들을 행복하게 만들어주는 운영툴 자동화</title>
      <link>https://jake2.tistory.com/entry/Plan</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 팀원들을 위한 어드민 운영 툴을 만드는 입장이지만, 작년까지만 하더라도 저 또한 어드민 운영툴을 사용하는 직원이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시에는 루틴하게 자주 쓰는 기능인데도 반복적으로 불필요한 작업들이 반복되어야만 해서 굉장히 투덜거리면서 일을 했었습니다. 정확히 1년 10개월 정도 재직했었는데, 퇴사하기 몇 개월 전 해당 운영툴이 획기적으로 바뀌더군요. 감동이었습니다. 불필요하게 낭비되는 시간도 많이 줄었고 이미 퇴사는 마음먹었지만 이상하게 약간의 애사심..? 도 생기더군요 ㅎㅎ..&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 다니고 있는 회사의 상황을 보자면, 일단 팀원들이 야근을 너무 많이 합니다... 그리고 그걸 당연하게 생각하는것도 너무 가슴 아프더군요... 그래서 저는 옆자리에 야근 자주 하는 팀원을 계속 염탐했습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 기존에 개발되었던 기능들 중 비효율 적인 기능들이 있다고 인지는 하고 있었으나 개발자가 1~2 명으로 유지되는 상황에서 우선순위가 항상 뒤로 밀리기 일쑤였습니다. 근데 저도 입사하고 6개월 정도가 되니, 신규 프로젝트를 진행하면서 팀원들을 위한 운영툴 개선 작업을 같이 진행할 수 있는 여유가 생기더라고요.&lt;/p&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;설문조사에 대한 기능이었습니다. 설문조사를 위해 현재 저희 서비스는 타입폼이라는 saas 프로그램을 사용 중이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 서비스에 대해 간략히 말씀드리면 B2B2C 로 학교 취업지원처와 계약 후 학생들에게 현직자와 취업에 관련된 멘토링을 진행시켜주는 서비스를 운영 중입니다. 이 과정에서 야근하는 팀원의 업무 루틴을 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;멘토링 클래스 진행 전&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영툴에서 멘토링 클래스 생성 -&amp;gt; 타입폼 어드민에서 설문조사 그룹 생성 -&amp;gt; 설문조사 페이지 생성 -&amp;gt; 설문조사 링크 자사 운영툴 클래스 페이지에 기입 -&amp;gt; 생성한 설문조사 publish 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;클래스 종료 후&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학생들에게 설문 안내 문자 발송 -&amp;gt; 데이터 누락 있을 시 개발팀에 데이터 연동 요청 -&amp;gt; 개발팀 데이터 수동 연동 진행 -&amp;gt; 타입폼 페이지에서 설문 데이터 엑셀 파일 다운로드 및 재가공 -&amp;gt; 멘토님께 설문조사 결과 발송 -&amp;gt; 고객사에게 보낼 결과보고서 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참 이렇게 써놓고 보니까 민망할 정도로 개발자 입장에서 톡 하고 손대면 개선될 수 있는 것들이 너무 많이 보이더군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 일단 당장 할 수 있는 작업을 잘게 쪼개서 메인 프로젝트를 진행하면서 남는 시간에 같이 개선 작업을 진행했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 클래스 진행 전의 과정들은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 설문조사 템플릿을 하나 세팅해놓고 운영툴에서 클래스를 생성하면 해당 설문조사 템플릿이 duplicate 되도록 구현,&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;클래스 진행 후 안내 문자는 클래스 종료 시간과 종료 후 익일에 2회 설문 독려 알림톡을 스케줄러를 통해 발송되도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 연동 작업은 기존에도 스케줄러가 돌면서 클래스 종료 뒤에 데이터 연동 작업을 1회 진행했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 연동 작업 후 설문조사 참여자로 인한 데이터 오차때문 이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이건 타입폼 webhook 통해서 구현하려고 했습니다. 이미 사이드킥(스케줄러) 이 돌아가는 워커 서버의 CPU Utilization 이 눈에 띄게 튀고 있는 게 보이는 중이었고(주기적으로 80%까지 튀는 중입니다.) 현재보다 클래스 참여자가 많아지고 가져와야 할 데이터가 커지게 되면 워커 서버가 멀쩡할 거라고 장담할 수 없었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-03 오전 1.24.37.png&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYQtZp/btrNwFPncni/1YaAoe8ykJpwAQn91PJenK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYQtZp/btrNwFPncni/1YaAoe8ykJpwAQn91PJenK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYQtZp/btrNwFPncni/1YaAoe8ykJpwAQn91PJenK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYQtZp%2FbtrNwFPncni%2F1YaAoe8ykJpwAQn91PJenK%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;624&quot; height=&quot;94&quot; data-filename=&quot;스크린샷 2022-10-03 오전 1.24.37.png&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에는 이미 만들어져 있는 데이터 연동 작업을 최초 연동 +1일에 retry 시키는 방향으로 개발하긴 했습니다. 개발자 1인인 스타트업에서 최대한 개발 공수를 줄이기 위함이었죠.. 사실 webhook도 retry 로직을 추가하는 것도 틀린 방법은 없다고 생각합니다. 해당 CPU utilization이 99%까지 치솓을 정도면 webhook으로 개발했을 때 프로덕션 서버에도 부하가 클 것이라는 이야기니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로는 일단 개발 공수는 작게 하고 추후에 MSA로 떼어내자가 되었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 연동 작업 이후에는 멘토님과 고객사 측에 제공하는 보고서 작성을 위해서 타입폼 어드민에서 직접 클래스마다 데이터를 다운로드하고 가공하는 작업이었습니다. 해당 작업은 운영팀에서도 시간이 가장 많이 걸리는 과정이었고 저 또한 개발 공수가 가장 많이 들어간 작업이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 클래스 하나의 raw data 자체를 받아오는 건 문제가 아니었습니다만,,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 최초 해당 saas를 도입하면서 db설계를 할 당시 이러한 확장성까지 염두에 두고 모델링이 된 것이 아니었고, 100% raw data가 아닌 가공이 이뤄지고 있다는 점 그리고 그 데이터를 활용해서 csv로 제공해줘야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설문조사 안에 경우의 수가 왜 이리도 많은지... 중복답변 가능 여부, 필수 답변(공백 제출 가능) 여부, 여러 답변 타입들 등등&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국은 타입폼 document 을 뜯어보며 나올 수 있는 경우를 촘촘히 짜서 설계한 뒤에야 정상적으로 결과물을 볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 csv 가공과 더불어 멘토의 개인 페이지에서 data chart로 볼 수 있게 화면까지 구성해주고 나니 &lt;b&gt;해당 11 단계의 과정이 클래스 생성 -&amp;gt; 결과보고서 작성&lt;/b&gt;(필요한 데이터 가공해서 제공까지 반 자동화) 의 &lt;b&gt;두 단계로&lt;/b&gt; 획기적으로 줄게 될 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이렇게 자동화를 해놓았지만 우리 팀원분들은 오늘도 어김없이 야근을 하시네요   진심으로 기뻐하고 고마워하던 모습을 잊을 수 없습니다. 한참 전에 해드렸어야 할 일을 이제 와서 해드린 건데 말이죠...&lt;/p&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;특히 스타트업에서는 개발자가 톡 하고 손댔을 때 결과물은 생각보다 드라마틱할 수 있습니다.&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;</description>
      <category>개발 이야기</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/43</guid>
      <comments>https://jake2.tistory.com/entry/Plan#entry43comment</comments>
      <pubDate>Fri, 6 May 2022 00:46:52 +0900</pubDate>
    </item>
    <item>
      <title>전인구 님의 강연을 듣고 온 뒤...</title>
      <link>https://jake2.tistory.com/entry/%EC%A0%84%EC%9D%B8%EA%B5%AC-%EB%8B%98%EC%9D%98-%EA%B0%95%EC%97%B0%EC%9D%84-%EB%93%A3%EA%B3%A0-%EC%98%A8-%EB%92%A4</link>
      <description>&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/dhQYeU/btrBiaTYdXe/EUJt4mFO6w0eZGxdx4gnD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhQYeU/btrBiaTYdXe/EUJt4mFO6w0eZGxdx4gnD0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;2376&quot; data-origin-height=&quot;1448&quot; data-filename=&quot;스크린샷 2022-05-05 오전 12.05.50.png&quot; style=&quot;width: 37.3315%; margin-right: 10px;&quot; data-widthpercent=&quot;37.77&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhQYeU/btrBiaTYdXe/EUJt4mFO6w0eZGxdx4gnD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhQYeU%2FbtrBiaTYdXe%2FEUJt4mFO6w0eZGxdx4gnD0%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;2376&quot; height=&quot;1448&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wd6KB/btrBjYkE7wn/T06FWcHML0PBHqk0pe3hf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wd6KB/btrBjYkE7wn/T06FWcHML0PBHqk0pe3hf0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;580&quot; data-filename=&quot;스크린샷 2022-05-05 오전 12.05.37.png&quot; style=&quot;width: 61.5057%;&quot; data-widthpercent=&quot;62.23&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wd6KB/btrBjYkE7wn/T06FWcHML0PBHqk0pe3hf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWd6KB%2FbtrBjYkE7wn%2FT06FWcHML0PBHqk0pe3hf0%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;1568&quot; height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;나의 유튜브 리스트는 개발, 요리, 운동, 경제 네 개의 카테고리가 90%를 차지한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그중 최근에 가장 재밌게 보고 있는 경제 유튜브 중 하나가 이분이다. 일단 최근에 알게 되었는데 거시적으로 접근하는 분석법이 상당히 임팩트 있고 인사이트가 뛰어난 분이라고 생각된다. 근데... 두둥탁 강연을 한다고 하시네...??&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이런 분의 살아온 이야기를 해준다는데 놓칠 수 없지..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;나의 학창 시절 이야기를 잠깐하자면 내가 동생, 후배들에게 항상 해주는 이야기가 빚내서 여행 다녀라.. 였다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그런 말을 하는 가장 큰 이유는 경험이었다. 나를 성장시키고 남들에 비해 경쟁력을 갖추는데 가장 효과적이고 효율적인 건 당연 경험이라고 생각한다. 경험에는 간접경험과 직접 경험이 있는데, 취준생 시절 직장 경험이나 사업 등 다양한 일들을 직접 경험해볼 수는 없지 않은가? 그래서 나는 여행을 다니며 나의 주변에는 없는 다양한 사람들을 만나, 다양한 경험 이야기들을 듣는 것을 너무 좋아했다. 너무 재밌었고, 그 간접경험들을 통해 생각이 확장되고 유연 해지는 것을 느낄 수 있었다. 그래서 남들의 살아온 이야기 듣는 것을 나는 참 좋아했다. 지금 내가 공대를 나와서 전공에 얽매이지 않고 편의점 영업관리를 하다 현재는 개발자가 되기까지... 아무렇지 않게 멀쩡한 전공을 포기하고, 멀쩡한 회사를 포기하고 지금의 진흙탕(?) 에 오기 까지 힘든길만 택할 수 있던 이유는 저 간접경험들 덕분이리라..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여튼 다시 돌아와서 이런 이유에서 너무 흥미로운 기회라고 생각했고, 회사에서 점심시간에 바로 지원서를 작성해서 보내버렸고,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-05 오전 12.28.08.png&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQBS77/btrBiZqz65N/tw8Kc6VG3qGKKr0XgYuBuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQBS77/btrBiZqz65N/tw8Kc6VG3qGKKr0XgYuBuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQBS77/btrBiZqz65N/tw8Kc6VG3qGKKr0XgYuBuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQBS77%2FbtrBiZqz65N%2Ftw8Kc6VG3qGKKr0XgYuBuK%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;360&quot; height=&quot;312&quot; data-filename=&quot;스크린샷 2022-05-05 오전 12.28.08.png&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연락을 받을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;1. 경제적 자유는 총량이 중요한것이 아니고, 현금 흐름의 파이프 라인을 구축하는것이 중요한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 버려야 할 것은 불필요한 시간, 여러개를 동시에 하고자 하는 욕심, 게으름, 방해하는 사람&lt;br /&gt;&amp;nbsp; &amp;nbsp;얻어야 할 것은 좋은 사람, 차별화된 기술, 지혜 from 책, 고객들의 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 투자의 핵심은 원금을 지키는 투자를 하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 복리매직.. 무시 못한다. 적은 돈으로 시작 해도 수입을 계속 투자에 태워서 어느 순간 경제적 자유를 만들 수 있도록 하라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 아래의 플로우... 중요!! 꾸준히 소득을 늘릴 수 있는 기술개발을 하되 일과 관련된 기술과 사이드잡을 통해 기술력의 성장 속도를 극대화 시키고 소득은 투자에 태운다. 투자는&amp;nbsp;&lt;span&gt;단타? 노 인디언 기우제 기법으로 비가 무조건 올만한 곳에 몇년이고 맘놓고 장투 한다 생각하고 묻어둔다. 이것과 별개로 기술력이 커지고 일을 하며 생긴 인사이트를 바탕으로 사업을 시작하는것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-05 오전 12.47.49.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TKeGB/btrBhggm2br/3XBufP6VvucPn9r7unRKSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TKeGB/btrBhggm2br/3XBufP6VvucPn9r7unRKSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TKeGB/btrBhggm2br/3XBufP6VvucPn9r7unRKSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTKeGB%2FbtrBhggm2br%2F3XBufP6VvucPn9r7unRKSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;306&quot; data-filename=&quot;스크린샷 2022-05-05 오전 12.47.49.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로... 재태크든 일이든, 롤모델..? 스승을 만드는 것이 중요한데 가장 좋은 것은 책이다 책 책책책 책을 읽읍시다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 재밌는 강연이었고 연차와 기차 연착되는 시간이 아깝지 않은 하루 였다.&lt;/p&gt;</description>
      <category>경제,비즈니스</category>
      <category>경제</category>
      <category>사업</category>
      <category>재태크</category>
      <category>주식</category>
      <category>투자</category>
      <author>Jake2</author>
      <guid isPermaLink="true">https://jake2.tistory.com/42</guid>
      <comments>https://jake2.tistory.com/entry/%EC%A0%84%EC%9D%B8%EA%B5%AC-%EB%8B%98%EC%9D%98-%EA%B0%95%EC%97%B0%EC%9D%84-%EB%93%A3%EA%B3%A0-%EC%98%A8-%EB%92%A4#entry42comment</comments>
      <pubDate>Thu, 5 May 2022 00:54:13 +0900</pubDate>
    </item>
  </channel>
</rss>