<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Bbaktaeho</title>
    <link>https://bbaktaeho-95.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 19 May 2026 18:13:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Bbaktaeho</managingEditor>
    <image>
      <title>Bbaktaeho</title>
      <url>https://tistory1.daumcdn.net/tistory/3035513/attach/9469087dd5cc424da0df48ec0e6e1b71</url>
      <link>https://bbaktaeho-95.tistory.com</link>
    </image>
    <item>
      <title>Lambda256 사내 첫 해커톤 참여 후기 (수상 소감 포함)</title>
      <link>https://bbaktaeho-95.tistory.com/114</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;최근 사내에서 주최한 해커톤에 참여하게 되었습니다. 개인적으로는 생애 첫 해커톤이었기 때문에 모든 과정이 흥미로웠고, 무엇보다 AI를 사용하는 해커톤이었기 때문에 이번 기회에 좀 더 친해질 수 있는 계기가 되기도 했습니다.&lt;br&gt;또한 &lt;b&gt;Nodit&lt;/b&gt;을 직접 사용하면서 여러 인사이트를 제품 담당자와 공유하며 서비스 품질도 고민하는 시간이 되기도 했네요.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mb7SF/btsOLtsQgbT/KvSY0i6sJ3ymVOpxf0VIT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mb7SF/btsOLtsQgbT/KvSY0i6sJ3ymVOpxf0VIT1/img.png&quot; data-alt=&quot;nodit blog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mb7SF/btsOLtsQgbT/KvSY0i6sJ3ymVOpxf0VIT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMb7SF%2FbtsOLtsQgbT%2FKvSY0i6sJ3ymVOpxf0VIT1%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;560&quot; height=&quot;169&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nodit blog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이 글에서는 해커톤의 주제와 참여 과정, 결과, 그리고 참여를 통해 느낀 점을 간단히 정리하고자 합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;해커톤 시작&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;해커톤 행사를 찾아보면 무박 2일, 혹은 3일 정도의 짧은 시간에 아이디어를 기획하고 개발해서 산출물을 만들어내는 행사라고 합니다.&lt;br&gt;아무래도 사내에서 하는 행사이다 보니 현업에 차질이 생기지 않도록 약 9일 동안 진행한 것 같습니다.&lt;br&gt;일정은 6월 10일 부터 19일 오전 11시까지 산출물을 제출하고 다음 날인 20일에 해커톤 최종 발표 행사로 진행되었습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;1134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9hKHW/btsOMUpmKyS/tBidGSEem7ZnOXe434hH00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9hKHW/btsOMUpmKyS/tBidGSEem7ZnOXe434hH00/img.png&quot; data-alt=&quot;사내 해커톤 포스터가 멋있습니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9hKHW/btsOMUpmKyS/tBidGSEem7ZnOXe434hH00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9hKHW%2FbtsOMUpmKyS%2FtBidGSEem7ZnOXe434hH00%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;560&quot; height=&quot;542&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;1134&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사내 해커톤 포스터가 멋있습니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;팀 빌딩 (참여 비하인드)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사실 저는 해커톤에 참여할 생각이 없었습니다.&lt;br&gt;실제 한번도 해본 적이 없어 부담도 있었고, 짧은 시간에 잘 만들어낼 자신도 없었습니다. 특히 눈에 보이는 부분(디자인 혹은 UI 개발)들은 더더욱 자신 없었습니다.&lt;br&gt;동료 몇 분이 오셔서 함께하자는 것도 거절했었는데 다른 동료 분의 엄청난 추진력으로 인해 참여하게 되었습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8RvcF/btsOMJamr3T/wA0LthUhNGTAQ4darPqZnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8RvcF/btsOMJamr3T/wA0LthUhNGTAQ4darPqZnk/img.png&quot; data-alt=&quot;제가 Leo 라는 닉넴입니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8RvcF/btsOMJamr3T/wA0LthUhNGTAQ4darPqZnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8RvcF%2FbtsOMJamr3T%2FwA0LthUhNGTAQ4darPqZnk%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;560&quot; height=&quot;140&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;제가 Leo 라는 닉넴입니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후로 한 시간 동안 답장을 안해주셔서 진짜 참여하게 되었구나 하고 팀원 분의 답변이 오기 전까지 뭐 할까 고민하기까지 했습니다.&lt;br&gt;거절하면 되는데 저도 내심 해보고 싶은 마음이 있어서 그런지 열정이 생기더라고요.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmUrpb/btsOMTD1PHZ/qn1UUIDpLeouXGCMomZ0M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmUrpb/btsOMTD1PHZ/qn1UUIDpLeouXGCMomZ0M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmUrpb/btsOMTD1PHZ/qn1UUIDpLeouXGCMomZ0M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmUrpb%2FbtsOMTD1PHZ%2Fqn1UUIDpLeouXGCMomZ0M1%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;560&quot; height=&quot;176&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 진짜로 장난 아닌 줄 알았습니다. 아무튼 이렇게 저희 팀이 결성되었습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;주제&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;해커톤 주제는 두 가지 트랙으로 구성되었습니다.&lt;br&gt;&lt;b&gt;Track 1. Develop with Nodit MCP&lt;/b&gt;: Nodit MCP를 활용한 서비스 구현 아이디어&lt;br&gt;&lt;b&gt;Track 2. Work with AI&lt;/b&gt;: AI를 활용한 업무 생산성 향상 및 창의적인 문제 해결 사례&amp;nbsp;&lt;br&gt;저희 팀은 블록체인 그리고 Web3에 관심이 많아서 사내 서비스 Nodit을 사용하는 첫 번째 트랙으로 해커톤에 참여했습니다. Nodit 이 궁금하시면 &lt;a href=&quot;https://nodit.io/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;여기&lt;/span&gt;&lt;/a&gt;를 참고해 주세요.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;개발&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;해커톤이 시작되고 저희는 매일 점심시간에 함께 식사를 하면서 아이디어를 공유했습니다.&lt;br&gt;따로 시간낼 필요도 없어서 아주 효과적인 방법이었던 것 같습니다. 식사 후에는 업무 시간 전까지 아이디어가 실현 가능한지 여러 실험들을 함께했습니다.&lt;br&gt;당연하게도 대부분의 아이디어들이 제가 본업으로 개발하는 부분이 아닌 프론트 개발이었습니다. 심지어 크롬 익스텐션이라서 더 복잡할 것 만 같았습니다. 이참에 AI 나 잘 써보자 하는 생각으로 기술 검토에 돌입하게 되었습니다.&lt;br&gt;그러기 위해&amp;nbsp;일단 주제에 맞춰 개발 환경에 &lt;b&gt;Nodit MCP&lt;/b&gt;를 연동해야 했고, 저는 Cursor를 사용하여 연동했습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;&quot;mcpServers&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;nodit&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;command&quot;: &quot;npx&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;args&quot;: [&quot;@noditlabs/nodit-mcp-server&quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;env&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;NODIT_API_KEY&quot;: &amp;lt;NODIT_API_KEY&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxFtlr/btsOMSSDeKA/4lFA931012DJeKqemrrKAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxFtlr/btsOMSSDeKA/4lFA931012DJeKqemrrKAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxFtlr/btsOMSSDeKA/4lFA931012DJeKqemrrKAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxFtlr%2FbtsOMSSDeKA%2F4lFA931012DJeKqemrrKAk%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;1128&quot; height=&quot;650&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 연동이 완료되면 Nodit MCP를 사용해서 Nodit Node, Data 등의 API 들을 LLM에서 직접 사용하거나 코드를 구현해 달라고 요청할 수 있게 됩니다. 이 역시 Nodit Docs에서 자세하게 설명되어 있습니다. &lt;a href=&quot;https://developer.nodit.io/docs/nodit-mcp&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;여기&lt;/span&gt;&lt;/a&gt;를 참고해 주세요.&lt;br&gt;&amp;nbsp;&lt;br&gt;이제 아이디어를 실현하기 위한 기본 설정이 완료되었습니다. Nodit MCP를 활용하면 LLM에게 Nodit을 통한 기능 구현이 매우 쉬워집니다. 실제 저는 해커톤을 위한 작품을 아래 첨부한 사진들처럼 개발했습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2380&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CIz95/btsOK9amg7E/s4z6kYgaheoghLenFDWE80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CIz95/btsOK9amg7E/s4z6kYgaheoghLenFDWE80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CIz95/btsOK9amg7E/s4z6kYgaheoghLenFDWE80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCIz95%2FbtsOK9amg7E%2Fs4z6kYgaheoghLenFDWE80%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;2380&quot; height=&quot;1440&quot; data-origin-width=&quot;2380&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 어떤 MCP와 연동되었는지 설명도 해주고, Nodit 기능을 사용하다 발생한 에러도 Nodit MCP를 사용해서 잘 해결해 줍니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2426&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dk2HNh/btsOL5FbHs9/8Isxtx294Minwi12viEoh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dk2HNh/btsOL5FbHs9/8Isxtx294Minwi12viEoh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dk2HNh/btsOL5FbHs9/8Isxtx294Minwi12viEoh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdk2HNh%2FbtsOL5FbHs9%2F8Isxtx294Minwi12viEoh0%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;2426&quot; height=&quot;852&quot; data-origin-width=&quot;2426&quot; data-origin-height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 제가 사용한 프롬프트와 그에 따른 LLM이 MCP를 사용하는 모습의 일부입니다.&lt;br&gt;이번 해커톤에서 위와 같이 &lt;b&gt;단 한 줄의 코드를 작성하지 않고 말로만 구현&lt;/b&gt;했습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2588&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbS487/btsOLzfuyFv/gPYsFFEkSDKgbJpV2NVVHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbS487/btsOLzfuyFv/gPYsFFEkSDKgbJpV2NVVHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbS487/btsOLzfuyFv/gPYsFFEkSDKgbJpV2NVVHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbS487%2FbtsOLzfuyFv%2FgPYsFFEkSDKgbJpV2NVVHk%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;2588&quot; height=&quot;778&quot; data-origin-width=&quot;2588&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 항상 순조롭지만은 않았습니다.&lt;br&gt;좌측의 캡처 화면처럼 되던 기능도 건드려서 문제가 커지거나 혹은 LLM이 MCP를 호출하다가 고장 나는 현상도 있었었습니다.&lt;br&gt;이땐 수동으로 개입해서 한 파일씩 reject 시켜주거나 채팅을 새로 만드는 방법으로 대응했던 것 같습니다.&lt;br&gt;뿐만 아니라 Cursor의 rules 설정으로 내가 현재 어떤 개발을 어떤 언어와 프레임워크, 라이브러리를 사용할 건지 명시해 두는 것도 완성도 높은 결과를 보여줬던 것 같습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종 결과&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;해커톤 마지막 날, 다른 팀들의 결과물을 보고 나서 솔직히 우승은 어렵겠다고 생각했습니다. 대부분의 팀들이 기술적으로 매우 높은 완성도를 보여주었고, 특히 제가 관심을 가졌던 기술을 실제로 구현해 낸 팀도 있었습니다.&lt;br&gt;반면 저희는 도입을 시도했던 기술이 의도한 대로 동작하지 않아 중도에 제외할 수밖에 없었고, 기술적인 면에서 특별히 부각될 만한 요소가 없다고 느꼈습니다.&lt;br&gt;그럼에도 불구하고 개발 과정은 무척 즐거웠습니다. 어떤 결과를 만들 수 있을지에 대한 기대감과 실제로 그것이 구현되어 가는 과정을 함께하며 몰입할 수 있었던 경험은 매우 소중했습니다. 구현한 기능이 경쟁자이든 동료이든 누군가에게 실제로 도움이 될 수 있다면 꼭 소개해주고 싶다는 마음도 들었습니다. 기술적인 화려함보다는 &lt;b&gt;정말 유용한 기능&lt;/b&gt;이라는 점이 결과적으로 좋은 평가를 받는 데 큰 역할을 했다고 생각합니다.&lt;br&gt;결국 저희 팀은 Nodit MCP + LLM을 활용해서 구현한 서비스를 제출했고 이 아이디어에 긍정적인 평가를 받아 &lt;b&gt;대상(1위)&lt;/b&gt;을 수상하게 되었습니다. 기대하지 않았던 결과였기에 더욱 기뻤고, 결과 그 자체보다도 그 과정에서 느꼈던 몰입과 재미가 오히려 더 큰 수확이었다고 생각합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;대상 소감&lt;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUzftl/btsOMmsWK5I/5xs1KHnDMdneVlYHs6nnP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUzftl/btsOMmsWK5I/5xs1KHnDMdneVlYHs6nnP1/img.png&quot; data-alt=&quot;우리가 만든 Web3 Minion&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUzftl/btsOMmsWK5I/5xs1KHnDMdneVlYHs6nnP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUzftl%2FbtsOMmsWK5I%2F5xs1KHnDMdneVlYHs6nnP1%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;560&quot; height=&quot;237&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우리가 만든 Web3 Minion&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 해커톤은 제 인생 첫 해커톤이었습니다. 그게 사내 해커톤이었다고 하더라도 매우 특별한 도전이었습니다.&lt;br&gt;처음 참여하는 자리였기에 기대보다는 걱정이 앞섰지만 해커톤이 진행될수록 그 걱정은 자연스럽게 사라졌고 어느새 팀원과 함께 몰입해 있는 제 자신을 발견할 수 있었습니다.&lt;br&gt;같이한 팀원과 각자 맡은 영역에서 서로 적극적이었으며 조율이 필요한 부분은 빠르게 방향을 바꿔나가며 완성도를 높여가는 과정은 매우 인상 깊었습니다. 역할의 경계를 넘나들며 서로를 보완해 주는 팀워크 덕분에 한정된 시간 속에서도 완성도 있는 결과를 만들 수 있었습니다.&lt;br&gt;또한 다른 팀들과의 선의의 경쟁도 큰 자극이 되었습니다.&lt;br&gt;이번 해커톤 작품들을 보면 하나도 겹치는 게 없는 점을 보면 다양한 시각에서 문제를 풀어나가는 방식, 기술 스택의 선택과 구현 방식 등을 보며 많은 것을 배울 수 있었습니다. 저는 그 자체로도 즐거운 경험이었습니다.&lt;br&gt;그리고 AI와 함께한 이번 해커톤은 또 다른 팀원과 함께한 경험이었습니다. 어느 정도 개발 관련 지식만 있다면 개발 분야를 막론하고 구현할 수 있다는 사실을 피부로 느끼게 되었습니다. 앞으로도 계속 친하게 지내야 될 것 같습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;대상을 수상하게 된 것은 매우 뜻깊은 일이었지만 그보다 더 기억에 남는 것은 이 모든 과정을 처음부터 끝까지 온전히 즐길 수 있었다는 점입니다. 앞으로도 이런 기회를 통해 더 자주 도전하고 다양한 사람들과 함께 성장할 수 있는 경험을 쌓아가고 싶습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/posts/lambda256_%EB%9E%8C%EB%8B%A4256-%EC%A0%9C1%ED%9A%8C-%ED%95%B4%EC%BB%A4%ED%86%A4-%EB%8D%B0%EB%AA%A8%EB%8D%B0%EC%9D%B4%EC%99%80-%EC%8B%9C%EC%83%81%EC%8B%9D%EC%9D%84-%EC%A7%84%ED%96%89%ED%96%88%EC%8A%B5%EB%8B%88%EB%8B%A4-nodit-mcp%EC%99%80-activity-7341736441883873280-mSn_?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAADzGtIIBJUa6hUTJeMISU46g_wCgCyFe4qc&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Lambda256 해커톤 수상&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://developer.nodit.io/docs/nodit-overview&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Nodit Docs&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</description>
      <category>대외 활동 (activity)/공모전, 해커톤 (contest, hackathon)</category>
      <category>Ai</category>
      <category>blockchain</category>
      <category>Hackathon</category>
      <category>lambda256</category>
      <category>nodit</category>
      <category>nodit mcp</category>
      <author>Bbaktaeho</author>
      <guid isPermaLink="true">https://bbaktaeho-95.tistory.com/114</guid>
      <comments>https://bbaktaeho-95.tistory.com/114#entry114comment</comments>
      <pubDate>Sun, 22 Jun 2025 23:52:14 +0900</pubDate>
    </item>
    <item>
      <title>Go 언어의 sync package 를 통해 간단하게 성능을 개선시킨 이야기</title>
      <link>https://bbaktaeho-95.tistory.com/113</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가기 전에&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go 언어를 실무에서 사용한 지 어느덧 3년이 되어갑니다. 그동안 우여곡절도 많았지만, 동시성 프로그래밍을 이해하고 실제 프로덕션 환경에서 안정적으로 운영하며 다양한 문제를 해결해 왔습니다. 짧다면 짧고, 길다면 긴 시간 동안 Go 언어를 다루면서 참 많은 경험을 했던 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Go 언어답게 문제를 해결했던 사례 하나를 공유하려 합니다. 특히 인상 깊었던 점은, 이러한 기능을 별도 라이브러리 없이 &lt;span&gt;&lt;b&gt;언어의 기본 패키지&lt;/b&gt;&lt;/span&gt;만으로 구현할 수 있었다는 점인데요. 이런 점이 바로 Go 언어의 큰 장점 중 하나라고 생각합니다. 이 장점을 활용한 사례를 전달드리도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마주친 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 담당하는 업무는 블록체인의 온체인 데이터를 ETL 하고 검색을 위해서 인덱싱하는 소프트웨어를 개발 및 운영하고 있습니다. 이를 Indexer라고 부르고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록체인마다 다른 특징들을 파악하여 적절한 인터페이스를 활용하거나 온체인 데이터를 분석해서 2차 가공 데이터를 생산해 내며 서비스를 개선하고 있기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 가장 중요한 부분은 이러한 데이터 파이프라인 과정이 빠르고 안전하게 일어나야 한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 블록체인의 블록 생성 시간보다 느려지면 안 되며 수많은 데이터의 누락과 정합성 이슈없이 전달 및 인덱싱이 마무리되어야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;As-is&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Indexer의 기본 구조부터 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lwhJL/btsOoFe2Ekx/8dkWOwNHuzrXRQWwkdgvt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lwhJL/btsOoFe2Ekx/8dkWOwNHuzrXRQWwkdgvt0/img.png&quot; data-alt=&quot;Indexer 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lwhJL/btsOoFe2Ekx/8dkWOwNHuzrXRQWwkdgvt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlwhJL%2FbtsOoFe2Ekx%2F8dkWOwNHuzrXRQWwkdgvt0%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;1284&quot; height=&quot;164&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Indexer 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Blockchain&lt;/b&gt; - 처리 대상이 되는 블록체인 노드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Source&lt;/b&gt; - Blockchain과 상호작용 및 1차 가공을 담당 (Extract)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Processor&lt;/b&gt; - Source로 부터 받은 원천 데이터를 조합 및 2차 가공을 담당 (Transform)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Destination&lt;/b&gt; - 여러 저장소 특성에 따라 sort, deduplicate, aggregation 등을 담당 (Load)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Store&lt;/b&gt; - 검색 혹은 Data Lake로 활용될 저장소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조는 간단합니다. 화살표 방향대로 데이터가 흐르며 최종 산출물이 나오게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 문제가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라 전환 작업으로 인해 블록체인의 과거 시점부터 다시 하도록 해야 했습니다. 이때 블록의 생성 속도를 따라가지 못하는 큰 문제가 발생하게 됩니다. 원인은 과거 시점에서 비용이 비싼 요청으로 인해서 응답 속도가 매우 느려진 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 요청은 과거 시점의 데이터를 조회할 때 캐시 히트가 될 수도 없어 디스크 I/O 비용이 매우 크며 노드의 내부 로직에서도 데이터를 만들기 위한 프로세싱 과정이 들어가고 또한 실제 데이터가 많은 구간이기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 병목은 Source 에 있었습니다. Source는 기본적으로 필요할 때마다 블록체인과 상호작용하도록 구현되어 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;To-Be&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 블록체인의 과거 시점만 문제 되는 것은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록체인은 하드포크 라는 과정을 통해 계속해서 더 많은 트랜잭션을 처리할 수 있도록 개선되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로의 블록에서도 많은 데이터들로 가볍다고 생각했던 요청이 무거운 요청이 될 수도 있게 됩니다. 이러한 문제를 해결하기 위해서 Source 개선이 반드시 필요하다고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록체인 특성을 고려했을 때 온체인 데이터는 항상 순차적이며 체크포인트가 되는 블록 이라는 데이터가 존재하므로 Buffer를 고안하게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YSdxb/btsOmXH9HsF/DcvdUNGPNw3p1hKcYlKIp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YSdxb/btsOmXH9HsF/DcvdUNGPNw3p1hKcYlKIp0/img.png&quot; data-alt=&quot;Source Buffer 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YSdxb/btsOmXH9HsF/DcvdUNGPNw3p1hKcYlKIp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYSdxb%2FbtsOmXH9HsF%2FDcvdUNGPNw3p1hKcYlKIp0%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;1134&quot; height=&quot;444&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Source Buffer 구조&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;Buffering 과정을 통해서 필요한 데이터를 미리 가져와서 Processor가 필요할 때 즉시 전달해 줄 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 어떻게 구현했는지 설명하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제를 해결하기 전에&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buffering을 구현하기 앞서 알아둬야 할 몇 가지 기본 패키지와 동시성 패턴이 있습니다.&amp;nbsp;&lt;br /&gt;sync package와 Worker Pool 패턴인데요. 동시성 프로그래밍에서 가장 대표적이고 기본적인 패턴이라고 생각합니다. 실제로 실무에서 빠른 성능을 보여줘야 할 때 많이 사용하는 패턴이기도 합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;간단하게 이 패턴에 대해 설명드리도록 하겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Worker Pool&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여러 개의 작업(Job)들을 여러 개의 Worker에게 동시에 분산 처리하게 함으로써 자원을 효율적으로 사용하는 패턴&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker Pool은 Go 언어로 구현하기 가장 쉬운 것 같습니다.&lt;br /&gt;예시 코드를 보면,&lt;/p&gt;
&lt;pre id=&quot;code_1748931626534&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

type Job struct {
	ID    int
	Value int
}

type Result struct {
	Job         Job
	SquareValue int
}

func worker(id int, jobs &amp;lt;-chan Job, results chan&amp;lt;- Result) {
	for job := range jobs {
		fmt.Printf(&quot;Worker %d: processing job %d\n&quot;, id, job.ID)
		time.Sleep(time.Second) // Job의 처리 시간
		results &amp;lt;- Result{Job: job, SquareValue: job.Value * job.Value}
	}
}

func main() {
	var (
		numJobs    = 5
		numWorkers = 3
	)

	jobs := make(chan Job, numJobs)
	results := make(chan Result, numJobs)

	// 워커 시작
	for w := 1; w &amp;lt;= numWorkers; w++ {
		go worker(w, jobs, results)
	}

	// 작업 할당
	for j := 1; j &amp;lt;= numJobs; j++ {
		jobs &amp;lt;- Job{ID: j, Value: j}
	}
	close(jobs)

	// 결과 수집
	start := time.Now()
	for a := 1; a &amp;lt;= numJobs; a++ {
		result := &amp;lt;-results
		fmt.Printf(&quot;Result: jobID=%d, input=%d, output=%d\n&quot;,
			result.Job.ID, result.Job.Value, result.SquareValue)
	}
	elapsed := time.Since(start)
	fmt.Printf(&quot;All jobs processed in %s\n&quot;, elapsed)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main 함수를 보면 몇 개의 Worker를 사용할지 미리 지정하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서는 3개의 Worker를 할당하고 있네요. 이는 여러 요소를 고려해 보고 적절한 수를 지정하면 자원을 매우 효율적으로 사용하며 매우 높은 성능 향상을 보여주게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Job은 예시로 5개만 설정해 뒀습니다. 하나의 Job은 1초의 소요 시간이 걸린다고 가정했을 때 순차적으로 처리되면 5초가 걸리게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 3개의 Worker로 동시에 작업이 가능한 Job은 3개이며 Woker가 마무리되면 즉시 다음 Job을 실행할 것이므로 남은 2개까지 바로 처리합니다. 예상 시간은 2초로 줄어들게 되네요.&lt;/p&gt;
&lt;pre id=&quot;code_1748932930509&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Worker 3: processing job 1
Worker 1: processing job 2
Worker 2: processing job 3
Worker 2: processing job 4
Worker 3: processing job 5
Result: jobID=3, input=3, output=9
Result: jobID=1, input=1, output=1
Result: jobID=2, input=2, output=4
Result: jobID=5, input=5, output=25
Result: jobID=4, input=4, output=16
All jobs processed in 2.002039792s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로서 Worker Pool을 사용하면 적절한 자원으로 쉬지 않고 작업을 처리해 낼 수 있어 성능 개선에 큰 영향을 미치게 됩니다.&lt;br /&gt;저는 Buffering 할 때 블록체인의 요청 부분을 Worker Pool로 처리하도록 개선했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;sync.Map&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;thread-safe 한 map 구조를 제공하여 동시에 읽기, 쓰기를 가능하게 하는 자료구조&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 역시 기본적으로 자주 활용되는 기본 패키지의 구조 중 하나입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748933458782&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;strconv&quot;
	&quot;sync&quot;
)

func main() {
	var m sync.Map
	var wg sync.WaitGroup

	// 100개의 고루틴이 동시에 Store
	for i := range 10 {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			key := &quot;key&quot; + strconv.Itoa(i)
			m.Store(key, i*i)
		}(i)
	}
	wg.Wait()

	wg = sync.WaitGroup{}

	// 100개의 고루틴이 동시에 Load
	for i := 0; i &amp;lt; 10; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			key := &quot;key&quot; + strconv.Itoa(i)
			val, ok := m.Load(key)
			if ok {
				fmt.Printf(&quot;Loaded %s: %v\n&quot;, key, val)
			} else {
				fmt.Printf(&quot;Failed to load %s\n&quot;, key)
			}
		}(i)
	}
	wg.Wait()
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1748933508079&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Loaded key9: 81
Loaded key6: 36
Loaded key1: 1
Loaded key0: 0
Loaded key2: 4
Loaded key4: 16
Loaded key8: 64
Loaded key5: 25
Loaded key7: 49
Loaded key3: 9&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 Store, Load 해도 안전합니다. 저는 이 구조를 &lt;b&gt;Buffer&lt;/b&gt;로 사용하려고 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;sync.Cond&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;특정 조건이 충족될 때까지 고루틴을 대기시키고, 조건이 만족되면 신호를 보내서 고루틴을 활성화시키는 구조&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 해결하는데 핵심 역할을 했던 구조입니다. 기본적으로 Mutex에 대한 이해가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용부터 사용하는 법까지 낯설긴 한데 간단한 예시 코드로 빠르게 이해해 보도록 하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748934115063&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

func main() {
	var wg sync.WaitGroup
	var mu sync.Mutex
	cond := sync.NewCond(&amp;amp;mu)

	wg.Add(2)

	go func() {
		fmt.Println(&quot;Goroutine 1 is started&quot;)
		defer wg.Done()

		cond.L.Lock()
		defer cond.L.Unlock()

		fmt.Println(&quot;Goroutine 1 is waiting for condition&quot;)
		cond.Wait()
		fmt.Println(&quot;Goroutine 1 met the condition&quot;)
		fmt.Println(&quot;Goroutine 1 is done&quot;)
	}()

	go func() {
		fmt.Println(&quot;Goroutine 2 is started&quot;)
		defer wg.Done()

		time.Sleep(5 * time.Second) // Simulating some work

		cond.L.Lock()
		defer cond.L.Unlock()

		fmt.Println(&quot;Goroutine 2 is signaling condition&quot;)
		cond.Signal()
		fmt.Println(&quot;Goroutine 2 completed signaling&quot;)
		fmt.Println(&quot;Goroutine 2 is done&quot;)
	}()

	wg.Wait()
}&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;고루틴 2 를 보면 5초 정도 Sleep을 하게 됩니다. 동시에 고루틴 1은 cond.Wait 에서 잠시 대기하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wait 전에는 Lock을 먼저 획득해야 합니다. Wait 안에서 Unlock 후 noti가 올 때까지 대기한 후 다시 Lock을 획득한 후 탈출합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H6ZeX/btsOmTyXsUf/WriEmHT9TsIAr20L8DVbJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H6ZeX/btsOmTyXsUf/WriEmHT9TsIAr20L8DVbJK/img.png&quot; data-alt=&quot;Wait 의 동작 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H6ZeX/btsOmTyXsUf/WriEmHT9TsIAr20L8DVbJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH6ZeX%2FbtsOmTyXsUf%2FWriEmHT9TsIAr20L8DVbJK%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;762&quot; height=&quot;266&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Wait 의 동작 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1748934140026&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Goroutine 2 is started
Goroutine 1 is started
Goroutine 1 is waiting for condition
Goroutine 2 is signaling condition
Goroutine 2 completed signaling
Goroutine 2 is done
Goroutine 1 met the condition
Goroutine 1 is done&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 보면 1, 2 누가 먼저 실행하던 2 에서 5초를 대기하고 1 은 대기하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5초 후 2 에서 Lock을 획득하게 됩니다. 1 에서 Wait을 실행하면서 Unlock을 했기 때문에 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Signal을 통해 대기 중인 고루틴들 중 랜덤으로 하나를 깨우게 됩니다. (Lock이 꼭 필요한 건 아니며 고루틴간 동기화를 보여주기 위함입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wait 안에서 대기 중이던 1 은 다시 Lock 획득을 시도하는데 2 가 아직 Unlock을 하지 않아서 &lt;b&gt;Gorutine 2 is done &lt;/b&gt;까지 기다립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 1 은 Lock을 획득 후 탈출하며 남을 로직을 실행하고 Unlock 후 마무리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signal 뿐만 아니라 대기 중인 모든 고루틴을 깨우는 Broadcast 기능도 있습니다. 실제 &lt;b&gt;Buffering&lt;/b&gt;을 구현한 코드에서 다시 설명드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Buffering 구현하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 개선하기 위해 사전에 알아야 할 지식들을 잠깐 다뤄봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Source 내부에서 Buffering 구현하기 위해 위 내용들을 어떻게 적용했는지 살펴보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Buffering 특징과 요구사항&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;블록체인의 블록은 항상 Sequencial Data&lt;/li&gt;
&lt;li&gt;데이터 누락이 발생하면 안됨&lt;/li&gt;
&lt;li&gt;Buffer 크기를 제한&lt;/li&gt;
&lt;li&gt;Worker 수를 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 블록체인의 특징 중 하나인 순서는 Buffering을 구현하기 매우 좋습니다. 또한 끝이 없기 때문에 계속해서 데이터를 순서대로 Buffering 하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 데이터 누락이 발생하면 안 되므로 에러에 대한 핸들링은 견고하게 해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Buffer 크기를 제한하지 않으면 OOM을 발생시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Worker 수를 제한하지 않으면 Worker 가 많을 때 더 빠른 성능을 보여줄 것 같지만 오히려 블록체인 노드의 부하를 주게 되어 Buffering 보다 더 안 좋은 성능을 보여줄 수도 있습니다. 노드와 Indexer 간 모니터링을 통해 적절한 Worker 수를 설정하게 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특징과 요구사항을 만족할 수 있도록 구현해야 하며&amp;nbsp;이를 통해 어떤 흐름으로 구현될지 나열해 보도록 하겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Buffering 플로우&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시작 전 Buffer 초기화&lt;/li&gt;
&lt;li&gt;Start blockNumber 부터 Workers 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Processor가 Block이 필요하면 Load &amp;amp; Delete (Reader)&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;필요한 Block이 없을 때 Reader는 대기 상태&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Worker가 Buffer에 Store 하면 대기하던 Reader들에게 Broadcast&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reader가 만족했다면 buffer에서 Load &amp;amp; Delete&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;or Reader가 만족하지 못했다면 다시 대기 상태&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Processor 종료 후 다음 라운드가 시작되면 3번 반복&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 2번은 실제 구현 코드에서 다시 설명드리도록 하겠습니다. 가장 중요한 부분은 3번 플로우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번의 플로우를 도식화하면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FovZv/btsOl1RIZfL/MjUKDnRQPAwfKXa2EIB9kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FovZv/btsOl1RIZfL/MjUKDnRQPAwfKXa2EIB9kk/img.png&quot; data-alt=&quot;1. 필요한 Block이 없을 때 Reader는 대기 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FovZv/btsOl1RIZfL/MjUKDnRQPAwfKXa2EIB9kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFovZv%2FbtsOl1RIZfL%2FMjUKDnRQPAwfKXa2EIB9kk%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;822&quot; height=&quot;322&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1. 필요한 Block이 없을 때 Reader는 대기 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100, 101, 102 Block 이 필요한 Reader 들은 현재 Buffer에 원하는 데이터가 없어 모두 대기하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Buffer에 없다면 블록체인에서 조회해 오면 되지 않나 생각할 수 있는데 이는 Cache처럼 동작하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buffer는 누락 없이 필요한 데이터들을 버퍼링 할 것입니다. 즉, 현재 응답을 기다리는 상황으로 다시 요청해서 응답을 받는 속도보다 더 빠른 성능을 보장해 줄 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAy8Rs/btsOnvxHLxI/uVWhKkx1cFwpMcOilAKdUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAy8Rs/btsOnvxHLxI/uVWhKkx1cFwpMcOilAKdUK/img.png&quot; data-alt=&quot;2, 3, 4. Worker의 Broadcast, Redaer의 L&amp;amp;amp;D 및 대기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAy8Rs/btsOnvxHLxI/uVWhKkx1cFwpMcOilAKdUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAy8Rs%2FbtsOnvxHLxI%2FuVWhKkx1cFwpMcOilAKdUK%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;1082&quot; height=&quot;322&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2, 3, 4. Worker의 Broadcast, Redaer의 L&amp;amp;D 및 대기&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;Worker(W)가 102 Block 데이터를 Buffering 하게 되면 대기하던 모든 Reader를 깨우게 됩니다. W는 다시 놀지 않고 다음 Job(Block)을 요청하러 떠나게 되죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서도 일치하는 Reader만 깨울 수 있지 않을까 생각할 수 있는데 구현의 복잡도와 실제 대기 중인 Reader가 많지 않아 불필요하다고 생각했습니다. 실제 응답 시간이 몇십 초 정도 소요되는 무거운 요청이라 Buffering만 되어도 충분하다고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 깨어난 Reader들은 자신의 데이터가 Buffer에 있는지 확인하고 있다면 Load &amp;amp; Delete를 수행하여 Buffer를 비워줍니다. 그림에선 102 Reader에 해당하네요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않은 Reader들은 다시 대기 상태로 돌아갑니다.&lt;/p&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; 일어나게 됩니다. 앞서 언급했던 무거운 요청의 응답 시간이 초 ~ 몇 십 초 정도 되어 미리 Buffering 하지 않는다면 블록체인을 절대 따라갈 수 없을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 플로우를 다시 정리해 보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wNtwl/btsOobFx19V/Vtznum5QNs7UTmzlwHNFRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wNtwl/btsOobFx19V/Vtznum5QNs7UTmzlwHNFRk/img.png&quot; data-alt=&quot;Buffering 플로우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wNtwl/btsOobFx19V/Vtznum5QNs7UTmzlwHNFRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwNtwl%2FbtsOobFx19V%2FVtznum5QNs7UTmzlwHNFRk%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;1460&quot; height=&quot;720&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Buffering 플로우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Buffer의 크기 초기화 - 6&lt;/li&gt;
&lt;li&gt;Worker 개수 초기화 - 3&lt;/li&gt;
&lt;li&gt;Processor의 일괄 처리 Block 수 - 3&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 설정으로 Buffering 전체 플로우를 도식화한 모습입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 동작하던 Source와 Processor 간 상호 작용에는 달라진 점 없이 Source 내부에 Buffering 기능을 추가만 했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코드 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 위의 Buffering 플로우를 그대로 코드로 옮겨보도록 하겠습니다. 여러 Indexer 중 Tron 블록체인 전용 Indexer를 예시로 잡았습니다. 먼저 초기화 과정입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748955673472&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type sourceBuffer struct {
	...
	blockBuffer          sync.Map
	blockBalanceBuffer   sync.Map
	txInfoBuffer         sync.Map

	// rwCond is used for reading and writing operations in the buffer.
	rwCond *sync.Cond
	// bufferingCond is used to signal when the buffer is full or has space available.
	bufferingCond *sync.Cond

	bufferingTargetCount int64 // count of buffered items

	bufferCount  atomic.Int64 // current count in buffer
	bufferLimit  int64        // max count in buffer
	workers      int64
	...
}

func newSourceBuffer(bufferLimit, workers int64) *sourceBuffer {
	sb := &amp;amp;sourceBuffer{
		rwCond:               sync.NewCond(&amp;amp;sync.Mutex{}),
		bufferingCond:        sync.NewCond(&amp;amp;sync.Mutex{}),
		blockBuffer:          sync.Map{}, // target 1
		blockBalanceBuffer:   sync.Map{}, // target 2
		txInfoBuffer:         sync.Map{}, // target 3
		bufferingTargetCount: 3,
		workers:              workers,
		...
	}
	if bufferLimit == 0 {
		b.bufferLimit = defaultBufferLimit * b.bufferingTargetCount
	} else {
		b.bufferLimit = bufferLimit * b.bufferingTargetCount
	}
	if sb.workers == 0 {
		sb.workers = defaultWorkers
	}
	return sb
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;xxxBuffer&lt;/b&gt; 필드들은 Buffer 저장 공간을 의미하는 필드들입니다. 모두 sync.Map으로 tread-safe 하게 접근하도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;xxxCond 는 &lt;/b&gt;Buffering 에서 동시에 실행되는 Gorutine 들의 동기화를 담당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rwCond은 Buffer에서 읽고 쓰기를 진행할 때 사용되는 Condition 이고, bufferingCond는 bufferLimit 에 도달한 후 Buffer가 소비된 다음 비었다는 Signal을 주기 위해서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;bufferingTargetCount&lt;/b&gt;는 하나의 블록에서 Buffering 해야 하는 데이터의 개수입니다. 실제 주입받는 Limit 값은 블록 단위로 받기 때문에 로직에서 targetCount와 곱해서 Limit을 다시 초기화합니다. 아래 코드에서 다시 설명하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;bufferCount와 bufferLimit&lt;/b&gt;의 관계는 count가 limit 을 넘지 않도록 제어하는 용도로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;workers&lt;/b&gt;는 Worker Pool 패턴으로서 몇 개의 Worker를 초기화할지 설정하는 값입니다. 자세한 Worker의 코드는 아래에서 설명하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속해서 다음은 Buffering을 시작하는 run 함수입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748956097526&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func (sb *sourceBuffer) run(fromBlocknumber int64, toBlockNumber *int64) {
	var blockNumberCh = make(chan int64, 1)
	for range sb.workers {
		go sb.worker(blockNumberCh)
	}
	go sb.buffering(blockNumberCh, fromBlocknumber, toBlockNumber)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;newSourceBuffer 로 초기화된 후 run 을 실행해서 Buffering을 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker는 개수만큼 생성되어 열심히 일 할 준비를 마칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fromBlockNumber 부터 toBlockNumber(nil 일 경우 블록체인 동기화를 따라감)까지 buffering 메서드를 통해 시작됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buffering 부터 설명하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748958013295&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func (sb *sourceBuffer) buffering(
	blockNumberCh chan&amp;lt;- int64,
	fromBlockNumber int64,
	toBlockNumber *int64,
) {
	sendToWorker := func(blockNumber int64) {
		for {
			if sb.bufferCount.Load() &amp;gt;= sb.bufferLimit {
				sb.bufferingCond.L.Lock()
				sb.bufferingCond.Wait()
				sb.bufferingCond.L.Unlock()
				continue
			}
			blockNumberCh &amp;lt;- blockNumber
			sb.bufferCount.Add(sb.bufferingTargetCount)
			return
		}
	}

	if toBlockNumber != nil {
		for blockNumber := fromBlockNumber; blockNumber &amp;lt;= *toBlockNumber; blockNumber++ {
			sendToWorker(blockNumber)
		}
		close(blockNumberCh)
		return
	}
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buffering 에서 순차적으로 증가하는 blockNumber를 blockNumberCh를 통해 Worker들에게 전달하는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 bufferCount를 증가시키며 이 값이 Limit에 도달하게 될 경우 bufferingCond.Wait 을 통해 Buffer가 소비될 때까지 대기합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bufferCount는 bufferingTargetCount 만큼 증가시킵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748958649716&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func (sb *sourceBuffer) worker(blockNumberCh &amp;lt;-chan int64) {
	for blockNumber := range blockNumberCh {
		var (
			wg         = &amp;amp;sync.WaitGroup{}
			block      = sb.sour.blockAndTxs(blockNumber)
		)
		sb.blockBuffer.Store(blockNumber, block)
		wg.Add(2)
		go func() {
			defer wg.Done()
			balance := sb.sour.blockBalance(block.BlockID, blockNumber)
			sb.blockBalanceBuffer.Store(blockNumber, balance)
		}()
		go func() {
			defer wg.Done()
			txInfo := sb.sour.txInfoByBlockNumber(blockNumber)
			sb.txInfoBuffer.Store(blockNumber, txInfo)
		}()
		wg.Wait()
		sb.rwCond.L.Lock()
		sb.rwCond.Broadcast()
		sb.rwCond.L.Unlock()
	}
}&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;worker 는 blockNumberCh 에서 받은 blockNumber를 받아 일을 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 코드에선 block, balance, txInfo 를 source 를 통해 요청합니다. 그리고 각자 Buffer에 Store 하게 됩니다. sync.Map 으로 여러 Worker가 병렬로 실행되어도 안전하게 저장할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 것이 완료되면 rwCond를 통해 대기 중이던 Reader들을 모두 깨우기 위해 Broadcast 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 코드를 다시 그림으로 본다면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEmG0s/btsOogtpjFc/YOkfKtioGLmMBROE7oLBd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEmG0s/btsOogtpjFc/YOkfKtioGLmMBROE7oLBd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEmG0s/btsOogtpjFc/YOkfKtioGLmMBROE7oLBd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEmG0s%2FbtsOogtpjFc%2FYOkfKtioGLmMBROE7oLBd1%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;1174&quot; height=&quot;680&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Buffer를 소비하는 코드를 구현하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1748959249870&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func (sb *sourceBuffer) getBlockAndTxs(blockNumber int64) *bufferingTargetBlock {
	sb.rwCond.L.Lock()
	defer sb.rwCond.L.Unlock()
	for {
		if v, ok := sb.blockBuffer.LoadAndDelete(blockNumber); ok {
			sb.bufferCount.Add(-1)
			sb.bufferingCond.L.Lock()
			sb.bufferingCond.Signal()
			sb.bufferingCond.L.Unlock()
			return v.(*bufferingTargetBlock)
		}
		sb.rwCond.Wait()
	}
}

func (sb *sourceBuffer) getBlockBalance(blockNumber int64) []*domain.ChangedAccount {
	sb.rwCond.L.Lock()
	defer sb.rwCond.L.Unlock()
	for {
		if v, ok := sb.blockBalanceBuffer.LoadAndDelete(blockNumber); ok {
			sb.bufferCount.Add(-1)
			sb.bufferingCond.L.Lock()
			sb.bufferingCond.Signal()
			sb.bufferingCond.L.Unlock()
			return v.([]*domain.ChangedAccount)
		}
		sb.rwCond.Wait()
	}
}

func (sb *sourceBuffer) getTxInfo(blockNumber int64) *bufferingTargetTxInfo {
	sb.rwCond.L.Lock()
	defer sb.rwCond.L.Unlock()
	for {
		if v, ok := sb.txInfoBuffer.LoadAndDelete(blockNumber); ok {
			sb.bufferCount.Add(-1)
			sb.bufferingCond.L.Lock()
			sb.bufferingCond.Signal()
			sb.bufferingCond.L.Unlock()
			return v.(*bufferingTargetTxInfo)
		}
		sb.rwCond.Wait()
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bufferingTargetCount 만큼 reader가 구현되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에 하나만 보자면 Buffer에서 읽기 전에 rwCond의 Lock를 먼저 획득하고 sync.Map의 LoadAndDelete를 통해 Buffer에 존재하면 bufferCount를 한 개 내리고 혹시나 Limit 에 도달해서 buffering이 대기 중인 상황을 위해 buffereCond.Signal 를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Buffer에 존재하지 않는다면 rwCond.Wait 을 통해 Unlock 후 대기하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 Worker가 쓰기 연산을 마무리했을 때 rwCond.Broadcast 를 통해 Reader들을 모두 깨워줄 것이므로 누락 없이 읽기가 가능하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 그림을 만족하는 코드가 구현되었네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bme5nE/btsOmuGlh9P/uJJbkwXaKkSTKRLfoRP81k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bme5nE/btsOmuGlh9P/uJJbkwXaKkSTKRLfoRP81k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bme5nE/btsOmuGlh9P/uJJbkwXaKkSTKRLfoRP81k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbme5nE%2FbtsOmuGlh9P%2FuJJbkwXaKkSTKRLfoRP81k%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;988&quot; height=&quot;660&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로서&lt;b&gt; Worker의 쓰기와 Reader의 읽기, 그리고 Buffer의 Limit 까지 sync package로 모두 제어가 가능하도록 구현&lt;/b&gt;되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구현하면서 Go 언어의 sync package가 매우 강력하다고 느끼게 되었고 특히 동시성 프로그래밍을 좀 더 쉽게 접근할 수 있도록 해주는 언어라고 생각됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 환경에서 동일한 블록체인 및 블록에서 테스트한 결과를 통해 어느 정도 성능 개선이 이루어졌는지 확인해 보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;As-Is&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;465&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkaL7h/btsOmo0lUSP/TYd8bqtv7ElopFF9P2ewck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkaL7h/btsOmo0lUSP/TYd8bqtv7ElopFF9P2ewck/img.png&quot; data-alt=&quot;Buffering 전 블록체인 노드의 CPU&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkaL7h/btsOmo0lUSP/TYd8bqtv7ElopFF9P2ewck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkaL7h%2FbtsOmo0lUSP%2FTYd8bqtv7ElopFF9P2ewck%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;2048&quot; height=&quot;465&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;465&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Buffering 전 블록체인 노드의 CPU&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;991&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cf6Sw0/btsOnqbWng4/UOAJVwdZB4QOtV3HMmI3W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cf6Sw0/btsOnqbWng4/UOAJVwdZB4QOtV3HMmI3W1/img.png&quot; data-alt=&quot;Buffering 전 Indexer 모니터링&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cf6Sw0/btsOnqbWng4/UOAJVwdZB4QOtV3HMmI3W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcf6Sw0%2FbtsOnqbWng4%2FUOAJVwdZB4QOtV3HMmI3W1%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;2048&quot; height=&quot;991&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;991&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Buffering 전 Indexer 모니터링&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buffering 전에 블록체인 노드의 CPU를 50% 정도 사용하면서 무거운 요청을 처리하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Indexer 같은 경우 약 700MB 메모리 사용량을 보여주고 있네요.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;To-Be&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t3WHe/btsOpeusLhq/kkHdPkr9ShKqJmIfDPD800/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t3WHe/btsOpeusLhq/kkHdPkr9ShKqJmIfDPD800/img.png&quot; data-alt=&quot;Buffering 후 블록체인 노드의 CPU&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t3WHe/btsOpeusLhq/kkHdPkr9ShKqJmIfDPD800/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft3WHe%2FbtsOpeusLhq%2FkkHdPkr9ShKqJmIfDPD800%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;2048&quot; height=&quot;445&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Buffering 후 블록체인 노드의 CPU&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cv66tP/btsOogUnJcM/TVHPpr1Roe6fsomIoHtqtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cv66tP/btsOogUnJcM/TVHPpr1Roe6fsomIoHtqtk/img.png&quot; data-alt=&quot;Buffering 후 Indexer 모니터링&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cv66tP/btsOogUnJcM/TVHPpr1Roe6fsomIoHtqtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcv66tP%2FbtsOogUnJcM%2FTVHPpr1Roe6fsomIoHtqtk%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;2048&quot; height=&quot;982&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;982&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Buffering 후 Indexer 모니터링&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buffering의 Worker 수에 따라 다르겠지만, 블록체인 노드를 효율적으로 사용하는 모습을 간접적으로 확인할 수 있었습니다. 물론 블록체인 동기화에 영향가지 않도록 적절한 Worker 수를 찾는 것도 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Indexer는 Buffer 크기에 따라 메모리가 증가한 모습을 보여주고 있네요. OOM이 발생하지 않도록 Limit 을 적절하게 조절해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 처리 속도는 얼마나 달라졌는지 확인해 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2962&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXuF2/btsOoPBUHPA/OCWAt8UZIYJcJu6TF0A8vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXuF2/btsOoPBUHPA/OCWAt8UZIYJcJu6TF0A8vk/img.png&quot; data-alt=&quot;A, B Chain 에서 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXuF2/btsOoPBUHPA/OCWAt8UZIYJcJu6TF0A8vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXuF2%2FbtsOoPBUHPA%2FOCWAt8UZIYJcJu6TF0A8vk%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;2962&quot; height=&quot;846&quot; data-origin-width=&quot;2962&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;A, B Chain 에서 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록 타임 및 블록의 크기가 큰 A, B 체인들 기준으로 테스트한 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A 체인은 &lt;b&gt;10 배&lt;/b&gt; 이상, B 체인은 &lt;b&gt;3-4 배&lt;/b&gt; 정도 성능 개선이 이루어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 일부 구간들을 추출해서 본 결과라서 모든 구간에서 같은 성능을 보여주진 않을 것입니다. 그래도 대부분의 블록체인과 블록 구간에서는 Buffering으로 문제를 해결할 수 있었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 관련 문제 정의를 할 때 다양한 부분에서 개선점이 보였습니다. 코드 상에 불필요한 재할당을 제거하거나, 인덱싱하는 DB를 스케일업 한다거나 등의 개선점들도 많았었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 모든 걸 한 번에 바꾸기엔 개선 작업이 커져 검토 과정도 커지게 되므로 정확한 병목 지점을 찾아 그 부분만 개선하는 것으로 결정하게 되었습니다. 또한 예제 코드만 봤었던 sync.Cond를 실제로 적용해보고 싶었는데 마주친 문제를 해결해 줄 수 있는 해결책이 될 수 있어서&amp;nbsp;재밌게 작업했네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 Indexer는 블록체인의 수많은 데이터를 Go 언어의 동시성을 활용하며 적은 리소스로 안전하고 빠른 성능을 보여줄 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글 읽어주셔서 감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hackernoon.com/lang/ko/%EC%B4%88%EB%B3%B4%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C%EC%97%90%EC%84%9C-%EC%8B%B1%ED%81%AC%EC%BD%98%ED%8A%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hackernoon.com/lang/ko/%EC%B4%88%EB%B3%B4%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C%EC%97%90%EC%84%9C-%EC%8B%B1%ED%81%AC%EC%BD%98%ED%8A%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로그래밍 (Programming)/고랭 (Golang)</category>
      <category>blockchain</category>
      <category>Concurrency</category>
      <category>go</category>
      <category>golang</category>
      <category>Sync</category>
      <category>sync.cond</category>
      <category>sync.map</category>
      <category>동시성</category>
      <category>블록체인</category>
      <author>Bbaktaeho</author>
      <guid isPermaLink="true">https://bbaktaeho-95.tistory.com/113</guid>
      <comments>https://bbaktaeho-95.tistory.com/113#entry113comment</comments>
      <pubDate>Tue, 3 Jun 2025 23:12:11 +0900</pubDate>
    </item>
    <item>
      <title>Ethereum EIP-7702 코드와 데이터 분석하기 (Account Abstraction, Pectra)</title>
      <link>https://bbaktaeho-95.tistory.com/112</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;들어가기 전에​&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2025년 5월 7일 Ethereum에서 &lt;b&gt;Pectra Hardfork&lt;/b&gt;가 예정되어 있습니다. 그중 핵심 기능으로 EIP-7702에 제안된 &lt;span style=&quot;text-align: start;&quot;&gt;계정 추상화(Account Abstraction, AA)가 관련된 변화가 포함되어 있습니다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;이 글에서 블록체인 코어가 이를 어떻게 적용했는지 코드를 분석해보고, 변경된 이더리움 블록체인 RPC Spec과 이미 적용된 테스트넷에 데이터를 기반으로 데이터 분석까지 함께 진행해 보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EIP-7702 란?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EIP-7702는 EOA에 컨트랙트 코드를 할당할 수 있는 기능을 제공합니다. 이를 통해 EOA는 트랜잭션 실행 중에 컨트랙트처럼 동작할 수 있습니다.&amp;nbsp;이러한 방식은 기존의 EOA와 컨트랙트 간의 경계를 허물고, 계정 추상화의 비전을 실현하는 데 기여합니다.​&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLvoOa/btsNCcEvYIg/SodrEOykv7Auo8rGrBu3Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLvoOa/btsNCcEvYIg/SodrEOykv7Auo8rGrBu3Dk/img.png&quot; data-alt=&quot;2025년 4월 26일 기준으로 Last Call 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLvoOa/btsNCcEvYIg/SodrEOykv7Auo8rGrBu3Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLvoOa%2FbtsNCcEvYIg%2FSodrEOykv7Auo8rGrBu3Dk%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;2004&quot; height=&quot;656&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2025년 4월 26일 기준으로 Last Call 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이더리움 창시자 비탈릭 부테린이 제안했네요. EIP-4337 도 그랬듯이 비탈릭 부테린이 계정 추상화에 대해 많은 고민과 관심을 갖고 있을 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EIP-7702 제안 시기가 Pectra Hardfork 시기와 딱 1년 차이가 나는 것도 재밌는 부분입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특징&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심 특징은 다음과 같습니다:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;0x04 Transaction&lt;/b&gt; - EOA에게 스마트 컨트랙트를 위임할 수 있는 트랜잭션으로 새로운 &amp;nbsp;0x04 type을 도입&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Delegation Indicator&lt;/b&gt; - (0xef0100 | contract address) tuple로 EOA의 code 저장 공간에 위임받은 컨트랙트를 나타냄&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Clear&lt;/b&gt; - 0x0000000000000000000000000000000000000000 주소로 위임하면 EOA의 위임 컨트랙트를 초기화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이로 인해서 EOA가 ETH 전송, 외부 코드 호출뿐만 아니라 자기 자신의 특별한 기능을 수행해서 구현하기 어려웠던 여러 문제들을 해결할 수 있을 것으로 보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H1TBY/btsNAJcDUWE/Nw05DKZGXCSAbjgekkR6wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H1TBY/btsNAJcDUWE/Nw05DKZGXCSAbjgekkR6wk/img.png&quot; data-alt=&quot;Ethereum Blog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H1TBY/btsNAJcDUWE/Nw05DKZGXCSAbjgekkR6wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH1TBY%2FbtsNAJcDUWE%2FNw05DKZGXCSAbjgekkR6wk%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;2022&quot; height=&quot;628&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Ethereum Blog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Ethereum Blog에서는 배치 트랜잭션, Gas 스폰서십, 대체 인증 등의 구현을 지원할 수 있다고 보고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코드 분석하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제로 EIP-7702가 코드로 어떻게 구현되었는지 go-ethereum 오픈소스를 통해서 분석해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0x04 Transaction 살펴보기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;go-ethereum에서 해당 트랜잭션의 테스트 코드를 확인해 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 코드에서 어떻게 트랜잭션을 생성하는지 살펴보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duGG30/btsNAVwPFec/hngDO1NpmkL1IVZQtaSqk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duGG30/btsNAVwPFec/hngDO1NpmkL1IVZQtaSqk0/img.png&quot; data-alt=&quot;0x04 타입 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duGG30/btsNAVwPFec/hngDO1NpmkL1IVZQtaSqk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduGG30%2FbtsNAVwPFec%2FhngDO1NpmkL1IVZQtaSqk0%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;304&quot; height=&quot;201&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;0x04 타입 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1706&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2MmzC/btsNAUSbRQk/FuSkuFjxiLOMg4GHk9h2d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2MmzC/btsNAUSbRQk/FuSkuFjxiLOMg4GHk9h2d1/img.png&quot; data-alt=&quot;0x04 트랜잭션을 전송하기 위한 구현체&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2MmzC/btsNAUSbRQk/FuSkuFjxiLOMg4GHk9h2d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2MmzC%2FbtsNAUSbRQk%2FFuSkuFjxiLOMg4GHk9h2d1%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;1706&quot; height=&quot;1066&quot; data-origin-width=&quot;1706&quot; data-origin-height=&quot;1066&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;0x04 트랜잭션을 전송하기 위한 구현체&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;신규 트랜잭션 0x04의 모습입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AuthList라는 field가 추가되었고 여러 개의 SetCodeAuthroization을 받게 구현되어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간단하게 설명하면 트랜잭션에&lt;b&gt; 어떤 코드를 위임받을지에 대한 내용이 서명된 상태&lt;/b&gt;로 여러 개를 받아 전송할 수 있는 구조입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 이미 누군가의 서명된 값을 모아서 제삼자가 트랜잭션을 전송할 수 있다는 얘기도 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SetCodeAuthorization&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F1oyj/btsNBwXy8ux/Q7pG4WjE44fLtKv9UuVOx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F1oyj/btsNBwXy8ux/Q7pG4WjE44fLtKv9UuVOx1/img.png&quot; data-alt=&quot;SetCodeAuthorization&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F1oyj/btsNBwXy8ux/Q7pG4WjE44fLtKv9UuVOx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF1oyj%2FbtsNBwXy8ux%2FQ7pG4WjE44fLtKv9UuVOx1%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;584&quot; height=&quot;426&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SetCodeAuthorization&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SetCodeAuthorization은 어떤 스마트 컨트랙트(Address)를 위임해서 사용할 건지 설명합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Nonce, V, R, S는 위임받고 싶어 하는 EOA의 서명 내용과 관련 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적인 트랜잭션 서명과 다르게 &lt;b&gt;MAGIC parameter로 0x05 prefix가 필요&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X0Tew/btsNAPKnzZ4/ZNTKcA2EJey79BBcUsLlyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X0Tew/btsNAPKnzZ4/ZNTKcA2EJey79BBcUsLlyK/img.png&quot; data-alt=&quot;MAGIC(0x05) Parameter&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X0Tew/btsNAPKnzZ4/ZNTKcA2EJey79BBcUsLlyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX0Tew%2FbtsNAPKnzZ4%2FZNTKcA2EJey79BBcUsLlyK%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;1328&quot; height=&quot;192&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MAGIC(0x05) Parameter&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;1344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/27RqD/btsNAa2HyWH/kw2ITjcdGv4aBjzHbi6mt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/27RqD/btsNAa2HyWH/kw2ITjcdGv4aBjzHbi6mt0/img.png&quot; data-alt=&quot;서명할 때 MAGIC이 필요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/27RqD/btsNAa2HyWH/kw2ITjcdGv4aBjzHbi6mt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F27RqD%2FbtsNAa2HyWH%2Fkw2ITjcdGv4aBjzHbi6mt0%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;2132&quot; height=&quot;1344&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;1344&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서명할 때 MAGIC이 필요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;sigHash 함수에서 MAGIC Parameter가 활용되었네요. MAGIC이 서명에서 중요한 부분이며 이를 누락하면 위임될 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;여기서 핵심은 prviate key가 위임받을 EOA의 키가 되어야 합니다.&lt;/b&gt; 즉 0x04 트랜잭션의 서명자와 다를 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 SetCodeAuthorization 서명에 필요한 nonce도 EOA의 nonce의 순서가 지켜질 수 있도록 해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서명까지 마무리되었다면 이제 트랜잭션에 담을 수 있는 authorization 이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;언어별로 구현체가 있겠지만 raw transaction을 직접 생성한다면 V, R, S 필드를 모두 채워줘야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Delegation Indicator (EOA의 code)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SetCodeAuthorization이 정상적으로 완료된다면 EOA의 code 저장 공간에는 Delegation이 저장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저장될 때 특별한 규칙이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2026&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zwGWy/btsNAd6aR6u/XsHxQMnJFxNdlelfSpKgO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zwGWy/btsNAd6aR6u/XsHxQMnJFxNdlelfSpKgO0/img.png&quot; data-alt=&quot;EIP-7702&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zwGWy/btsNAd6aR6u/XsHxQMnJFxNdlelfSpKgO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzwGWy%2FbtsNAd6aR6u%2FXsHxQMnJFxNdlelfSpKgO0%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;2026&quot; height=&quot;136&quot; data-origin-width=&quot;2026&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;EIP-7702&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ey7ipz/btsNA99sPhf/V1Or5SIOdEXlWoaS6BB8UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ey7ipz/btsNA99sPhf/V1Or5SIOdEXlWoaS6BB8UK/img.png&quot; data-alt=&quot;delegation prefix와 유틸 함수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ey7ipz/btsNA99sPhf/V1Or5SIOdEXlWoaS6BB8UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fey7ipz%2FbtsNA99sPhf%2FV1Or5SIOdEXlWoaS6BB8UK%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;1630&quot; height=&quot;880&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;delegation prefix와 유틸 함수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;1142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceA93L/btsNz33zz87/TkbquoglNU6NlANhvTISR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceA93L/btsNz33zz87/TkbquoglNU6NlANhvTISR1/img.png&quot; data-alt=&quot;이더리움 0x04 트랜잭션 execute&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceA93L/btsNz33zz87/TkbquoglNU6NlANhvTISR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceA93L%2FbtsNz33zz87%2FTkbquoglNU6NlANhvTISR1%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;1942&quot; height=&quot;1142&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;1142&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이더리움 0x04 트랜잭션 execute&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위임받을 컨트랙트 코드에 &lt;b&gt;0xef0100 prefix를&lt;/b&gt; 추가해서 EOA(applyAuthorization에선 authority)에게 SetCode 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 코드로 다시 보기&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1745638996173&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func TestEIP7702(t *testing.T) {
	var (
		...
		key1, _ = crypto.HexToECDSA(&quot;b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291&quot;)
		key2, _ = crypto.HexToECDSA(&quot;8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a&quot;)
		addr1   = crypto.PubkeyToAddress(key1.PublicKey)
		addr2   = crypto.PubkeyToAddress(key2.PublicKey)
		aa      = common.HexToAddress(&quot;0x000000000000000000000000000000000000aaaa&quot;)
		bb      = common.HexToAddress(&quot;0x000000000000000000000000000000000000bbbb&quot;)
		...
	)
	gspec := &amp;amp;Genesis{
		Config: &amp;amp;config,
		Alloc: types.GenesisAlloc{
			addr1: {Balance: funds},
			addr2: {Balance: funds},
			aa: { // The address 0xAAAA calls into addr2
				Code:    program.New().Call(nil, addr2, 1, 0, 0, 0, 0).Bytes(),
				Nonce:   0,
				Balance: big.NewInt(0),
			},
			bb: { // The address 0xBBBB sstores 42 into slot 42.
				Code:    program.New().Sstore(0x42, 0x42).Bytes(),
				Nonce:   0,
				Balance: big.NewInt(0),
			},
		},
	}

	// Sign authorization tuples.
	// The way the auths are combined, it becomes
	// 1. tx -&amp;gt; addr1 which is delegated to 0xaaaa
	// 2. addr1:0xaaaa calls into addr2:0xbbbb
	// 3. addr2:0xbbbb writes to storage
	auth1, _ := types.SignSetCode(key1, types.SetCodeAuthorization{
		ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
		Address: aa,
		Nonce:   1,
	})
	auth2, _ := types.SignSetCode(key2, types.SetCodeAuthorization{
		Address: bb,
		Nonce:   0,
	})

	_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
		b.SetCoinbase(aa)
		txdata := &amp;amp;types.SetCodeTx{
			ChainID:   uint256.MustFromBig(gspec.Config.ChainID),
			Nonce:     0,
			To:        addr1,
			Gas:       500000,
			GasFeeCap: uint256.MustFromBig(newGwei(5)),
			GasTipCap: uint256.NewInt(2),
			AuthList:  []types.SetCodeAuthorization{auth1, auth2},
		}
		tx := types.MustSignNewTx(key1, signer, txdata)
		b.AddTx(tx)
	})
	chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil)
	if err != nil {
		t.Fatalf(&quot;failed to create tester chain: %v&quot;, err)
	}

	...

	// Verify delegation designations were deployed.
	state, _ := chain.State()
	code, want := state.GetCode(addr1), types.AddressToDelegation(auth1.Address)
	if !bytes.Equal(code, want) {
		t.Fatalf(&quot;addr1 code incorrect: got %s, want %s&quot;, common.Bytes2Hex(code), common.Bytes2Hex(want))
	}
	code, want = state.GetCode(addr2), types.AddressToDelegation(auth2.Address)
	if !bytes.Equal(code, want) {
		t.Fatalf(&quot;addr2 code incorrect: got %s, want %s&quot;, common.Bytes2Hex(code), common.Bytes2Hex(want))
	}
	// Verify delegation executed the correct code.
	var (
		fortyTwo = common.BytesToHash([]byte{0x42})
		actual   = state.GetState(addr2, fortyTwo)
	)
	if actual.Cmp(fortyTwo) != 0 {
		t.Fatalf(&quot;addr2 storage wrong: expected %d, got %d&quot;, fortyTwo, actual)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 어카운트들을 생성합니다.&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EOA (key1, addr1), (key2, addr2) 두 개 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CA aa, bb 생성&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CA의 코드를 초기화합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EOA들이 코드를 위임받기 위해 SetCodeAuthorization을 생성 및 서명합니다. (Nonce 중요)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;블록을 만들어 0x04 트랜잭션을 생성하여 추가시킵니다.&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 서명자는 addr1이며 트랜잭션이 먼저 검증되므로 nonce는 0으로 초기화합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서명이 완료된 SetCodeAuthorization 들을 포함합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트를 위해 가상의 블록체인을 생성합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EOA address의 GetCode(eth_getCode)를 통해서 Delegation Indicator를 비교합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EOA를 통해서 코드를 호출했을 때 원하는 결과 값과 일치하는지 비교합니다. (위임되었는지 확인)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AuthList 가 문제 있는 경우라면?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;go-ethereum의 core/error.go 에 EIP-7702 관련 에러를 정의해 뒀습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pKN8u/btsNBEVA3fo/9Fvnm2Lh0HBeY85RfqWCp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pKN8u/btsNBEVA3fo/9Fvnm2Lh0HBeY85RfqWCp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pKN8u/btsNBEVA3fo/9Fvnm2Lh0HBeY85RfqWCp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpKN8u%2FbtsNBEVA3fo%2F9Fvnm2Lh0HBeY85RfqWCp1%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;2560&quot; height=&quot;894&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;894&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ErrEmptyAuthList, ErrSetCodeTxCreate 에러 같은 경우, 트랜잭션 메시지의 &lt;b&gt;preCheck&lt;/b&gt;에서 발생할 수 있으므로 트랜잭션 실패로 이어질 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다만, ErrAuthorization... 에러들은 트랜잭션 실행 및 성공 실패에 관여하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2126&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tyGmn/btsNAZzeoVh/WXyDPXthR5cunmzYKdwzNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tyGmn/btsNAZzeoVh/WXyDPXthR5cunmzYKdwzNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tyGmn/btsNAZzeoVh/WXyDPXthR5cunmzYKdwzNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtyGmn%2FbtsNAZzeoVh%2FWXyDPXthR5cunmzYKdwzNk%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;2126&quot; height=&quot;576&quot; data-origin-width=&quot;2126&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;applyAuthorization는 에러를 반환하지만 스킵합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 말해서 AuthList가 잘못되었다 하더라도 transaction은 실패하지 않게 됩니다. 즉, &lt;b&gt;개발자 혹은 데이터 분석가들이 0x04 트랜잭션을 분석할 때 AuthList 가 존재한다고 해서 무조건 위임받았다고 볼 수 없습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정확한 분석을 위해서 블록체인 노드의 RPC를 통해 delegation indicator를 확인해줘야 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Ethereum JSONRPC Spec&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기까지 이해하셨다면 스캔 혹은 RPC를 통해 트랜잭션 분석을 쉽게 접근할 수 있을 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 eth_transactionByHash, eth_transactionReceipt RPC를 통해 추가된 0x04 트랜잭션이 어떻게 응답되는지 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;eth_transactionByHash&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1745642789549&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;id&quot;: 1,
  &quot;result&quot;: {
    &quot;blockHash&quot;: &quot;0x04b8f42e9b84559fe410ee430634cd68c24defd86c523d0d3b93886d05b88beb&quot;,
    &quot;blockNumber&quot;: &quot;0x79fa67&quot;,
    &quot;from&quot;: &quot;0x0b21d03690d8322ada9c65fb671fa1dd97b2cb72&quot;,
    &quot;gas&quot;: &quot;0x372c6&quot;,
    &quot;gasPrice&quot;: &quot;0xb3c316&quot;,
    &quot;maxFeePerGas&quot;: &quot;0xd540de&quot;,
    &quot;maxPriorityFeePerGas&quot;: &quot;0xf4240&quot;,
    &quot;hash&quot;: &quot;0x83b4bd33ef9274522ee8c3d90debbc7222bdfa98e52dba844830bb475be16240&quot;,
    &quot;input&quot;: &quot;0xc91e20100000000000000000000000008a910720406ce2109fab303bdebed6a2f961d81e&quot;,
    &quot;nonce&quot;: &quot;0x69&quot;,
    &quot;to&quot;: &quot;0x0b21d03690d8322ada9c65fb671fa1dd97b2cb72&quot;,
    &quot;transactionIndex&quot;: &quot;0x5a&quot;,
    &quot;value&quot;: &quot;0x0&quot;,
    &quot;type&quot;: &quot;0x4&quot;,
    &quot;accessList&quot;: [],
    &quot;chainId&quot;: &quot;0xaa36a7&quot;,
    &quot;authorizationList&quot;: [
      {
        &quot;chainId&quot;: &quot;0xaa36a7&quot;,
        &quot;address&quot;: &quot;0x973c5f90d1fdd41c7befd3b2dca48f73609a3b46&quot;,
        &quot;nonce&quot;: &quot;0x3e7&quot;,
        &quot;yParity&quot;: &quot;0x1&quot;,
        &quot;r&quot;: &quot;0x412ac85d0fd6af584bc61eea8c78a27ddd5db9b123b2c879150e3007c442a536&quot;,
        &quot;s&quot;: &quot;0x1b4167d50ff178b2ec31764f3eb29da62372cee6ac469018ada484e50e01da8c&quot;
      }
    ],
    &quot;v&quot;: &quot;0x1&quot;,
    &quot;r&quot;: &quot;0x96a5aca1d2e88e358d349f3579a36465ebf4f013e915855fa1ea7b1ef7871f16&quot;,
    &quot;s&quot;: &quot;0x2da8907da3ae77d9a669bbe6749be34415fc0fe7a832ae1a39baf4c5f894cdaa&quot;,
    &quot;yParity&quot;: &quot;0x1&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 type이 0x4 이면서 &lt;b&gt;authorizationList&lt;/b&gt; 필드가 추가되었네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;chainId, address, nonce, yParity, r, s&lt;/b&gt; 정보들로 어떤 EOA가 CA를 위임받으려고 하는지 알아볼 수 있을 겁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;1238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doluxc/btsNzZG1asW/3UxxgQZHv7v1HE79knlFnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doluxc/btsNzZG1asW/3UxxgQZHv7v1HE79knlFnk/img.png&quot; data-alt=&quot;SetCodeAuthorization 에서 EOA(Authority) 찾기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doluxc/btsNzZG1asW/3UxxgQZHv7v1HE79knlFnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdoluxc%2FbtsNzZG1asW%2F3UxxgQZHv7v1HE79knlFnk%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;1720&quot; height=&quot;1238&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;1238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SetCodeAuthorization 에서 EOA(Authority) 찾기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;figure data-ke-type=&quot;image&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;별도 코드를 작성해서 확인해 보니 &lt;b&gt;0x0b21d03690d8322ada9c65fb671fa1dd97b2cb72로&lt;/b&gt; 확인되네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 실패되어 위임받지 않았을 수도 있으니 찾은 EOA 주소로 eth_getCode를 통해 확인이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;eth_transactionReceipt&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1745644039602&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;id&quot;: 1,
  &quot;result&quot;: {
    &quot;blockHash&quot;: &quot;0x04b8f42e9b84559fe410ee430634cd68c24defd86c523d0d3b93886d05b88beb&quot;,
    &quot;blockNumber&quot;: &quot;0x79fa67&quot;,
    &quot;contractAddress&quot;: null,
    &quot;cumulativeGasUsed&quot;: &quot;0x108c75a&quot;,
    &quot;effectiveGasPrice&quot;: &quot;0xb3c316&quot;,
    &quot;from&quot;: &quot;0x0b21d03690d8322ada9c65fb671fa1dd97b2cb72&quot;,
    &quot;gasUsed&quot;: &quot;0xc72e&quot;,
    &quot;logs&quot;: [
      {
        &quot;address&quot;: &quot;0x0b21d03690d8322ada9c65fb671fa1dd97b2cb72&quot;,
        &quot;topics&quot;: [
          &quot;0x275be3927759bc75800d5e5c5fe82794b62efb216cf7eb3da58b9f5d7ca63290&quot;
        ],
        &quot;data&quot;: &quot;0x0000000000000000000000000b21d03690d8322ada9c65fb671fa1dd97b2cb720000000000000000000000008a910720406ce2109fab303bdebed6a2f961d81e&quot;,
        &quot;blockNumber&quot;: &quot;0x79fa67&quot;,
        &quot;transactionHash&quot;: &quot;0x83b4bd33ef9274522ee8c3d90debbc7222bdfa98e52dba844830bb475be16240&quot;,
        &quot;transactionIndex&quot;: &quot;0x5a&quot;,
        &quot;blockHash&quot;: &quot;0x04b8f42e9b84559fe410ee430634cd68c24defd86c523d0d3b93886d05b88beb&quot;,
        &quot;logIndex&quot;: &quot;0xc7&quot;,
        &quot;removed&quot;: false
      }
    ],
    &quot;logsBloom&quot;: &quot;0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000040000000000000010000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&quot;,
    &quot;status&quot;: &quot;0x1&quot;,
    &quot;to&quot;: &quot;0x0b21d03690d8322ada9c65fb671fa1dd97b2cb72&quot;,
    &quot;transactionHash&quot;: &quot;0x83b4bd33ef9274522ee8c3d90debbc7222bdfa98e52dba844830bb475be16240&quot;,
    &quot;transactionIndex&quot;: &quot;0x5a&quot;,
    &quot;type&quot;: &quot;0x4&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;receipt 정보에서도 type이 0x4 인 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;receipt의 응답이 달라진 건 아니지만 한 가지 &lt;b&gt;확인해 볼 필요가 있는 건 log의 address&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;log는 컨트랙트에서 event를 emit 했을 때 생성되며 address는 CA가 됩니다. 하지만 첨부한 receipt에서 from과 log의 address 가 동일하다는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 또한 EOA가 CA를 위임받아 마치 자기 자신의 코드를 실행시켰다고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이더리움 Pectra Hardfork에서 AA 관련 EIP-7702 제안과 변경 사항을 코드 레벨부터 데이터 분석까지 알아봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 제안은 0x04 트랜잭션을 통해서 EOA에게 CA를 위임할 수 있으며 하나의 트랜잭션에서 여러 EOA들에게 위임을 대신해 줄 수도 있다는 것을 알게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 실제 트랜잭션 데이터를 보고 누가 위임을 받으려고 하는지 EOA를 찾아볼 수 있었고 위임이 되었는지 확인하기 위해 eth_code로 확인이 필요하단 사실도 알게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇다면 이전 AA 관련 제안들은(EIP-4337 등) 물거품이 되는 건지 고민되기도 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ethereum research 혹은 여러 아티클에서 EIP-4337 제안과 EIP-7702 제안을 비교 분석하고 전망을 설명하는 글이 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;etherscan 에서도 bundler, userOps 방식의 AA 스캔을 지원하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2802&quot; data-origin-height=&quot;1054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PH4pD/btsNBEVCmEl/N1BleKofrBM1Es3qO7F5OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PH4pD/btsNBEVCmEl/N1BleKofrBM1Es3qO7F5OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PH4pD/btsNBEVCmEl/N1BleKofrBM1Es3qO7F5OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPH4pD%2FbtsNBEVCmEl%2FN1BleKofrBM1Es3qO7F5OK%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;2802&quot; height=&quot;1054&quot; data-origin-width=&quot;2802&quot; data-origin-height=&quot;1054&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;관심 있으면 관련 내용들도 함께 보면 좋을 것 같네요. 이상 글을 마치겠습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;References&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://etherscan.io/txsAA&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://etherscan.io/txsAA&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://github.com/ethereum/go-ethereum&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/ethereum/go-ethereum&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://eips.ethereum.org/EIPS/eip-7702#rationale&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://eips.ethereum.org/EIPS/eip-7702#rationale&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발 (Develop)/블록체인 (Blockchain)</category>
      <category>aa</category>
      <category>account abstraction</category>
      <category>eip-7702</category>
      <category>ethereum</category>
      <category>계정 추상화</category>
      <category>이더리움</category>
      <author>Bbaktaeho</author>
      <guid isPermaLink="true">https://bbaktaeho-95.tistory.com/112</guid>
      <comments>https://bbaktaeho-95.tistory.com/112#entry112comment</comments>
      <pubDate>Sat, 26 Apr 2025 14:21:56 +0900</pubDate>
    </item>
    <item>
      <title>Tron 온체인 데이터 파헤치기</title>
      <link>https://bbaktaeho-95.tistory.com/111</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Tron 블록체인은 저렴한 수수료와 빠른 블록 생성 시간으로 인해 많은 Web3 빌더들이 이용하고 있는 네트워크입니다.&lt;br /&gt;특히 예전부터 하루에 일정량 무료로 트랜잭션을 전송할 수 있도록 하여 접근성도 좋은 편인 것 같습니다.&lt;br /&gt;또한 여러 시스템 컨트랙트(Actuator)가 빌더들을 위한 기능들을 제공하고 있기도 합니다.&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;최근 Sun Pump가 Tron 네트워크에 릴리즈 되면서 활성 어카운트가 증가하고 있습니다. 이에 따라 자산 이동과 관련된 트랜잭션 TPS 까지 증가되고 있죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;저는 증가하는 관심도에 따라 온체인 데이터를 보고 이해하는 것이 더더욱 중요하다고 생각했습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;따라서 이 글을 통해 Tron 의 온체인 데이터를 분석하여 누구나 온체인 데이터를 이해하고 설명할 수 있도록 공유하고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;분석하기 전에&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이번 글에서는 자산과 관련된 온체인 데이터를 분석해 볼 것입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;자산은 흔히 알고있는 &lt;/span&gt;&lt;b&gt;TRX&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;b&gt;TRC-10&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;b&gt;TRC-20&lt;/b&gt;, &lt;b&gt;721&lt;/b&gt;,&lt;b&gt; 1155&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;nbsp;등의 코인과 토큰들 입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;데이터를 분석하기에 앞서 선행되면 좋은 Tron 의 간단한 설명과 관련 용어를 알아보도록 하겠습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;좀 더 깊은 지식을 원하신다면 Tron 문서를 참고해 주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Resource Model&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Resource Model 은 크게 &lt;b&gt;Bandwidth&lt;/b&gt;, &lt;b&gt;Energy&lt;/b&gt; 두 가지로 나눌 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이 두 모델은 네트워크의 컴퓨팅 파워를 사용하거나, 네트워크상의 전송 비용 등을 표현합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;블록체인 클라이언트의&amp;nbsp;&lt;/span&gt;&lt;b&gt;wallet/getaccountresource&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;nbsp;API 를 통해 Account의 리소스 상태를 확인할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Bandwith Point (BP)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;BP&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;는 하루에 Account가 사용할 수 있는 bytes의 수와 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;예를 들어 네트워크의 트랜잭션을 전송할 때 트랜잭션의 bytes가 200이면 200 BP가 소모되게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;직접 BP 를 얻을 수도 있습니다. 방법은 TRX를 동결해서 BP를 얻는 방식입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;전체 네트워크의 동결된 자산의 양과 계산식에 따라 변동될 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;계산식은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741095672128&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BP = amount of TRX staked / total amount of TRX staked * 43_200_000_000&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;span style=&quot;color: #333333;&quot;&gt;또한 Tron은 매일 무료로 BP를 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Account당 하루에 &lt;b&gt;600 BP&lt;/b&gt;가 무료로 제공되며 계정 잔액이 동결되지 않았거나 BP를 모두 소진한 경우에만 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;과거에는 5000 BP, 1500 BP 도 제공했으나 최근 v4.7.2 버전부터는 600 BP가 제공되고 있네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;만약 BP가 부족한 경우, 소유하고 있는 TRX를 사용하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Energy&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;Energy&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;nbsp;는 스마트 컨트랙트를 실행하는 동안 소모되는 리소스입니다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;BP와 달리 무료로 제공받을 수는 없고 TRX를 스테이킹해야만 얻을 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;계산식은 아래와 같습니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741095690923&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;amount of energy obtained = amount of TRX staked / total amount of TRX staked for obtaining energy in the whole network * 90_000_000_000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;여기서 90_000_000_000 은 일일 총 Energy 공급량입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Energy 역시 부족한 경우, 소유하고 있는 TRX를 사용하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;TRX를 사용할 땐 Energy 당 210 SUN (0.000210 TRX)를 지불해야 합니다. 역시 과거에는 더 비싼 가격이었지만 점점 발전하면서 현재 210 SUN 으로 되었습니다. 아래 데이터 분석에서 다시 다루도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;그 외, &lt;b&gt;Dynamic Energy Model&lt;/b&gt; 이 존재하는데 이는 리소스 균형 메커니즘으로 리소스 점유에 따라 에너지 소비를 동적으로 조정할 수 있는 모델입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;쉽게 얘기하면 인기 있는 컨트랙트에 네트워크 자원이 과도하게 집중되어 페널티를 부여하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;즉, 더 비싼 Energy 값을 줘야 한 다는 것입니다. Tron 은 6시간의 Epoch 으로 블록을 제안 및 합의를 진행하는데 다음 주기가 오기 전 특정 비율 계산을 통해 Dynamic Energy Model 이 동작하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;증가 공식:&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741095504035&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;energy_factor = min((1 + energy_factor) * (1 + increaese_factor)-1, max_factor)&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;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;감소 공식:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741095630527&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;energy_factor = max((1 + energy_factor) * (1 - decrease_factor)- 1, 0)&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;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;트랜잭션을 전송하기 전에&lt;b&gt; wallet/getcontractinfo &lt;/b&gt;API 를 통해 limit 설정을 다이나믹하게 하는 게 좋습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;TRX, TRC-10&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;먼저&amp;nbsp;&lt;/span&gt;&lt;b&gt;TRX&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;nbsp;는 Tron의 공식 코인입니다. 코어에서 Account 구조의 balance 를 담당합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;아래는 Tron 공식 스캔 사이트에서 확인한 TRX 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F0k6O/btsMAIESwOH/2YHQ7Ai8grJxRQgnStbwW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F0k6O/btsMAIESwOH/2YHQ7Ai8grJxRQgnStbwW1/img.png&quot; data-alt=&quot;tronscan TRX&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F0k6O/btsMAIESwOH/2YHQ7Ai8grJxRQgnStbwW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF0k6O%2FbtsMAIESwOH%2F2YHQ7Ai8grJxRQgnStbwW1%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;1247&quot; height=&quot;394&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan TRX&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;네트워크에서 블록 보상과 수수료 모델에 가장 중요한 역할을 하죠.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;가장 작은 단위는&amp;nbsp;&lt;/span&gt;SUN&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;nbsp;이며 &lt;b&gt;1,000,000 SUN 에 1 TRX&lt;/b&gt; 입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;TRC-10&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;nbsp;은 Tron 코어에서 발행되는 토큰입니다. 스캔부터 보겠습니다.&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-origin-width=&quot;945&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8rVq1/btsMAF9fKnp/vkwEiXaOFUw71geqrv3MFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8rVq1/btsMAF9fKnp/vkwEiXaOFUw71geqrv3MFK/img.png&quot; data-alt=&quot;tronscan TRC-10&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8rVq1/btsMAF9fKnp/vkwEiXaOFUw71geqrv3MFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8rVq1%2FbtsMAF9fKnp%2FvkwEiXaOFUw71geqrv3MFK%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;945&quot; height=&quot;381&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan TRC-10&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;흔히 알고 있는 ERC-20 과 형태는 비슷하나 스마트 컨트랙트는 아니며 코어 내부에서 관리됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;실제로 Solidity 언어에서 TRX 뿐만 아니라 TRC-10 도 다룰 수 있도록 되어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740993446246&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pragma solidity ^0.5.0;

contract transferTokenContract {
    constructor() payable public{}
    
    function() payable external {}
    
    function transferTokenTest(address payable toAddress, uint256 tokenValue, trcToken id) payable public    {
        toAddress.transferToken(tokenValue, id);
    }
    
    function msgTokenValueAndTokenIdTest() public payable returns(trcToken, uint256){
        trcToken id = msg.tokenid;
        uint256 value = msg.tokenvalue;
        return (id, value);
    }
    
    function getTokenBalanceTest(address accountAddress) payable public returns (uint256){
        trcToken id = 1000001;
        return accountAddress.tokenBalance(id);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;msg.tokenid, msg.tokenvalue, &amp;lt;address&amp;gt;.tokenBalance(id) &lt;/b&gt;와 같이 스마트 컨트랙트에서 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이 토큰을 발행하려면 1024 TRX 가 필요하며 TIP-10 제안을 통해 순차적인 id 값이 생성됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;TRC-20(ERC-20) 처럼 스마트 컨트랙트를 배포해서 Account Address가 존재하는 건 아닙니다. id 가 유일성을 보장합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;제가 현재까지 분석한 바로는 한 Account 당 한 개의 TRC-10 이 배포되는 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;자세한 내용은&amp;nbsp;&lt;a href=&quot;https://developers.tron.network/docs/trc10-transfer-in-smart-contracts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기를&lt;/a&gt; 확인해 주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;TRX, TRC-10 은 코어 내부에서 설계된 자산이기 때문에 transaction, internalTransaction 에서 자산 이동 관련 정보를 분석할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;TRC-20, 721, 1155&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TRC-20, 721, 1155&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;는 Ethereum 에서 ERC-20, 721, 1155 토큰 표준과 동일합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;역시 스캔에서 확인해 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t6bTH/btsMBZTP76a/ZqFsbGF07YVuOb4TPHfT7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t6bTH/btsMBZTP76a/ZqFsbGF07YVuOb4TPHfT7K/img.png&quot; data-alt=&quot;tronscan TRC-20&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t6bTH/btsMBZTP76a/ZqFsbGF07YVuOb4TPHfT7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft6bTH%2FbtsMBZTP76a%2FZqFsbGF07YVuOb4TPHfT7K%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;1400&quot; height=&quot;458&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan TRC-20&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1411&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7cnRa/btsMCpYYHDe/Wn8ECCIU9cdmTmdtlLAz70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7cnRa/btsMCpYYHDe/Wn8ECCIU9cdmTmdtlLAz70/img.png&quot; data-alt=&quot;tronscan TRC-721&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7cnRa/btsMCpYYHDe/Wn8ECCIU9cdmTmdtlLAz70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7cnRa%2FbtsMCpYYHDe%2FWn8ECCIU9cdmTmdtlLAz70%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;1411&quot; height=&quot;445&quot; data-origin-width=&quot;1411&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan TRC-721&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7IPe6/btsMBjyGn5a/uF0CuE5VyLtl5BQGVB4JQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7IPe6/btsMBjyGn5a/uF0CuE5VyLtl5BQGVB4JQk/img.png&quot; data-alt=&quot;tronscan TRC-1155&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7IPe6/btsMBjyGn5a/uF0CuE5VyLtl5BQGVB4JQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7IPe6%2FbtsMBjyGn5a%2FuF0CuE5VyLtl5BQGVB4JQk%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;1382&quot; height=&quot;434&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan TRC-1155&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tron은 NFT 관련 컨트랙트가 비교적 활성화되어 있지 않은 듯합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스캔에서 보여주는 개수만 봐도 거의 사용되고 있지 않는 수준이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들은 모두 ERC 와 동일한 기능을 가진 컨트랙트이며 표준입니다. Tron 에서 Transaction Info 의 Log 를 통해 표준 토큰의 이벤트를 분석할 수 있습니다.&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;Transaction&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;블록체인을 좋아하는 분들은 누구나 알고 있는 블록체인에 상태를 변경하는 가장 기본적인 단위라 볼 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Tron 역시 트랜잭션으로 자산을 전송하거나 스마트 컨트랙트를 호출하려면 트랜잭션을 생성하고 서명해서 블록에 포함되어야 합니다.&lt;/span&gt;&lt;br /&gt;Transaction&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 블록에 포함되면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Transaction Info&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를 확인할 수 있으며 Info에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Receipt&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Log(event)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Internal Transaction&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&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;C3TZR1g81UNaPs7vzNXHueW5ZM76DSHWEY7onmfLxcK2iPEkUZrDreLBXZwD2ZSunW7ja4ZWcZWctgYrBKaziS9SNCkD7JoQX97rqb4bE6gpZzGBeWL95v6.png&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eoG79h/btsMz0fMY8p/mck0oGmEaLYbUk5sWM5Bo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eoG79h/btsMz0fMY8p/mck0oGmEaLYbUk5sWM5Bo0/img.png&quot; data-alt=&quot;tronscan Block Transaction Overview&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eoG79h/btsMz0fMY8p/mck0oGmEaLYbUk5sWM5Bo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeoG79h%2FbtsMz0fMY8p%2Fmck0oGmEaLYbUk5sWM5Bo0%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;316&quot; height=&quot;340&quot; data-filename=&quot;C3TZR1g81UNaPs7vzNXHueW5ZM76DSHWEY7onmfLxcK2iPEkUZrDreLBXZwD2ZSunW7ja4ZWcZWctgYrBKaziS9SNCkD7JoQX97rqb4bE6gpZzGBeWL95v6.png&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan Block Transaction Overview&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캡처 이미지를 보면 한 블록에 79 개의 Transaction이 포함되었으며 이 트랜잭션 들 중 여러 타입의 트랜잭션들이 오버레이 되어 보여주고 있습니다. 이러한 데이터를 분석하여 자산이 이동된 Trnasfer 개수도 보여주고 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Internal Transaction 의 개수도 보여주고 있습니다.&lt;/p&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: #333333; text-align: start;&quot;&gt;Tron은 core 에서 다양한 기능들을 미리 구현해 뒀습니다. 이러한 기능들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;System Contract(Actuator)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;로 불리며 트랜잭션을 통해 사용해 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741093889911&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum ContractType {
      AccountCreateContract = 0;
      TransferContract = 1;
      TransferAssetContract = 2;
      VoteAssetContract = 3;
      VoteWitnessContract = 4;
      WitnessCreateContract = 5;
      AssetIssueContract = 6;
      WitnessUpdateContract = 8;
      ParticipateAssetIssueContract = 9;
      AccountUpdateContract = 10;
      FreezeBalanceContract = 11;
      UnfreezeBalanceContract = 12;
      WithdrawBalanceContract = 13;
      UnfreezeAssetContract = 14;
      UpdateAssetContract = 15;
      ProposalCreateContract = 16;
      ProposalApproveContract = 17;
      ProposalDeleteContract = 18;
      SetAccountIdContract = 19;
      CustomContract = 20;
      CreateSmartContract = 30;
      TriggerSmartContract = 31;
      GetContract = 32;
      UpdateSettingContract = 33;
      ExchangeCreateContract = 41;
      ExchangeInjectContract = 42;
      ExchangeWithdrawContract = 43;
      ExchangeTransactionContract = 44;
      UpdateEnergyLimitContract = 45;
      AccountPermissionUpdateContract = 46;
      ClearABIContract = 48;
      UpdateBrokerageContract = 49;
      ShieldedTransferContract = 51;
      MarketSellAssetContract = 52;
      MarketCancelOrderContract = 53;
      FreezeBalanceV2Contract = 54;
      UnfreezeBalanceV2Contract = 55;
      WithdrawExpireUnfreezeContract = 56;
      DelegateResourceContract = 57;
      UnDelegateResourceContract = 58;
      CancelAllUnfreezeV2Contract = 59;
    }&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;java-tron protocol 에선 위와 같이 정의되어 있지만 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;제가 조사해 봤을 때는 System Contract는 약 40개 정도 존재하는 것으로 파악했습니다. 또한 구현되어 있지만 네트워크 상에서 사용되지 않는 케이스도 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Transaction은 온체인 데이터를 분석할 때 매우 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이번 글에서 특히 Transaction 과 Transaction Info 그리고 몇 가지 주요 System Contract 를 다뤄보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;온체인 데이터 파헤치기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이제 본격적으로 앞서 언급했던 자산과 관련된 온체인 데이터를 분석해 보겠습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Tron 데이터에 대해서 상세하게 파악해 보고 앞으로 Tron 데이터를 읽을 때 많은 도움이 되었으면 좋겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;데이터는 Tron Node 에서&amp;nbsp;&lt;/span&gt;&lt;b&gt;HTTP API&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;gRPC&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;JSONRPC(Ethereum 호환)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를 통해 온체인 데이터를 조회할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;아마 대부분 익숙한 JSONRPC 로 온체인 데이터를 조회할 텐데요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;JSONRPC 를 통해 Tron 데이터를 조회하면 TRC-10, Resource Model 등의 일부 데이터에 대해서는 조회가 불가능할 것입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;따라서 저는 HTTP API 를 통해서 분석해 보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Transaction 과 Transaction Info&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;가장 기본적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Transaction&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;과&amp;nbsp;&lt;/span&gt;Transaction Info&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;에 대해 먼저 알아야 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;수수료가 어떻게 쓰였고, 어떤 System Contract를 호출했는지 등의 기본적인 정보들을 다 가지고 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;b&gt;wallet/gettransactionbyid&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;wallet/gettransactioninfobyid&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;API 를&lt;/span&gt; 통해서 조회할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Transaction Response&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1741094283893&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;signature&quot;: [...],
    &quot;txID&quot;: &quot;...&quot;,
    &quot;raw_data&quot;: {
        &quot;contract&quot;: [{
            ...,
            &quot;type&quot;: &quot;...&quot;
        }],
        &quot;ref_block_bytes&quot;: &quot;...&quot;,
        &quot;ref_block_hash&quot;: &quot;...&quot;,
        &quot;expiration&quot;: ...
    },
    &quot;raw_data_hex&quot;: &quot;...&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;signature&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 배열인 이유는 &lt;b&gt;Multi-Signature&lt;/b&gt; 때문입니다. 보통은 배열에 하나의 요소만 존재할 것입니다.&lt;br /&gt;데이터를 분석하기에 가장 중요한 field는&lt;span&gt;&amp;nbsp;&lt;b&gt;raw_data.contract&lt;/b&gt;&amp;nbsp;입니다.&lt;/span&gt;&lt;br /&gt;앞서 설명드린 것처럼 Tron 은 모든 트랜잭션이 System Contract를 통해서 처리됩니다. 그 부분이&lt;span&gt;&amp;nbsp;&lt;/span&gt;raw_data.contract&lt;span&gt;&amp;nbsp;&lt;/span&gt;에서 System Contract 데이터가 달라지는 것이죠.&lt;br /&gt;&lt;b&gt;raw_data.contract.type&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에서 어떤 컨트랙트 타입을 호출했는지 확인할 수 있습니다.&lt;br /&gt;가장 기본적인 코인(TRX) 전송 수량이 안보이시죠?&lt;span&gt;&amp;nbsp;&lt;/span&gt;raw_data.contract&lt;span&gt;&amp;nbsp;&lt;/span&gt;안에서 볼 수 있으며 type 에 따라 field 명이 달라집니다. 아래에서 상세하게 다뤄보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 raw_data.contract &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;를 자세히 보시면 배열 형태를 나타내고 있는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;실제 과거 데이터에서 배열로 들어오던 케이스가 있었습니다. 하지만 현재는 배열 안에 단 하나의 요소만 있으니, 단일 요소만 있다고 파악하시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ref_ &lt;/b&gt;prefix 가 붙은 field 들은 Tron TAPOS 메커니즘을 위해 존재하는 데이터입니다. 이는 트랜잭션 재생을 방지하는 용도로 이용된다고만 이해하시고 넘어가도 괜찮습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Transaction Info&lt;/h4&gt;
&lt;pre id=&quot;code_1741094593772&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;...&quot;,
  &quot;fee&quot;: ...,
  &quot;blockNumber&quot;: ...,
  &quot;blockTimeStamp&quot;: ...,
  &quot;contract_address&quot;: &quot;...&quot;,
  &quot;receipt&quot;: {
    &quot;energy_usage&quot;: ...,
    &quot;energy_fee&quot;: ...,
    &quot;origin_energy_usage&quot;: ...,
    &quot;energy_usage_total&quot;: ...,
    &quot;net_usage&quot;: ...,
    &quot;net_fee&quot;: ...,
    &quot;energy_penalty_total&quot;: ...,
    &quot;result&quot;: &quot;SUCCESS&quot;, // FAILED
    ...
  },
  &quot;log&quot;: [...],
  &quot;internal_transactions&quot;: [...],
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 Response 는 핵심 데이터 위주로만 작성했습니다. Transaction Info 역시 System Contract 에 따라 다양한 데이터가 포함된다는 점 참고 부탁드립니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;id &lt;/b&gt;는 transaction의 txID 와 동일합니다. field 이름의 통일성이 없는 게 조금 아쉽네요.&lt;br /&gt;&lt;b&gt;fee &lt;/b&gt;는 이 트랜잭션 수수료로 사용한 전체 TRX 총량입니다. 여기서 조금 헷갈릴 수 있지만 수수료 설명은 별도로 진행하겠습니다.&lt;br /&gt;&lt;b&gt;block_number&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;block_timestamp&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;은 트랜잭션이 포함된 블록의 정보입니다.&lt;br /&gt;&lt;b&gt;contract_address&lt;/b&gt;&amp;nbsp;는&amp;nbsp;&lt;b&gt;TriggerSmartContract&lt;/b&gt;&amp;nbsp;를 통해 호출한 contract 주소입니다. TriggerSmartContract 는 흔히 알고 있는 Smart Contract를 호출할 때 쓰이는 System Contract 입니다.&lt;br /&gt;&lt;b&gt;receipt&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;은 트랜잭션의 성공 실패와 상세한 수수료 정보를 포함하고 있습니다. fee 와 동일하게 아래에서 상세하게 설명하겠습니다.&lt;br /&gt;&lt;b&gt;log&lt;/b&gt;&amp;nbsp;는 스마트 컨트랙트의 event 입니다.&lt;br /&gt;&lt;b&gt;internal_transactions&lt;/b&gt;&amp;nbsp;는 호출했던 스마트 컨트랙트가 내부 로직에서 다른 컨트랙트를 호출할 때 TRX, TRC-10 자산 이동이 있었는지 알 수 있는 데이터입니다.&lt;br /&gt;다만 아쉬운 점이 있는데 내부에서 어떤 기능을 호출했는지를 알 수는 없었습니다. 이 데이터도 아래에서 다시 다루도록 하겠습니다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;수수료&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 fee 와 receipt 에 대해서 분석해 보겠습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;선행으로 배웠던 Resource Model 에서 Bandwidth Point(BP), Energy 가 부족할 때 TRX를 사용한다고 했습니다.&lt;br /&gt;먼저 리소스 소모량은 receipt 에&amp;nbsp;&lt;b&gt;_usage&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 붙은 데이터에서 확인 가능합니다. BP 같은 경우 receipt 에선 &lt;b&gt;net&lt;/b&gt; 으로 표기됩니다. &lt;b&gt;receipt.energy_usage&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;receipt.net_usage&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;receipt.origin_energy_usage&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;이 되겠군요.&lt;br /&gt;여기서&lt;span&gt;&amp;nbsp;&lt;b&gt;receipt.origin_energy_usage&lt;/b&gt;&amp;nbsp;는&lt;/span&gt; 스마트 컨트랙트를 배포할 때 사용된 Energy 수량입니다.&lt;br /&gt;&lt;b&gt;receipt.energy_usage_total&lt;/b&gt;&amp;nbsp;는 전체 Energy 소모량이 됩니다. 여기서 Energy 수량이 부족해서 TRX를 사용하게 돼도 이 값은 존재하게 되는데요.&lt;br /&gt;&lt;b&gt;이는 1 Energy 당 210 SUN 으로 계산됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741094902356&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;receipt.energy_usage_total = receipt.energy_usage + receipt.origin_energy_usage
          or
receipt.energy_usage_total = energy_fee / 210&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;210 SUN 으로 변경된 시점은 비교적 최근입니다. 다음 &lt;a href=&quot;https://github.com/tronprotocol/tips/blob/dc2cc2412bd1e37b926bb735adffab1d2e99a79a/proposal/README.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 참고해서 Energy Price 의 변동 사항을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 리소스가 없을 땐&lt;span&gt;&amp;nbsp;&lt;b&gt;_fee&lt;/b&gt;&amp;nbsp;를&lt;/span&gt; 통해 TRX 소모량을 확인할 수 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;receipt.energy_fee&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;receipt.net_fee&lt;/b&gt;가 리소스를 대체한 TRX 소모량입니다. 이렇게 소모된 TRX 는 소각됩니다. 이 데이터들을 합산한 것이 위에 설명한 것처럼 Info 의&amp;nbsp;fee&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741095100798&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fee = receipt.energy_fee + receipt.net_fee&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;마지막으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;receipt.energy_penalty_total&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;은 Dynamic Energy Model 과 관련 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Tron 은 독특하게 인기 있는 스마트 컨트랙트에게 페널티를 부여합니다. 이는 Resource가 한정적이기 때문에 그렇다고 하네요.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;예제&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1741095759965&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;9735cac6d402d17056ed4505e0071ea77512273be9422bd4937806158e546a82&quot;,
  &quot;fee&quot;: 13499850,
  &quot;blockNumber&quot;: 67844603,
  &quot;blockTimeStamp&quot;: 1734184338000,
  &quot;contract_address&quot;: &quot;TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t&quot;,
  &quot;receipt&quot;: {
    &quot;energy_fee&quot;: 13499850,
    &quot;energy_usage_total&quot;: 64285,
    &quot;net_usage&quot;: 345,
    &quot;result&quot;: &quot;SUCCESS&quot;,
    &quot;energy_penalty_total&quot;: 49635
  },
  &quot;log&quot;: [...],
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터는 USDT 토큰의 Transfer 함수를 실행한 Transaction Info 입니다.&lt;br /&gt;먼저 Tron 에서 가장 활발한 스마트 컨트랙트이기 때문에 페널티가 붙은 모습을 볼 수 있네요.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;energy_fee&amp;nbsp;를 보니 Energy Resource 가 부족해서 TRX 를 사용했나 봅니다. 전체 합계인&lt;span&gt;&amp;nbsp;fee&amp;nbsp;와&lt;/span&gt; 동일하네요.&lt;br /&gt;energy_usage_total&amp;nbsp;는 energy_fee / 210 으로 계산하면 되겠죠? (64285 = 13499850 / 210)&lt;br /&gt;net_usage&amp;nbsp;를 사용하셨네요. BP 가 충분했나 봅니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이제 tronscan을 통해서 보면,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHmLYG/btsMBimg0ls/Y5vMlIiz7Uzxso1btUttR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHmLYG/btsMBimg0ls/Y5vMlIiz7Uzxso1btUttR0/img.png&quot; data-alt=&quot;tronscan USDT Transfer Transaction&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHmLYG/btsMBimg0ls/Y5vMlIiz7Uzxso1btUttR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHmLYG%2FbtsMBimg0ls%2FY5vMlIiz7Uzxso1btUttR0%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;640&quot; height=&quot;340&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan USDT Transfer Transaction&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;log&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 설명드린 것처럼 스마트 컨트랙트의 Event 가 담긴 데이터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TRC-20, 721, 1155 의 전송 내역을 log 에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ERC-20, 721, 1155 와 동일하므로 분석 방법은 생략하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Internal Transaction&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 것처럼 Transaction Info 에 포함되어 있습니다. 다만 이 데이터를 API 를 통해 보려면 node 를 실행할 때 아래 값들을 활성화시켜 줘야 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741096042273&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vm = {
    ...
  saveInternalTx = true
  saveFeaturedInternalTx = true
    ...
}&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;이전 데이터는 Tron 에서 제공하는 Node Snapshot 을 이용하거나 Genesis Block (0 block) 부터 다시 받아야 됩니다.&lt;br /&gt;데이터는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741096076260&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Transaction Info 의 일부
{
    ...,
    &quot;internal_transaction&quot;: [
        &quot;hash&quot;: &quot;...&quot;,
        &quot;caller_address&quot;: &quot;...&quot;,
        &quot;transferTo_address&quot;: &quot;...&quot;,
        callValueInfo: [
            {
                &quot;callValue&quot;: ...,
                &quot;tokenId&quot;: &quot;&quot;,
            },
            ...
        ],
        &quot;note&quot;: ...,
        &quot;rejected&quot;: ...
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;hash&lt;/b&gt;&amp;nbsp;는 Internal Transaction의 고유한 값을 나타냅니다. Transaction의 id 값과 다릅니다.&lt;br /&gt;&lt;b&gt;caller_address&lt;/b&gt;&amp;nbsp;와&amp;nbsp;&lt;b&gt;transferTo_address&lt;/b&gt;&amp;nbsp;는 흔히 알고 있는 from, to 주소 정보가 됩니다. Internal Transaction 이므로 caller_address 는 스마트 컨트랙트의 주소가 될 것입니다.&lt;br /&gt;&lt;b&gt;callValueInfo&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 자산과 관련된 정보입니다. TRC-10 도 스마트 컨트랙트에서 TRX 처럼 전송이 가능합니다. 그래서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;tokenId&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값이 어떤 TRC-10 인지 알 수 있습니다.&lt;br /&gt;만약 tokenId 가 비어있으면 TRX 를 전송한 것입니다. 수량은&lt;span&gt;&amp;nbsp;callValue&amp;nbsp;를&lt;/span&gt; 통해 알 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개인적으로 어떤 기능을 호출했는지 input 값이 있었더라면 좋았을 것 같네요.&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;System Contract&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이제 본격적으로 Transaction 에서&amp;nbsp;&lt;/span&gt;&lt;b&gt;raw_data.contract&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 해당하는 System Contract 에 대해서 분석해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;AccountCreateContract&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AccountCreateContract&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;는 새로운 Account를 네트워크에 활성화시킵니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Tron 은 Account 를 활성화시켜야 블록체인 원장에 기록됩니다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Node의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;wallet/createaccount&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;API 를&lt;/span&gt; 통해서 Account를 활성화시키지만, 굳이 사용하지 않고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;TRX&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;TRC-10&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;전송만으로 새로운 Account가 활성화됩니다. 활성화 트랜잭션은 수수료를 보고 판단할 수도 있습니다. 다만 과거에는 지금과 달라서 판단 기준을 잡으려면 블록체인이 업데이트되는 시점을 찾아야 합니다. 지금은 참고로 이러한 데이터도 있다로 봐주시면 좋을 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741096424343&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;signature&quot;: [...],
  &quot;txID&quot;: &quot;0409c784cd51543340ad13c2ecf300201a1a9678934fc6392d45ce8bbbad1d9f&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;owner_address&quot;: &quot;TW5nGmU4KuZZoactLZ7y7FZnhx6H1jTxLj&quot;,
            &quot;account_address&quot;: &quot;TMmXir2ohc7eTZFZxn1P6AeTbJnwLrT7b5&quot;
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.AccountCreateContract&quot;
        },
        &quot;type&quot;: &quot;AccountCreateContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;8ad8&quot;,
    &quot;ref_block_hash&quot;: &quot;c4c1dc14e7f872d2&quot;,
    &quot;expiration&quot;: 1530784992000
  },
  &quot;raw_data_hex&quot;: &quot;...&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;raw_data.contract&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만 보면 됩니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 AccountCreateContract 라고 친절히 설명해주고 있네요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;데이터를 분석하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 신규&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;account_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;Account 를&lt;/span&gt; 활성화시켰다로 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;TransferContract&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TransferContract&lt;/b&gt;&amp;nbsp;는 가장 기본적인 TRX 전송 컨트랙트입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741096687858&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;signature&quot;: [...],
  &quot;txID&quot;: &quot;0e40b1141a41ca6f4acb52341f17b2e5cbcdb161150362ca40e1eff3ae16eceb&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;amount&quot;: 100000,
            &quot;owner_address&quot;: &quot;TGPnfvkVkUdyCWmrVGnQ9jFW5Ca1S7XH6h&quot;,
            &quot;to_address&quot;: &quot;T9yxiz8M6EJ3RcDPMJo22HrGAwUZ2753u7&quot;
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.TransferContract&quot;
        },
        &quot;type&quot;: &quot;TransferContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;07da&quot;,
    &quot;ref_block_hash&quot;: &quot;7ad993fc087fce41&quot;,
    &quot;expiration&quot;: 1529897868000,
    &quot;timestamp&quot;: 1529897570464000000
  },
  &quot;raw_data_hex&quot;: &quot;...&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;to_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;에게&amp;nbsp;&lt;/span&gt;&lt;b&gt;amount&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만큼 전송했네요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;amount 는 SUN 단위이므로 TRX 로 환산하면 0.1 TRX 를 전송했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;TransferAssetContract&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TransferAssetContract&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;는 TRC-10 을 전송하는 컨트랙트입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;여기선 asset 으로 TRC-10 을 표현했는데 Internal Transaction 에서는 보셨던 것처럼 token 으로 표현하기도 합니다. 개인적으로 field 이름을 통일성 있게 설계했으면 가독성이 좋았을 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741096750673&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...
  &quot;txID&quot;: &quot;3426db0be3d16fd1601b674e84c5ff31947aee0e2fbd99a830ff84298c3de564&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;amount&quot;: 1,
            &quot;asset_name&quot;: &quot;Bitcoin&quot;,
            &quot;owner_address&quot;: &quot;TAEvaU3YDxjcQBHNv4PLAMJqvcJdSWdhiM&quot;,
            &quot;to_address&quot;: &quot;TCHLAjmhPBEpR1Qi9g9KAmBPiuMQkjNrRp&quot;
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.TransferAssetContract&quot;
        },
        &quot;type&quot;: &quot;TransferAssetContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;8b52&quot;,
    &quot;ref_block_hash&quot;: &quot;2dbca869a78e9721&quot;,
    &quot;expiration&quot;: 1535515809000,
    &quot;timestamp&quot;: 1535515750703
  },
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;TRX 전송 컨트랙트와 크게 다르지 않습니다.&lt;/span&gt;&lt;br /&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;to_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;에게&amp;nbsp;&lt;/span&gt;&lt;b&gt;amount&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만큼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;asset_name&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;을 전송했네요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;그런데 왜 TRC-10 의 asset_name 일까요?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;위&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;의 트랜잭션은 2018 년 9월의 트랜잭션입니다. 최근 트랜잭션에서는 id 값으로 조회됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741096805346&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...,
  &quot;txID&quot;: &quot;2860325a5c7a0a4402451cc026f9c6a87e0410febe860f1501efdbac06b2c4a1&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;amount&quot;: 11654260000,
            &quot;asset_name&quot;: &quot;1002000&quot;,
            &quot;owner_address&quot;: &quot;TXdaiUHATBx1y2z9PMihv8ssqNJarWxyrU&quot;,
            &quot;to_address&quot;: &quot;TYYWL9khqAfNC7goamRMF4guVahupoHSAy&quot;
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.TransferAssetContract&quot;
        },
        &quot;type&quot;: &quot;TransferAssetContract&quot;
      }
    ],
   ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이 트랜잭션은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;asset_name&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 id 값입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;즉, 과거에는 field 이름의 의미대로 name 이 나왔지만 현재는 id 로 결과가 나온다는 것입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;정확하게는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;TIP-10&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;전과 후로 나뉩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TIP-10 에 대해서 간단히 설명하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;name 중복이 불가능했다가 가능하도록 변경하자는 제안입니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또한 이때부터 id 가 추가되었고 ERC-20 의 decimal 처럼 precision 도 추가되어 소수점 표기를 가능하게 했습니다.&lt;br /&gt;TransactionAssetContact 도 이 제안에 영향을 받습니다. asset_name 이름은 과거부터 있었기 때문에 호환성을 위해 field 이름과 값 타입을 변경하지 않은 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 첫 번째 예시(&quot;asset_name&quot;: &quot;Bitcoin&quot;)는 어떤 TRC-10 인지 분석해 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k765c/btsMCaHLYrn/LUkZsSLKMwnHk1qOy2BFI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k765c/btsMCaHLYrn/LUkZsSLKMwnHk1qOy2BFI0/img.png&quot; data-alt=&quot;tronscan Transaction&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k765c/btsMCaHLYrn/LUkZsSLKMwnHk1qOy2BFI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk765c%2FbtsMCaHLYrn%2FLUkZsSLKMwnHk1qOy2BFI0%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;745&quot; height=&quot;389&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan Transaction&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;스캔은 TRC-10 을 잘 알고 있습니다. Amount 의 BTC 를 클릭하면 해당 TRC-10 토큰 정보 페이지로 넘어갑니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스캔도 알고 있으니 우리도 직접 분석해 낼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;찾는 방법은 매우 간단합니다. &lt;b&gt;wallet/getassetissuelistbyname&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에서 가장 낮은 id 찾는 것입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이젠 asset_name 은 중복이 가능해서 여러 개의 토큰이 조회될 수 있는데 이때 가장 낮은 id 를 찾게 되면 바로 찾아낼 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;즉, &lt;/span&gt;asset_name 이 id 가 아닌 트랜잭션은 asset_name 그 자체 토큰이 TIP-10 이전에는 단 하나였다는 겁니다. 중복이 있다면 그 TRC-10 토큰은 TIP-10 이후로 생겼을 겁니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&quot;Bitcoin&quot; 이름을 갖는 TRC-10 토큰 리스트를 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741097158605&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;assetIssue&quot;: [
    {
      &quot;owner_address&quot;: &quot;TV7Tkx7XiFVYUCy8q7dkn3iQsBNuuGvoCK&quot;,
      &quot;name&quot;: &quot;Bitcoin&quot;,
      &quot;abbr&quot;: &quot;BTC&quot;,
      &quot;total_supply&quot;: 21000000,
      &quot;trx_num&quot;: 1000000,
      &quot;num&quot;: 1,
      &quot;start_time&quot;: 1529991531000,
      &quot;end_time&quot;: 1561441020000,
      &quot;description&quot;: &quot;Bitcoin is an innovative payment network and a new kind of money.&quot;,
      &quot;url&quot;: &quot;http://bitcoin&quot;,
      &quot;id&quot;: &quot;1000004&quot;
    },
    {
      &quot;owner_address&quot;: &quot;TBNmtwdvevnd6uHUimUFLDD7odEzEFJegD&quot;,
      &quot;name&quot;: &quot;Bitcoin&quot;,
      &quot;abbr&quot;: &quot;BTC&quot;,
      &quot;total_supply&quot;: 21000000000000,
      &quot;frozen_supply&quot;: [
        {
          &quot;frozen_amount&quot;: 20000000000000,
          &quot;frozen_days&quot;: 30
        }
      ],
      &quot;trx_num&quot;: 1000000,
      &quot;precision&quot;: 6,
      &quot;num&quot;: 1000000,
      &quot;start_time&quot;: 1553184000000,
      &quot;end_time&quot;: 1561132800000,
      &quot;description&quot;: &quot;True Bitcoin&quot;,
      &quot;url&quot;: &quot;https://bitcoin.org&quot;,
      &quot;id&quot;: &quot;1002211&quot;
    },
    {
      &quot;owner_address&quot;: &quot;THBFadZJEk8Wg8dVuYRJUhbJky1HLtfn7y&quot;,
      &quot;name&quot;: &quot;Bitcoin&quot;,
      &quot;abbr&quot;: &quot;BTW&quot;,
      &quot;total_supply&quot;: 2.1e18,
      &quot;trx_num&quot;: 888000000,
      &quot;precision&quot;: 4,
      &quot;num&quot;: 247980000,
      &quot;start_time&quot;: 1609023600000,
      &quot;end_time&quot;: 1609110000000,
      &quot;description&quot;: &quot;Bitcoin Token ses peer-to-peer technology to operate with no central authority or banks; managing transactions and the issuing of bitcoins is carried out collectively by the network.&quot;,
      &quot;url&quot;: &quot;https://bitcoin.org/&quot;,
      &quot;id&quot;: &quot;1003591&quot;
    },
    {
      &quot;owner_address&quot;: &quot;TH8kLNBptvnV4w5LbokEeApc9mhqwt2YLH&quot;,
      &quot;name&quot;: &quot;Bitcoin&quot;,
      &quot;abbr&quot;: &quot;BITCOIN&quot;,
      &quot;total_supply&quot;: 25000000,
      &quot;trx_num&quot;: 1000000,
      &quot;precision&quot;: 1,
      &quot;num&quot;: 10,
      &quot;start_time&quot;: 1640732400000,
      &quot;end_time&quot;: 1640818800000,
      &quot;description&quot;: &quot;Bitcoin uses peer-to-peer technology to operate with no central authority or banks; managing transactions and the issuing of bitcoins is carried out collectively by the network. Bitcoin is open-source&quot;,
      &quot;url&quot;: &quot;https://bitcoin.org/&quot;,
      &quot;id&quot;: &quot;1004673&quot;
    }
  ]
}&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;id가 &lt;b&gt;1000004&lt;/b&gt; 인 TRC-10 토큰이 첫 예제의 Bitcoin TransactionAssetContract 트랜잭션이었네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TIP-10 에 대해서 자세한 내용은&lt;a href=&quot;https://github.com/tronprotocol/tips/blob/master/tip-10.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; tip-10.md &lt;/a&gt;를 참고해 주세요.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;ParticipateAssetIssueContract&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ParticipateAssetIssueContract&lt;/b&gt;&amp;nbsp;는 ICO 기간 동안 TRX 로 TRC-10 을 구매하는 컨트랙트입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741097302913&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...,
  &quot;txID&quot;: &quot;bcae3954e4157cb904c67b6a3de9aea7224f6a4713fd805176cb9aef15f81de9&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;amount&quot;: 25000000,
            &quot;asset_name&quot;: &quot;$EED&quot;,
            &quot;owner_address&quot;: &quot;TXw7nYSoxrzUHtXqC61AWqtVdyr75byoLa&quot;,
            &quot;to_address&quot;: &quot;TVWbwZ13tPjRUgXn1rKHmR1Nf6szgEftB8&quot;
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.ParticipateAssetIssueContract&quot;
        },
        &quot;type&quot;: &quot;ParticipateAssetIssueContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;3464&quot;,
    &quot;ref_block_hash&quot;: &quot;c45fbc3133f9bcf2&quot;,
    &quot;expiration&quot;: 1533081996000,
    &quot;timestamp&quot;: 1533081945183
  },
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;amount&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를 주고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;to_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;에서&amp;nbsp;&lt;/span&gt;&lt;b&gt;asset_name&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&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: #333333; text-align: start;&quot;&gt;그런데 몇 개의 TRC-10 토큰을 구매한 지는 알 수 없습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;먼저 알아야 할 사항은 TRC-10 의&amp;nbsp;&lt;/span&gt;&lt;b&gt;trx_num&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;num&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;데이터들 입니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;wallet/getasset...&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;API 들을 통해 확인할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;$EED 토큰으로 분석해 보겠습니다. 순서는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;토큰 정보 찾기&lt;/li&gt;
&lt;li&gt;trx_num&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;num 으로 계산하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;우리가 아는 사실은 asset_name($EED) 뿐이니&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;wallet/getassetissuelistbyname&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;&amp;nbsp;으로 토큰 정보를 조회해 보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741097432949&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;assetIssue&quot;: [
    {
      &quot;owner_address&quot;: &quot;TVWbwZ13tPjRUgXn1rKHmR1Nf6szgEftB8&quot;,
      &quot;name&quot;: &quot;$EED&quot;,
      &quot;abbr&quot;: &quot;$EED&quot;,
      &quot;total_supply&quot;: 1000000000000,
      &quot;trx_num&quot;: 1000000,
      &quot;num&quot;: 2,
      &quot;start_time&quot;: 1532814900950,
      &quot;end_time&quot;: 1564338660950,
      &quot;description&quot;: &quot;SEED&quot;,
      &quot;url&quot;: &quot;sesameseed.org&quot;,
      &quot;id&quot;: &quot;1000301&quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;trx_num, num field 를 찾았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 계산식을 통해 몇 개의 토큰을 구매했는지 확인해 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741097503232&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// java-tron 코드 일부
 long exchangeAmount = Math.multiplyExact(cost, assetIssueCapsule.getNum());
 exchangeAmount = Math.floorDiv(exchangeAmount, assetIssueCapsule.getTrxNum());
 ownerAccount.addAssetAmountV2(key, exchangeAmount, dynamicStore, assetIssueStore);
 ...
 AccountCapsule toAccount = accountStore.get(toAddress);
 toAccount.setBalance(Math.addExact(toAccount.getBalance(), cost));
 if (!toAccount.reduceAssetAmountV2(key, exchangeAmount, dynamicStore, assetIssueStore)) {
     throw new ContractExeException(&quot;reduceAssetAmount failed !&quot;);
 }`&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 통해 얻은 계산 식은 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741097589264&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TRC-10 수량 = amount(cost) x num / trx_num&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가&lt;span&gt;&amp;nbsp;&lt;b&gt;to_address&lt;/b&gt;&amp;nbsp;에게&amp;nbsp;&lt;/span&gt;&lt;b&gt;amount(25000000 SUN)&lt;/b&gt;&amp;nbsp;를 주고,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;to_address&lt;/span&gt;&lt;/b&gt;&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;가&lt;/span&gt;&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;asset_name&lt;/span&gt;&lt;/b&gt;&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;토큰을&lt;/span&gt;&amp;nbsp;&lt;b&gt;25000000 x 2 / 1000000 =&lt;span&gt;&amp;nbsp;&lt;/span&gt;50&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt; 전달&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;&lt;/span&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;640&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sewtp/btsMB3PqEDX/62Nd42l9FpBGhQN9WvkfHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sewtp/btsMB3PqEDX/62Nd42l9FpBGhQN9WvkfHK/img.png&quot; data-alt=&quot;tronscan Transaction&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sewtp/btsMB3PqEDX/62Nd42l9FpBGhQN9WvkfHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsewtp%2FbtsMB3PqEDX%2F62Nd42l9FpBGhQN9WvkfHK%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;640&quot; height=&quot;312&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan Transaction&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스캔과 일치하는 모습입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TriggerSmartContract&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TriggerSmartContract&lt;/b&gt;&amp;nbsp;는 스마트 컨트랙트를 호출할 때 사용됩니다.&lt;br /&gt;TRC-20 을 전송하는 트랜잭션이라면 TriggerSmartContract 타입의 트랜잭션입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741097985331&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...,
  &quot;txID&quot;: &quot;e81456cb9523cf6abf36347d714f2c9a04504d1a47639b4b553a2d195da9937b&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;data&quot;: &quot;a3082be900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000001&quot;,
            &quot;owner_address&quot;: &quot;TB4XxoJQ2vyyYffNJgi3ge81QfbiuGzHdm&quot;,
            &quot;contract_address&quot;: &quot;TEEXEWrkMFKapSMJ6mErg39ELFKDqEs6w3&quot;,
            &quot;call_value&quot;: 250000000
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.TriggerSmartContract&quot;
        },
        &quot;type&quot;: &quot;TriggerSmartContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;4ba2&quot;,
    &quot;ref_block_hash&quot;: &quot;be22ab2ceb21bd29&quot;,
    &quot;expiration&quot;: 1545002346000,
    &quot;fee_limit&quot;: 6000000,
    &quot;timestamp&quot;: 1545002289587
  },
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이 트랜잭션은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;contract_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;스마트 컨트랙트에게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;data&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를 실행하면서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;call_value&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만큼 TRX 를 전송하고 있네요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;TRX 뿐만 아니라 TRC-10 도 전송할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741098011444&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;message TriggerSmartContract {
  bytes owner_address = 1;
  bytes contract_address = 2;
  int64 call_value = 3;
  bytes data = 4;
  int64 call_token_value = 5;
  int64 token_id = 6;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;call_token_value&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;token_id&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를 볼 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이 값들이 존재한다면 TRC-10 도 컨트랙트로 전송한 것으로 분석하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;보통&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;_value&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를 전송하게 된다면 Internal Transaction 도 있을 수 있습니다. 가능하면 정확한 자산 이동을 분석하기 위해 Internal Transaction 까지 분석하는 게 좋겠네요.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;ExchangeCreateContract&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Tron 은 TRX &amp;lt;-&amp;gt; TRC-10, TRC-10 &amp;lt;-&amp;gt; TRC-10 DEX 가 코어에 존재합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;Exchange&lt;/b&gt; 라고 불리고 API 로 쉽게 사용할 수 있습니다. 현재는 잘 사용되지는 않지만 과거에 사용되었던 자산 이동은 이미 벌어진 일이니 데이터를 읽을 수는 있어야 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;또한 개선되거나 새로운 기능이 추가될 수도 있죠. 아무튼 그중에 첫 번째로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;ExchangeCreateContract&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를 분석해 보겠습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이 컨트랙트는 이름 그대로 Exchange를 새롭게 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741098267693&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; ...,
  &quot;txID&quot;: &quot;11115b0e5bc868b94cc26b4d4c273f0aa641f6f202f2ac9aa2e674f8f1beeccc&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;first_token_balance&quot;: 1875000,
            &quot;first_token_id&quot;: &quot;LoveHearts&quot;,
            &quot;owner_address&quot;: &quot;TJPTRXBBYdqWHRb4gZ3uG2KARrmetoveoL&quot;,
            &quot;second_token_id&quot;: &quot;TRONGOLD&quot;,
            &quot;second_token_balance&quot;: 1500000
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.ExchangeCreateContract&quot;
        },
        &quot;type&quot;: &quot;ExchangeCreateContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;45a7&quot;,
    &quot;ref_block_hash&quot;: &quot;f80086d084652c6d&quot;,
    &quot;expiration&quot;: 1542991974000
  },
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;는 새로운 &lt;b&gt;Pair(Exchange)&lt;/b&gt;를 생성합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;first&lt;/b&gt;, &lt;b&gt;second&lt;/b&gt; 모두 &lt;b&gt;token_id&lt;/b&gt; 에 이름이 있는 것을 보니 &lt;b&gt;TRC-10 &amp;lt;-&amp;gt; TRC-10&lt;/b&gt; 쌍을 생성하고 있네요.&lt;/span&gt;&lt;br /&gt;&lt;b&gt;first_token_id&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;의&amp;nbsp;&lt;/span&gt;&lt;b&gt;first_token_balance&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;와&amp;nbsp;&lt;/span&gt;&lt;b&gt;second_token_id&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;의&amp;nbsp;&lt;/span&gt;&lt;b&gt;second_token_balance&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만큼 쌍을 생성하고 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;모두&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;owner_address&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;에서 자산이 이동되었습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;추가로 Transaction Info를 보면,&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741098308107&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;11115b0e5bc868b94cc26b4d4c273f0aa641f6f202f2ac9aa2e674f8f1beeccc&quot;,
  &quot;fee&quot;: 1024000000,
  &quot;blockNumber&quot;: 4343209,
  &quot;blockTimeStamp&quot;: 1542991680000,
  &quot;contractResult&quot;: [
    &quot;&quot;
  ],
  &quot;receipt&quot;: {
    &quot;net_usage&quot;: 269
  },
  &quot;exchange_id&quot;: 91
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;exchange_id(91)&lt;/b&gt;&amp;nbsp;가 생성된 것을 볼 수 있습니다.&lt;br /&gt;앞으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;wallet/getexchangebyid&lt;/b&gt;&lt;span&gt;&amp;nbsp;API 를&lt;/span&gt; 통해서 pair 정보를 확인할 수 있게 됩니다.&lt;/p&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: #333333; text-align: start;&quot;&gt;그리고 TRX &amp;lt;-&amp;gt; TRC-10 도 가능하다고 했는데요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;TRX 는 token_id 가 없어서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;_&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;으로 표시됩니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;아래 예제를 보시면,&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741098340868&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...,
  &quot;txID&quot;: &quot;b4735fdbf6ae79bc57f20f303c2fc2df7a858365bd09532262d4f410c9294fa1&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;first_token_balance&quot;: 10000,
            &quot;first_token_id&quot;: &quot;TRONATLAS&quot;,
            &quot;owner_address&quot;: &quot;TNfR9coWH76XNFwhJd4XS5ysFcYW3JfxD8&quot;,
            &quot;second_token_id&quot;: &quot;_&quot;,
            &quot;second_token_balance&quot;: 1000000
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.ExchangeCreateContract&quot;
        },
        &quot;type&quot;: &quot;ExchangeCreateContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;7a92&quot;,
    &quot;ref_block_hash&quot;: &quot;c367c7f169708863&quot;,
    &quot;expiration&quot;: 1544221803000
  },
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;second_token_id&lt;/b&gt; 이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;_&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;으로 되어있으니&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;TRX 를 1000000 SUN 만큼&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;쌍으로 만들었다고 보시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;ExchangeInjectContract&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ExchangeInjectContract&lt;/b&gt;&amp;nbsp;는 Exchange 에 Pair에서 한쪽 토큰을 추가로 주입하는 컨트랙트입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741098402800&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...,
  &quot;txID&quot;: &quot;3d11aa9806b6eb45f4c26fbdaccfe915e83c2e027402c4bb565aace9597353fd&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;exchange_id&quot;: 134,
            &quot;token_id&quot;: &quot;_&quot;,
            &quot;owner_address&quot;: &quot;TYftfpnCitoxju9rMYx6XNegUHmFoPMary&quot;,
            &quot;quant&quot;: 100000000000
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.ExchangeInjectContract&quot;
        },
        &quot;type&quot;: &quot;ExchangeInjectContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;e906&quot;,
    &quot;ref_block_hash&quot;: &quot;12026db87bb2c539&quot;,
    &quot;expiration&quot;: 1545132804000
  },
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;quant&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만큼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;token_id&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;exchange_id&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;에 주입시켰네요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;token_id 가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;_&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이니까 owner_address 는 &lt;b&gt;100,000,000,000 SUN (100,000 TRX)&lt;/b&gt; 만큼 사용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;ExchangeWithdrawContract&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ExchangeWithdrawContract&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;은 Exchange 에서 자신이 Pair 에 주입했던 토큰을 출금하는 컨트랙트입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741098463158&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...,
  &quot;txID&quot;: &quot;9a2e31c618b7ff066637966b0e1f26d28e28593919842698325c1fd09ae79229&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;exchange_id&quot;: 46,
            &quot;token_id&quot;: &quot;_&quot;,
            &quot;owner_address&quot;: &quot;TJhZzCtF5macLpJuEYFRSMgcvi7g2e3tKo&quot;,
            &quot;quant&quot;: 159059000000
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.ExchangeWithdrawContract&quot;
        },
        &quot;type&quot;: &quot;ExchangeWithdrawContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;cef0&quot;,
    &quot;ref_block_hash&quot;: &quot;31e87e7d8963bfe3&quot;,
    &quot;expiration&quot;: 1542500319000
  },
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;quant&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만큼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;token_id&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를&amp;nbsp;&lt;/span&gt;&lt;b&gt;exchange_id&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;에서 출금했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;token_id 가 _ 인걸 보니 TRX 를 출금했습니다. 이 트랜잭션을 통해 &lt;b&gt;159,059,000,000 SUN (159,059 TRX)&lt;/b&gt; 를 챙겨갔네요.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;ExchangeTransactionContract&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ExchangeTransactionContract&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;는 Pair 에서 쌍이 되는 토큰을 교환하는 컨트랙트입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Swap 이라고도 부릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741098588070&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...,
  &quot;txID&quot;: &quot;f5bc35c6ab42bcc4aaa537e02910d072830a99a9c741b3e7236518a876d72db5&quot;,
  &quot;raw_data&quot;: {
    &quot;contract&quot;: [
      {
        &quot;parameter&quot;: {
          &quot;value&quot;: {
            &quot;exchange_id&quot;: 69,
            &quot;token_id&quot;: &quot;ReynaToken&quot;,
            &quot;expected&quot;: 1978730300,
            &quot;owner_address&quot;: &quot;TBAL1iNxXo63922jgnBCphktwv3CPmfcQa&quot;,
            &quot;quant&quot;: 1000000
          },
          &quot;type_url&quot;: &quot;type.googleapis.com/protocol.ExchangeTransactionContract&quot;
        },
        &quot;type&quot;: &quot;ExchangeTransactionContract&quot;
      }
    ],
    &quot;ref_block_bytes&quot;: &quot;a8dd&quot;,
    &quot;ref_block_hash&quot;: &quot;54e7ff8921310e6b&quot;,
    &quot;expiration&quot;: 1543659708000
  },
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;owner_address&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;quant&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만큼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;token_id&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를&amp;nbsp;&lt;/span&gt;&lt;b&gt;exchange_id&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;에서 상대 토큰과 교환합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;트랜잭션에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;expected&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;를 통해 기대 값을 확인할 수 있지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;실제로 상대 토큰의 교환 수량은 다릅니다&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;실제 수량은 Transaction Info 에서 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741098619829&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;f5bc35c6ab42bcc4aaa537e02910d072830a99a9c741b3e7236518a876d72db5&quot;,
  &quot;fee&quot;: 2680,
  &quot;blockNumber&quot;: 4565215,
  &quot;blockTimeStamp&quot;: 1543659414000,
  &quot;contractResult&quot;: [
    &quot;&quot;
  ],
  &quot;receipt&quot;: {
    &quot;net_fee&quot;: 2680
  },
  &quot;exchange_received_amount&quot;: 2055561166
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;exchange_received_amount&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 실제 수량입니다.&lt;br /&gt;상대 토큰이 무엇인지 확인하려면 트랜잭션에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;exchange_id&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 있으니&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;wallet/getexchangebyid&lt;/b&gt;&lt;span&gt;&amp;nbsp;API 를&lt;/span&gt; 통해 확인 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회해 보면,&lt;/p&gt;
&lt;pre id=&quot;code_1741098664176&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;first_token_id&quot;: &quot;31303030383933&quot;
...
&quot;second_token_id&quot;: &quot;5f&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 응답이 오는데 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;해당 값은 byte string 으로서 변환시키면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;first_token_id는 1000893 이고 second_token_id는 _ 으로 TRX 입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 owner_address 는 1000000 ReynaToken 으로&amp;nbsp;2,055,561,166 SUN (2,055.561166 TRX) 를 얻으셨네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;여기까지 대표적인 자산 이동과 관련된 온체인 데이터를 분석해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에는 포함되지 않았지만 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;자산을 동결 및 스테이킹하여 네트워크의 Resource 를 얻을 수 있는 System Contract 들도 존재합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;현재 Tron 에서는 TRC-20 등의 스마트 컨트랙트 자산들이 매우 활성화되어 있을 겁니다. 이에 따라 Account 도 늘어나고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwrqsX/btsMzQj4R33/MltVq9fm5LWk779L37zjmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwrqsX/btsMzQj4R33/MltVq9fm5LWk779L37zjmk/img.png&quot; data-alt=&quot;tronscan Data Charts&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwrqsX/btsMzQj4R33/MltVq9fm5LWk779L37zjmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwrqsX%2FbtsMzQj4R33%2FMltVq9fm5LWk779L37zjmk%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;758&quot; height=&quot;436&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tronscan Data Charts&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;스캔을 보면 신규 Account 가 늘어나고 있는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;따라서 Tron 블록체인의 트랜잭션이 증가할 것이며 이에 따라 자산 관련 트랜잭션은 많아질 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;앞으로 Tron 은 TIP 를 통해 계속 발전할 것입니다. 이 또한 온체인 상에서 제안에 대한 투표를 진행하죠.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;계속해서 새로운 데이터들도 생기는 것이니 온체인 데이터에 대한 안목을 넓히는 게 중요합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Tron 온체인 데이터에 가까워졌을 거라 생각하며 이만 마치겠습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;긴 글 읽어주셔서 감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;References&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.tron.network/docs/getting-start&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Tron Docs&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.tron.network/reference/background&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Tron API&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tronprotocol/java-tron&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;java-tron&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://trondao.medium.com/tron-developer-guide-bandwidth-points-143ecf4d4d99&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Tron Blog&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발 (Develop)/블록체인 (Blockchain)</category>
      <category>blockchain</category>
      <category>EVM</category>
      <category>on-chain</category>
      <category>onchain</category>
      <category>tron</category>
      <category>TRX</category>
      <category>tvm</category>
      <category>USDT</category>
      <author>Bbaktaeho</author>
      <guid isPermaLink="true">https://bbaktaeho-95.tistory.com/111</guid>
      <comments>https://bbaktaeho-95.tistory.com/111#entry111comment</comments>
      <pubDate>Tue, 4 Mar 2025 23:53:55 +0900</pubDate>
    </item>
    <item>
      <title>[DB] PostgreSQL Large Table 최적화하기</title>
      <link>https://bbaktaeho-95.tistory.com/110</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 블록체인 데이터를 인덱싱하기 위해 재단에서 제공하는 오픈소스 인덱서를 사용 중이었다. 인덱서는 Rust로 구현되어 있으며 PostgreSQL에 데이터를 인덱싱한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱서는 프로세싱 기준마다 서로 다른 프로세서로 분리되며 서로 의존 없이 배포가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 프로세서를 구동했을 때 약 70개의 테이블에 데이터를 적재하고 있었고 테이블은 모두 단일 테이블이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점점 증가하는 블록체인의 데이터 때문에 테이블은 매우 무거워졌으며 row 개수도 100억 개가 넘는 테이블도 존재했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL의 특징 중 &lt;b&gt;VACUUM&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;하지만 테이블이 커지면서 VACUUM 자체의 성능도 나빠지기 시작했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721826627838&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;+----+----------------------------------------------------+--------+------+---------------------------------------------------------+
|pid |runtime                                             |usename |state |query                                                    |
+----+----------------------------------------------------+--------+--------------------+------+------------------------------------+
|6450|0 years 0 mons 0 days 1 hours 52 mins 4.721775 secs |null    |active|autovacuum: VACUUM public.xxx (to prevent wraparound)    |
|6457|0 years 0 mons 0 days 1 hours 51 mins 59.408857 secs|null    |active|autovacuum: VACUUM public.xxxxx (to prevent wraparound)  |
|8033|0 years 0 mons 0 days 1 hours 33 mins 43.169177 secs|null    |active|autovacuum: VACUUM public.xxxxxx (to prevent wraparound) |
+----+----------------------------------------------------+--------+--------------------+------+------------------------------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 커져가는 테이블로 인해 upsert 성능이 느려졌으며 튜닝하기 위한 index 생성도 어려워졌다. 하필 블록체인에서 여러 행사가 많아지면서 수많은 트랜잭션이 생겼다. (블록체인 트랜잭션 기준으로 약 5700 TPS 이상 지속적으로 발생)&lt;/p&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;특정 트랜잭션 기준으로 여러 테이블에 파생된 row의 개수를 확인해 보니 16 개 정도 생성됐다. 데이터베이스 기준으로 봤을 때 생성되는 rows의 개수는 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1초에 생성되는 rows 수 = 5700 x 16 = 91,200&lt;/blockquote&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;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;423&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2hzCO/btsIMzdPGUa/Dp6xWSkoJ2ReU79BcwkCU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2hzCO/btsIMzdPGUa/Dp6xWSkoJ2ReU79BcwkCU0/img.png&quot; data-alt=&quot;AWS EBS 볼륨 증가 지표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2hzCO/btsIMzdPGUa/Dp6xWSkoJ2ReU79BcwkCU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2hzCO%2FbtsIMzdPGUa%2FDp6xWSkoJ2ReU79BcwkCU0%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;700&quot; height=&quot;423&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;423&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS EBS 볼륨 증가 지표&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해서 테이블 파티셔닝과 index 튜닝, cluster 등의 튜닝 작업을 진행해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 작업을 진행하면서 겪은 문제에 대해 삽질 과정을 공유하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Large Table Partitioning&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 서버의 사양에 따라 테이블이 크다는 기준은 모두 다를 것이다. 나 역시 기준을 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.anayrat.info/en/2021/09/01/partitioning-use-cases-with-postgresql/&quot;&gt;여기&lt;/a&gt;에서 얘기하는 Large도 답을 내릴 수 없다고 한다. workload, 하드웨어 등에 의존적이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 하나 확실한 건 단일 테이블에 증가하는 데이터를 받기엔 잠재적인 성능 저하를 일으킬 수 있다. upsert, select 모두 마찬가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 언제 파티셔닝 하면 좋을지 문서를 통해서 알아봤다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;These benefits will normally be worthwhile only when a table would otherwise be very large. The exact point at which a table will benefit from partitioning depends on the application, although a rule of thumb is that the size of the table should exceed the physical memory of the database server. &lt;br /&gt;&lt;br /&gt;참고: PostgreSQL 공식 문서&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;PostgreSQL은 서버 메모리보다 테이블이 클 경우 테이블 파티셔닝을 권장한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 데이터베이스 서버의 메모리 사양이 32 GB 일 때 32 GB 보다 큰 테이블은 파티셔닝을 하는 게 경험적으로 좋다고 말하고 있다.&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;PostgreSQL에서 DELETE는 용량 확보를 할 수 없으므로 VACUUM FULL 을 수동으로 실행하거나, DROP TABLE을 해야 하는데 이들은 테이블 lock 그 이상의 문제를 야기할 수 있어서 시도조차 할 수 없다.&lt;/p&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;보통 쿼리 성능을 위해 index를 생성하는데 index를 생성할 때 테이블이 한 개라면 write를 하지 못하므로 &lt;b&gt;CREATE INDEX CONCURRENTLY&lt;/b&gt;로 생성해야 하지만 많은 제약이 따르며 테이블이 크니 오래 걸린다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Table Size &amp;amp; Rows&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업이 필요한 테이블이 테이블 파티셔닝이 필요한지 확인이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 테이블의 크기와 row 개수를 확인해 봤다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827266767&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 테이블 크기
select
  table_name,
  pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) as total_size,
  pg_size_pretty(pg_relation_size(quote_ident(table_name))) as table_size,
  pg_total_relation_size(quote_ident(table_name))
from information_schema.tables
where table_schema = 'public' and is_insertable_into = 'YES' and table_type != 'VIEW'
order by 4 desc;

-- 결과
+-----------------------------------------------------+
|table_name                     |total_size|table_size|
+-----------------------------------------------------+
|.............................. |4785 GB   |3269 GB   |
|.............................. |3957 GB   |1349 GB   |
|.............................. |3182 GB   |1230 GB   |
|.............................. |2619 GB   |2176 GB   |
|.............................. |2529 GB   |2022 GB   |
|.............................. |2256 GB   |1330 GB   |
|.............................. |2096 GB   |1743 GB   |
|.............................. |1847 GB   |735 GB    |
|.............................. |1785 GB   |1100 GB   |
|.............................. |1553 GB   |1233 GB   |
|.............................. |1333 GB   |800 GB    |
|.............................. |1185 GB   |919 GB    |
|.............................. |660 GB    |392 GB    |
|.............................. |435 GB    |264 GB    |
|.............................. |162 GB    |117 GB    |
|.............................. |112 GB    |73 GB     |
|.............................. |101 GB    |83 GB     |
|.............................. |89 GB     |65 GB     |
|.............................. |68 GB     |26 GB     |
|.............................. |53 GB     |39 GB     |
|.............................. |49 GB     |32 GB     |
|.............................. |45 GB     |21 GB     |
|.............................. |43 GB     |20 GB     |
|.............................. |40 GB     |10 GB     |
|.............................. |28 GB     |21 GB     |
|.............................. |27 GB     |10 GB     |
|.............................. |26 GB     |13 GB     |
|.............................. |7401 MB   |4121 MB   |
|.............................. |6990 MB   |3786 MB   |
|.............................. |6036 MB   |3718 MB   |
...&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1721827276686&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 테이블 row 개수 추정치
DO $$
DECLARE
    rec TEXT;
    res bigint;
BEGIN
    FOR rec IN
        SELECT table_name
        FROM information_schema.tables where table_schema = 'public' and is_insertable_into = 'YES' and table_type != 'VIEW'
    LOOP
        EXECUTE format('SELECT reltuples::bigint AS estimate FROM pg_class WHERE relname = %L', rec) INTO res;
        IF res &amp;gt; 0 THEN
            RAISE NOTICE '% %', to_char(res, 'fm00000000000'), rec;
        END IF;
    END LOOP;
END $$;

-- 결과 (따로 정렬함)
table_name                  |estimate   |
+---------------------------+-----------+
|..................         |19911690240|
|..................         |11979251712|
|..................         |05961646080|
|..................         |05899252224|
|..................         |05769236992|
|..................         |05603352576|
|..................         |05187501056|
|..................         |05182718464|
|..................         |04616607232|
|..................         |04606320128|
|..................         |04498319872|
|..................         |04351470592|
|..................         |01397714432|
|..................         |00413145024|
|..................         |00300619616|&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100 GB가 넘는 테이블이 여럿 있었으며 몇십 GB 테이블들도 수두룩했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 추정치이긴 하지만 row 개수도 10억 개가 넘는 테이블 혹은 100억이 넘는 테이블도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블이 너무 커서 정확한 count를 확인하기는 어려웠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Table Partitioning&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 large 테이블에 대해서 파티셔닝을 진행해야 한다.&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;b&gt;public&lt;/b&gt; 스키마에 있으며 새로 생성한 스키마는 &lt;b&gt;temp&lt;/b&gt; 로 지정했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827325543&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create schema temp;&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;파티셔닝이 필요한 테이블은 public 스키마에 있으며 편의 상 테이블 이름은 &lt;b&gt;source&lt;/b&gt; 또는 &lt;b&gt;public.source&lt;/b&gt; 로 설명할 것이며 파티셔닝 된 테이블은 &lt;b&gt;dest&lt;/b&gt; 또는 &lt;b&gt;temp.dest&lt;/b&gt;로 표기하겠다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실시간 데이터 동기화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱서는 쉬지 않고 데이터를 만들고 데이터베이스에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 파티셔닝 작업을 하면서 최신 데이터에 대한 누락이 발생할 수도 있다. 따라서 실시간 데이터를 동기화하는 live 테이블도 생성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 테이블은 기존 테이블과 동일한 컬럼과 PK를 가진 테이블로 구성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827381566&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE IF NOT EXISTS temp.dest_live
(
    ...,
    CONSTRAINT ... primary key (...)
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 실제 인덱서가 바라보는 테이블에 &lt;b&gt;trigger&lt;/b&gt;를 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827402047&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE OR REPLACE FUNCTION public.&amp;lt;함수이름&amp;gt;()
RETURNS TRIGGER AS $$
BEGIN
		INSERT INTO temp.dest_live VALUES (NEW.*);
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER &amp;lt;트리거이름&amp;gt;
AFTER INSERT OR UPDATE ON public.source
FOR EACH ROW
EXECUTE FUNCTION public.&amp;lt;함수이름&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 dest_live 테이블에 실시간으로 데이터가 저장될 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Partition 테이블 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 테이블을 물리적으로 나눌 때 어떻게 분할할지 조건이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티셔닝을 진행하려고 했던 테이블들이 모두 &lt;span data-token-index=&quot;1&quot;&gt;historical data&lt;/span&gt;가 적재되는 테이블로서 시간 정보가 조건이 될 수 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827431881&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE IF NOT EXISTS temp.dest
(
    ...,
    transaction_timestamp  timestamp not null,
    ...,
    CONSTRAINT ... primary key (...)
) PARTITION BY RANGE (transaction_timestamp);&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;b&gt;PostgreSQL에서는 제약 조건에 해당하지 않는 column이 partition 조건이 될 수 없는 것이다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 에러 메세지의 내용이다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827492886&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[0A000] ERROR: unique constraint on partitioned table must include all partitioning columns
Detail: PRIMARY KEY constraint on table &quot;dest&quot; lacks column &quot;transaction_timestamp&quot; which is part of the partition key.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 에러는 PostgreSQL의 Partitioning limitations 중 하나임을 문서를 통해 확인할 수 있었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;To create a unique or primary key constraint on a partitioned table, the partition keys must not include any expressions or function calls and the constraint's columns must include all of the partition key columns. This limitation exists because the individual indexes making up the constraint can only directly enforce uniqueness within their own partitions; therefore, the partition structure itself must guarantee that there are not duplicates in different partitions.&lt;br /&gt;&lt;br /&gt;참고: PostgreSQL 공식 문서&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 방법이 없는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 테이블이 아닌 파티셔닝 된 자식 테이블에 대해서 PK를 생성하면 간단하게 해결된다.&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;a href=&quot;https://github.com/pgpartman/pg_partman&quot;&gt;pg_partman&lt;/a&gt; extension을 사용하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치하는 법은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827573602&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE SCHEMA partman;
CREATE EXTENSION pg_partman WITH SCHEMA partman;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;partman 스키마를 생성해서 설치하면 필요한 함수들이 partman 스키마 안에서 생성되어 스키마가 지저분해지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 partman은 dynamic partition 테이블을 손쉽게 생성하는 방법도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 통해서 버전도 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827608160&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM pg_available_extensions where name = 'pg_partman';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 내가 사용하는 버전은 &lt;b&gt;v4.7.3&lt;/b&gt; 이다. 버전마다 사용법 차이가 있으니 꼭 doc를 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면 &lt;b&gt;create_parent&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;size18&quot;&gt;1. 부모 테이블 생성&lt;/p&gt;
&lt;pre id=&quot;code_1721827676355&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- parent 테이블 생성
create table if not exists temp.dest
(
    ...
    transaction_timestamp timestamp not null,
		...
) PARTITION BY RANGE (transaction_timestamp);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티셔닝 조건 때문에 PK 생성은 하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. &lt;span data-token-index=&quot;0&quot;&gt;template 테이블 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pg_partman template 기능을 통해 자식 테이블의 제약 조건이나 index 등을 편리하게 생성도 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827847723&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- template 테이블 생성
create table temp.dest_template (LIKE temp.dest);
-- template 테이블에 제약 조건 추가
alter table temp.dest_template ADD PRIMARY KEY (...);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 테이블에 생성하지 못했던 제약 조건은 template 테이블에다가 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index도 생성할 수 있지만 마이그레이션 작업에서 성능 저하를 유발하므로 완료된 후에 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;unlogged&lt;/b&gt;를 세팅해 줄 수도 있는데 만약 replication이 구축되어 있다면 WAL를 통해서 데이터를 캡처할 수 없으니 주의해야 한다. (필자도 unlogged로 진행하다가 한번 더 작업하게 되었다. unlogged가 확실히 빠르긴 하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. 파티션 테이블 생성&lt;/p&gt;
&lt;pre id=&quot;code_1721827904191&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- partman을 통한 파티션 테이블 생성
select partman.create_parent(
            'temp.dest',
            'transaction_timestamp',
            'native',
						'1 month',
            p_start_partition := '2022-09-01',
            p_template_table := 'temp.dest_template',
            p_premake := 12
       );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v4.7.3&amp;nbsp;버전에서 create_parent 시그니처는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 참고 링크에서 확인 가능하다.&amp;nbsp; (참고: &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/pgpartman/pg_partman/blob/v4.7.3/doc/pg_partman.md&quot;&gt;https://github.com/pgpartman/pg_partman/blob/v4.7.3/doc/pg_partman.md&lt;/a&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1721827946003&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create_parent(
	p_parent_table text, 
	p_control text, 
	p_type text, 
	p_interval text, 
	p_constraint_cols text[] DEFAULT NULL, 
	p_premake int DEFAULT 4, 
	p_automatic_maintenance text DEFAULT 'on', 
	p_start_partition text DEFAULT NULL, 
	p_inherit_fk boolean DEFAULT true, 
	p_epoch text DEFAULT 'none', 
	p_upsert text DEFAULT '', 
	p_publications text[] DEFAULT NULL, 
	p_trigger_return_null boolean DEFAULT true, 
	p_template_table text DEFAULT NULL, 
	p_jobmon boolean DEFAULT true, 
	p_date_trunc_interval text DEFAULT NULL
) RETURNS boolean&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 생성한 parent 테이블을 토대로 설명하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;temp.dest&lt;/b&gt; 테이블이 부모 테이블이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;transaction_timestamp&lt;/b&gt; 컬럼이 분할 기준이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;native&lt;/b&gt; 타입으로 지정하여 PostgreSQL의 native partitioning 타입을 사용하게 되고 (range, list)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티셔닝은 &lt;b&gt;1 month&lt;/b&gt; 단위로 생성되며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티션 테이블의 시작은 &lt;b&gt;2022-09-01&lt;/b&gt; 부터 시작하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;temp.dest_template&lt;/b&gt; 테이블을 템플릿 삼아 테이블들이 생성될 것이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 날짜 기준으로 &lt;b&gt;12&lt;/b&gt; 개의 테이블을 미리 생성하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성이 완료되면 아래 쿼리로 파티션 테이블을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1721827986277&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT inhrelid::regclass AS child
FROM   pg_catalog.pg_inherits
WHERE  inhparent = 'temp.dest'::regclass;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략 도식화 하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q0NQD/btsILhd14Pt/zn1EwWt0iYrEmT29qHyz41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q0NQD/btsILhd14Pt/zn1EwWt0iYrEmT29qHyz41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q0NQD/btsILhd14Pt/zn1EwWt0iYrEmT29qHyz41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq0NQD%2FbtsILhd14Pt%2Fzn1EwWt0iYrEmT29qHyz41%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;536&quot; height=&quot;566&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Migration&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티션 테이블을 생성했으니 이제 source에서 데이터를 옮겨와야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 &lt;b&gt;insert into select&lt;/b&gt; 쿼리 한 번으로 마이그레이션 하는 방식이 많이 보여서 그대로 진행했었는데 끝날 기미가 안보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르게 마이그레이션 할 수 없을까 고민하다가 작업하려는 source 테이블에서 inserted_at 컬럼에 index가 생성되어 있어서 이를 활용하기로 했다. 해당 테이블에 수정될 일도 없었으니 최적의 조건으로 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식은 inserted_at index에 범위를 잡고 병렬 insert를 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈소스 인덱서를 구동한 시점부터 현재까지 시점을 보니 대략 3개월 정도였다. 그래서 inserted_at를 1일 단위로 쪼개어 병렬 insert 할 수 있도록 코드를 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어는 Go로 작성했으며 pgx 드라이버 라이브러리를 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828153318&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 생략...

func worker(_ int, sqlCh chan string, finishCh chan&amp;lt;- struct{}) {
	for sql := range sqlCh {
		process(sql)
	}
	finishCh &amp;lt;- struct{}{}
}

func process(sql string) {
	fmt.Printf(&quot;[process] start, %s\n&quot;, sql)
	start := time.Now()
	conn, err := pgDB.Conn()
	if err != nil {
		fmt.Printf(&quot;[process] %s, error=%s\n&quot;, sql, err.Error())
		panic(err)
	}
	defer conn.Release()

	ct, err := conn.Exec(context.Background(), sql)
	if err != nil {
		fmt.Printf(&quot;[process] %s, error=%s\n&quot;, sql, err.Error())
		panic(err)
	}

	fmt.Printf(&quot;[process] finish, %s, count: %d, elapsed: %s\n&quot;, sql, ct.RowsAffected(), time.Since(start))
}

func main() {
	sqlCh := make(chan string, workers)
	finishCh := make(chan struct{}, workers)

	go func() {
		for i := 0; i &amp;lt; workers; i++ {
			go worker(i, sqlCh, finishCh)
		}

		for d := start; d.Before(end); d = d.AddDate(0, 0, 1) {
			sql := fmt.Sprintf(
				&quot;insert into %s (select * from %s WHERE %s &amp;gt;= '%s' and %s &amp;lt; '%s')&quot;,
				destTable, sourceTable, timestampColumn, d.Format(&quot;2006-01-02&quot;), timestampColumn, d.AddDate(0, 0, 1).Format(&quot;2006-01-02&quot;),
			)
			sqlCh &amp;lt;- sql
		}
		close(sqlCh)
	}()

	finishCount := 0
	for {
		&amp;lt;-finishCh
		finishCount++
		if finishCount == workers {
			return
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main 함수에서 생성하는 SQL 예시를 보면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828173659&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;insert into dest (select * from source WHERE inserted_at &amp;gt;= '2023-01-01' and inserted_at &amp;lt; '2023-01-02')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 1일 단위의 SQL을 생성하여 worker 수에 맞게 postgres 세션이 활성화될 것이며 병렬로 처리되어 빠르게 마이그레이션 할 수 있었다. 또한 날짜별로 얼마나 많은 row가 생겨났는지 데이터 적재 추이를 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 많은 세션을 만들면 데이터베이스에 부하를 일으켜 문제가 발생할 수 있다. 데이터베이스 서버를 모니터링해 가며 적절한 worker 수를 지정하는 게 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 pg_partman 에서도 이와 비슷한 기능이 있다. &lt;b&gt;partition_data_*&lt;/b&gt; 기능을 사용하면 쉽게 마이그레이션 할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 기본 값으로 데이터를 지울 것이다. 버전마다 차이가 있는 것으로 확인했는데 정확한 사용법을 숙지할 필요가 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Index 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index 생성에는 write를 할 수 없도록 lock이 잡혀서 PostgreSQL은 &lt;b&gt;CREATE INDEX CONCURRENTLY&lt;/b&gt; 를 통해 lock 없이 생성할 수 있다. 대신 더 많으 리소스가 필요하므로 느리게 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 dest 테이블들에는 그럴 일이 없으므로 &lt;b&gt;CREATE INDEX&lt;/b&gt; 명령어로 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 index를 생성할 수도 있지만 index가 매번 변경되어 마이그레이션 성능 저하를 불러온다. 따라서 마이그레이션이 완료된 후에 index 생성을 하기로 했다. 이 역시 테이블별로 parallel하게 세션을 생성하여 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 진행 중에 아래와 같은 에러가 발생했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828328529&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;could not write to file &quot;파일이름&quot;: No space left on device&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index를 생성할 때 메모리가 부족할 경우 임시 파일(PostgreSQL Temporary File)을 생성하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 공간이 없어서 죽은 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL에서 임시 파일을 저장하는 공간은 &lt;b&gt;temp_tablespace&lt;/b&gt; 다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 temp space를 확인해 보니 internal storage를 붙인 장비가 아니라서 temp space가 비어있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828352204&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;show temp_tablespaces;

-- 결과
+----------------+
|temp_tablespaces|
+----------------+
|                |
+----------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아본 방법은 config를 튜닝하는 법, instance를 변경하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 instance를 변경하거나 &lt;b&gt;work_mem&lt;/b&gt; 값을 올려서 각 세션별 메모리 사용 한계를 늘리는 것은 비용적인 문제나 OOM의 위험을 높일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 work_mem은 세션별로 사용하는 메모리 공간을 늘리는 것으로 공용 메모리 공간으로 쓰이는 것이 아닌 세션 단위로 쓰이는 로컬 메모리의 값이다. PostgreSQL 커넥션 수가 많아지고 &lt;b&gt;shared_buffers&lt;/b&gt; 메모리 공간을 쓰지 않는 쿼리(sort, join 등)가 많아질 때 OOM을 발생시킬 수 있다.&lt;/p&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;원인은 테이블 크기다. 에러가 발생한 테이블의 크기를 확인해 보니 약 300 GB 정도였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL config를 튜닝보단 테이블을 파티셔닝 하는 게 더 좋은 판단이라고 생각해서 sub partitioning을 하기로 했다.&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;Sub Partitioning&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월별로 파티셔닝 되어 있는 특정 파티션 테이블에 대해서 추가적인 파티셔닝을 진행해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828438414&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;+-------------+
|dest_p2022_09|
|dest_p2022_10|
|dest_p2022_11|
|dest_p2022_12|
|dest_p2023_01|
|dest_p2023_02|
|dest_p2023_03|
|dest_p2023_04|
|dest_p2023_05|
|dest_p2023_06|
|dest_p2023_07|
|dest_p2023_08|
|dest_p2023_09|
|dest_p2023_10|
|dest_p2023_11|
|dest_p2023_12|
|dest_p2024_01|
|dest_p2024_02|
|dest_p2024_03|
|dest_p2024_04|
|dest_p2024_05|        +----------------+
|dest_p2024_06| -----&amp;gt; |child           |
|dest_p2024_07|        +----------------+        
|dest_p2024_08|        |dest_p2024_06_01|
|dest_p2024_09|        |dest_p2024_06_02|
|dest_p2024_10|        |dest_p2024_06_03|
|dest_p2024_11|        |dest_p2024_06_04|
|dest_p2024_12|        |dest_p2024_06_05|
|dest_p2025_01|        |dest_p2024_06_06|
|dest_p2025_02|        |dest_p2024_06_07|
|dest_p2025_03|        |dest_p2024_06_08|
|dest_p2025_04|        |      ...       |
|dest_p2025_05|
|dest_p2025_06|
|dest_p2025_07|
+-------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림처럼 2024년 6월의 1일 데이터는 dest_p2024_06_01 테이블에 존재하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티셔닝 된 테이블에 파티셔닝을 sub partitioning 이라고 한다. (subpartition도 찾아볼 수 있었다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pg_partman에도 sub partition 생성을 위한 기능이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;create_sub_parent()&lt;/b&gt; 함수를 통해 생성할 수 있으며 이 함수는 이미 존재하는 파티션 세트의 하위 파티션을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고: &lt;a href=&quot;https://github.com/pgpartman/pg_partman/blob/v4.7.3/doc/pg_partman.md&quot;&gt;pg_partman github&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 기능을 사용하면 모든 파티션 테이블에 대해 하위 파티션이 생성된다. 즉, 굳이 sub partition이 필요 없는 테이블들까지 생성된다는 것이다. 이러면 테이블이 너무 많아진다.&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;size18&quot;&gt;1. 1 day 기준으로 파티셔닝 할 parent 테이블 생성&lt;/p&gt;
&lt;pre id=&quot;code_1721828537785&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- parent 테이블 생성
create table if not exists temp.dest_p2024_06_day
(
    ...
    transaction_timestamp timestamp not null,
		...
) PARTITION BY RANGE (transaction_timestamp);&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;size18&quot;&gt;2. 파티션 테이블 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 pg_partman을 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;template 테이블에 index를 생성해두지 않았기 때문에 기존걸 이용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828581810&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- partman을 통한 파티션 테이블 생성
select partman.create_parent(
            'temp.dest_p2024_06_day',
            'transaction_timestamp',
            'native',
						'1 day',
            p_start_partition := '2024-06-01',
            p_template_table := 'temp.dest_template'
       );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 시점까지 파티션 테이블을 만들어내므로 2024년 6월이 아닌 테이블에 대해서는 수동 삭제했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. Migration&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시계열 기반의 index가 생성되지 않아서 &lt;span data-token-index=&quot;1&quot;&gt;insert into select 로&lt;/span&gt; 진행했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828611709&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO dest_p2024_06_day SELECT * FROM deset_p2024_06;&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;size18&quot;&gt;4. Index 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작업한 것과 같이 index를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;5. DETACH &lt;span data-token-index=&quot;0&quot;&gt;PARTITION&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이그레이션이 마무리되면 기존에 있던 2024년 6월 파티션 테이블은 DETACH 해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828656664&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; ALTER TABLE dest DETACH PARTITION deset_p2024_06;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업과 관련 없는 내용이지만 참고로 말씀드리면 파티션 테이블을 DROP 할 때 부모 테이블에 &lt;b&gt;ACCESS EXCLUSIVE LOCK&lt;/b&gt;이 걸린다. 이를 피하기 위해서 DETACH를 해야 한다. (DETACH CONCURRENTLY 도 있다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;6. ATTACH PARTITION&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 day 파티션 테이블을 기존 월별 테이블에 붙인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 ATTACH 할 때 우선적으로 해줄 작업이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL에서 ATTACH 작업을 진행할 때 암묵적으로 파티션 제약 조건을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CHECK 제약 조건이 없다면 ACCESS EXCLUSIVE LOCK 잡고서 모든 데이터를 풀스캔한다. SELECT까지 안되므로 스킵하기 위해 미리 만들어두는 것이 좋다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 CHECK 제약 조건 자체를 만드는 시간도 오래 걸리긴 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828760369&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE dest_p2024_06_day ADD CONSTRAINT d_p2024_06 CHECK ( transaction_timestamp &amp;gt;= '2024-06-01' AND transaction_timestamp &amp;lt; '2024-07-01')
elapsed: 2h54m58.523125083s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 테이블에서 걸린 시간은 약 3시간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 테이블에 미리 만들어두는 것도 하나의 방법일 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ATTACH 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828775905&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; alter table temp.dest attach partition temp.dest_p2024_06_day for values from ('2024-06-01 00:00:00') TO ('2024-07-01 00:00:00')
[2024-07-12 21:52:28] completed in 651 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 ATTACH는 곧바로 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ATTACH가 완료되었다면 CHECK 제약 조건을 제거한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;It is recommended to drop the now-redundant CHECK constraint after the&amp;nbsp;ATTACH PARTITION is complete. If the table being attached is itself a partitioned table, then each of its sub-partitions will be recursively locked and scanned until either a suitable CHECK&amp;nbsp;constraint is encountered or the leaf partitions are reached.&lt;br /&gt;&lt;br /&gt;참고: PostgreSQL 공식 문서&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서에서도 불필요한 CHECK가 sub partition에 도달할 때까지 재귀적으로 LOCK이 발생한다고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828890778&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE dest_p2024_06_day DROP CONSTRAINT d_p2024_06;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도식화해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1033&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDLIiK/btsIMhdv8fN/gXPV6qH6a82wOKBdAyTrj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDLIiK/btsIMhdv8fN/gXPV6qH6a82wOKBdAyTrj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDLIiK/btsIMhdv8fN/gXPV6qH6a82wOKBdAyTrj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDLIiK%2FbtsIMhdv8fN%2FgXPV6qH6a82wOKBdAyTrj0%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;748&quot; height=&quot;604&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1033&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;6월 테이블을 데이터를 1 day 파티션 테이블로 마이그레이션&lt;/li&gt;
&lt;li&gt;DETACH&lt;/li&gt;
&lt;li&gt;ATTACH&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 순서로 정리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로서 테이블이 하루 단위로 분할되어 가벼워졌고 index를 생성하는데 문제없었다.&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;Clustered Index&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL에는 clustered index와 non-clustered index 두 가지 타입의 인덱스가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 RDBMS 에서 clustered index를 PK로 자동 설정하지만 PostgreSQL 에서는 원하는 index를 clustered index로 만들어 물리적인 정렬 순서를 index 순서로 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 테이블에 랜덤 access가 자주 발생하는 경우에는 중요하지 않을 수도 있다. 내가 작업하던 테이블은 시간 순서로 access되는 검색이 많아서 유의미한 결과를 가져올 것 같아 적용하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 과거에 적재되었던 테이블의 row도 변경되지 않기 때문에 미리 작업해 두기 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-cluster.html&quot;&gt;여기에서&lt;/a&gt; 확인 가능하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cluster Index 적용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 관련해서 범위 검색이 많을 것으로 예상하여 &lt;b&gt;transaction_timestamp&lt;/b&gt; 컬럼으로 생성했던 index로 적용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721828998168&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CLUSTER dest USING dest_transaction_timestamp_idx;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Vacuuming&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;VACUUM ANALYSE&lt;/b&gt; 작업을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dead tuple 과 무관한 작업이었지만 VACUUM을 해야 하는 이유가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index scan과 관련 있는 &lt;b&gt;visibility map&lt;/b&gt; 이나 쿼리 계획에서 사용되는 &lt;b&gt;통계 정보&lt;/b&gt;를 업데이트하기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL 에서도 insert only 테이블의 autovacuum을 작동시키기 위해 &lt;b&gt;autovacuum_vacuum_insert_scale_factor&lt;/b&gt;, &lt;b&gt;autovacuum_vacuum_insert_threshold&lt;/b&gt; 등의 설정 값들이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 &lt;a href=&quot;https://www.crunchydata.com/blog/insert-only-tables-and-autovacuum-issues-prior-to-postgresql-13&quot;&gt;여기&lt;/a&gt;에서 확인할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VACUUM ANALYSE&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹여나 autovacuum으로 처리되지 않을 것을 대비하여 모든 테이블에 대해 VACUUM 을 수동 실행했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721829124979&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VACUUM ANALYSE dest;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 테이블 교체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로서 파티셔닝 작업은 마무리되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 작업만 마무리되면 실제 쿼리를 받는 테이블과 바꿔치기가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 live 테이블 마이그레이션 작업이다. 해당 테이블은 데이터 누락을 방지하기 위해 처음 생성했던 테이블이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터 누락 방지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;live 테이블에 있던 데이터를 모두 dest 테이블로 이동시키기 전에 source 테이블에서 dest 테이블로 데이터를 sync 할 수 있도록 트리거를 하나 더 생성해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 순서는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;source &amp;rarr; dest trigger 추가&lt;/li&gt;
&lt;li&gt;source &amp;rarr; deset_live trigger 제거&lt;/li&gt;
&lt;li&gt;dest_live &amp;rarr; dest 마이그레이션&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;1250&quot; data-origin-height=&quot;1332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0oR9H/btsIKJWm2XS/Trkgg0d2a7Z5nWjVm4KSMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0oR9H/btsIKJWm2XS/Trkgg0d2a7Z5nWjVm4KSMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0oR9H/btsIKJWm2XS/Trkgg0d2a7Z5nWjVm4KSMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0oR9H%2FbtsIKJWm2XS%2FTrkgg0d2a7Z5nWjVm4KSMk%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;680&quot; height=&quot;725&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;1332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 작업 모두 위에 설명되어 있으므로 생략하겠다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;테이블 교체&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 source 테이블의 모든 데이터와 실시간으로 적재되는 데이터까지 파티셔닝 된 dest 테이블에 안전하게 적재되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 작업으로 source 테이블과 dest를 교체해줘야 한다.&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;size18&quot;&gt;1. 인덱서 중단&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. Trigger 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. source 테이블의 schema 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시로 이동시킬 schema는 &lt;b&gt;wait_drop&lt;/b&gt;으로 지었다.&lt;/p&gt;
&lt;pre id=&quot;code_1721829485450&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE SCHEMA wait_drop;
ALTER TABLE public.source SET SCHEMA wait_drop;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. dest 테이블을 public schema로 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 이름이 동일하므로 그대로 이동시킨다.&lt;/p&gt;
&lt;pre id=&quot;code_1721829541681&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE temp.dest SET SCHEMA public;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 parent 테이블만 스키마 이동이 일어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당황하지 않아도 된다. 쿼리는 문제없이 partition 테이블로 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 다른 스키마여서 권한과 관련된 문제도 생각해 볼 수 있는데 parent 테이블이 존재하는 스키마에 권한만 부여되어 있다면 문제없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다른 스키마에 partition 테이블이 존재하는 것은 보기에 불편하며 partman에도 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;partman config&lt;/b&gt; 테이블의 &lt;b&gt;parent_table&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;size18&quot;&gt;5. source 테이블 제거 (optional)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정합성에 문제가 없다면 상황 봐서 테이블을 지우고 용량을 확보한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721829672192&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;drop schema wait_drop;&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;size18&quot;&gt;6. 인덱서 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 모습은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;713&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zHbpw/btsIMaFq1mi/FnLDixGOFbA94OIs9ez6K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zHbpw/btsIMaFq1mi/FnLDixGOFbA94OIs9ez6K0/img.png&quot; data-alt=&quot;wait_drop의 테이블들은 제거 대상이다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zHbpw/btsIMaFq1mi/FnLDixGOFbA94OIs9ez6K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzHbpw%2FbtsIMaFq1mi%2FFnLDixGOFbA94OIs9ez6K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;713&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;713&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;wait_drop의 테이블들은 제거 대상이다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;large table인 source를 dest 테이블로 대체한 모습이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실상 테이블 이름은 동일하고 편의상 표기만 다르게 했다는 점 참고 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글이 길어서 한번 더 정리하고자 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;요약&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 메모리보다 큰 테이블은 파티셔닝 권장&lt;/li&gt;
&lt;li&gt;파티셔닝이 필요하다면 pg_partman 사용&lt;/li&gt;
&lt;li&gt;replication이 존재한다면 set logged, 없다면 set unlogged&lt;/li&gt;
&lt;li&gt;index 생성은 마이그레이션 이후에 생성&lt;/li&gt;
&lt;li&gt;파티션 테이블이 아직 크다면 sub partitioning&lt;/li&gt;
&lt;li&gt;ATTACH 할 파티션 테이블이 있다면 CHECK 제약조건 추가&lt;/li&gt;
&lt;li&gt;random access가 적다면 CLUSTER INDEX 고려&lt;/li&gt;
&lt;li&gt;더 이상 변동되지 않는 파티션 테이블에 대해 VACUUM ANALYSE 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기대 효과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;블록체인 TPS가 높아져도 디비 스케일업이 필요 없을 수 있음&lt;/li&gt;
&lt;li&gt;VACUUM 퍼포먼스가 높아져서 데이터베이스 팽창을 줄일 수 있음&lt;/li&gt;
&lt;li&gt;INDEX 변경 및 생성에 유리하므로 변경되는 요구사항에 대응&lt;/li&gt;
&lt;li&gt;정책에 따라 데이터를 제거하고, 용량을 확보&lt;/li&gt;
&lt;li&gt;파티션 기준에 맞춰진 쿼리일 때 비용이 저렴&lt;/li&gt;
&lt;li&gt;파티션 테이블별로 통계 샘플을 가질 수 있어 계획의 대한 정확도 상승&lt;/li&gt;
&lt;li&gt;필요에 따라 특정 파티션 테이블의 tablespace를 빠른 SSD로 변경하거나 메모리에 캐싱&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/sql-cluster.html&quot;&gt;https://www.postgresql.org/docs/current/sql-cluster.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/routine-vacuuming.html&quot;&gt;https://www.postgresql.org/docs/current/routine-vacuuming.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/docs/16/ddl-partitioning.html#DDL-PARTITIONING&quot;&gt;https://www.postgresql.org/docs/16/ddl-partitioning.html#DDL-PARTITIONING&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.anayrat.info/en/2021/09/01/partitioning-use-cases-with-postgresql/&quot;&gt;https://blog.anayrat.info/en/2021/09/01/partitioning-use-cases-with-postgresql/&lt;/a&gt;&lt;/p&gt;</description>
      <category>컴퓨터 공학 (Computer Science)/데이터베이스 (Database)</category>
      <category>cluster</category>
      <category>large</category>
      <category>Partitioning</category>
      <category>postgres</category>
      <category>PostgreSQL</category>
      <category>subpartition</category>
      <category>table</category>
      <author>Bbaktaeho</author>
      <guid isPermaLink="true">https://bbaktaeho-95.tistory.com/110</guid>
      <comments>https://bbaktaeho-95.tistory.com/110#entry110comment</comments>
      <pubDate>Wed, 24 Jul 2024 23:06:00 +0900</pubDate>
    </item>
  </channel>
</rss>