<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>이것이 점프 투 공작소</title>
    <link>https://jaykos96.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 20 Jul 2026 03:35:11 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>겅겅겅</managingEditor>
    <image>
      <title>이것이 점프 투 공작소</title>
      <url>https://tistory1.daumcdn.net/tistory/5582314/attach/a15977572e6c48dda043fa2d80707fb0</url>
      <link>https://jaykos96.tistory.com</link>
    </image>
    <item>
      <title>피쳐 추출 시스템에 대해 알아보자</title>
      <link>https://jaykos96.tistory.com/109</link>
      <description>&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;피쳐란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'피쳐((feature))'는 단일 스팬에서 계산할 수 없는 하나 이상의 값을 계산하는 프로세스를 의미합니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 APM은 이를 시계열로 보거나 추세를 모니터링하거나 경보를 제공하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피쳐로 추출되어 보여지는 대표적인 데이터들은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추적의 총 대기 시간&lt;/li&gt;
&lt;li&gt;추적 시간 시간&lt;/li&gt;
&lt;li&gt;스팬 수&lt;/li&gt;
&lt;li&gt;네트워크 호출 횟수&lt;/li&gt;
&lt;li&gt;루트 서비스 및 해당 엔드포인트 이름&lt;/li&gt;
&lt;li&gt;클라이언트 타입&lt;/li&gt;
&lt;li&gt;대기 시간 분석: CDN, 백엔드, 네트워크, 스토리지 등&lt;/li&gt;
&lt;li&gt;다양한 메타데이터: 클라이언트 호출 발신 국가, 요청을 처리하는 데이터 센터&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;데이터 마이닝 파이프라인 구성 요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 데이터 마이닝 파이프라인 아키텍쳐와 실제 프레임워크를 사용한 예시 그림입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;agent와 추적 백엔드를 통해 들어온 데이터는 트리거에 의해 피쳐 추출기로 전달되고 집계 후 최종 storage에 도착합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QvpOI/btsQcnkGzzi/9EoHSTM4DPcoEXYFTjhBk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QvpOI/btsQcnkGzzi/9EoHSTM4DPcoEXYFTjhBk1/img.png&quot; data-origin-width=&quot;2838&quot; data-origin-height=&quot;1404&quot; data-is-animation=&quot;false&quot; width=&quot;668&quot; height=&quot;330&quot; style=&quot;width: 51.2309%; margin-right: 10px;&quot; data-widthpercent=&quot;51.83&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QvpOI/btsQcnkGzzi/9EoHSTM4DPcoEXYFTjhBk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQvpOI%2FbtsQcnkGzzi%2F9EoHSTM4DPcoEXYFTjhBk1%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;2838&quot; height=&quot;1404&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edPaJR/btsQclUKr6P/z2FfA7XJCBKiAE1xwYtkSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edPaJR/btsQclUKr6P/z2FfA7XJCBKiAE1xwYtkSk/img.png&quot; data-origin-width=&quot;2656&quot; data-origin-height=&quot;1414&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.6064%;&quot; data-widthpercent=&quot;48.17&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edPaJR/btsQclUKr6P/z2FfA7XJCBKiAE1xwYtkSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FedPaJR%2FbtsQclUKr6P%2Fz2FfA7XJCBKiAE1xwYtkSk%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;2656&quot; height=&quot;1414&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;추적 완료 트리거&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추적 완료 트리거는 Collector에 존재하며,&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;OTel에서는 주로 SpanProcessor((Batch))에서 OTLP Exporter로 전달되고 최종적으로 Collector로 전달하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 Span을 전달하는 과정일 뿐입니다.&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;하지만 피쳐추출에서는 span이 아닌 trace에 대한 정보가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Collector에서 trace가 종료되었는지 판단 후 파이프라인으로 보내야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Trace 완료를 판단하는 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;APM과 collector는 서로 다른 프로그램이기에 정확하게 trace의 종료 시점을 알 수 없기에 몇가지 기준으로 판단해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collector에서는 전달받은 span을 버퍼로 모아두고 몇가지 기준을 가지고 해당 span이 trace가 완료되었는지 판단합니다.&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;가장 간단한 방법은 첫 스팬을 수집 후 해당 span에 대한 trace가 끝날 때 까지 일정 시간동안 무작정 기다리는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 대기시간, 최대 대기시간, 강제 flush 이벤트와 같은 기능들을 사용합니다.&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;먼저 이 방법의 단점은 네트워크 측면에서 p99 보다 더 높은 대기 시간을 일상적으로 경험하는 사용자가 있을 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에 대해서 너무 짧은 타임 윈도우를 사용하게 되면 이런 요청에 대한 실제 추적이 끝나기 전 트리거에서 trace가 끝났다고 판단하게 됩니다.&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;하나는 부모 스팬에서 자식 스팬의 수를 collector에 같이 전달해주면 해당 값을 이용해 trace의 완료를 판단 할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나는 과거 데이터를 머신러닝하여 해당 span의 trace 종료 시점을 판단하는 방법입니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;피쳐 추출기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피쳐 추출기에서는 추적 결과를 수신하고, trace와 span에서 적절한 데이터를 추출 후 피처를 계산하는 비즈니스 로직이 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collector로는 RPS, 에러율, 지연 히스토그램, 서비스 맵과 같은 기본적인 분석을 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피쳐 추출기를 사용해서 원인 분석, 최적화, 병목/변화 감시, ML을 사용한 예측, 이상탐지와 같은 기능들을 알 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;집계 연산기 ((Agggregator))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출기에서 추출한 피쳐들을 비즈니스 로직에 맞게 집계하여 수치로 변환시켜줍니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Ad-hoc 분석 ((임시 분석))&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피쳐 추출을 위한 데이터 마이닝은 기본적으로 실시간 데이터들에 대한 값들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS의 athena 처럼 저장해둔 피쳐 데이터들을 기반으로 쿼리를 통해 원하는 가치를 탐색하는 분석 방법입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756524463448&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 지난 30일 /checkout p95 추세
SELECT window_start, p95
FROM endpoint_agg_1m
WHERE service='checkout' AND route='/orders/{id}' AND method='GET'
  AND window_start &amp;gt;= now() - INTERVAL 30 DAY
ORDER BY window_start;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>APM만들기</category>
      <category>추적 데이터 마이닝 파이프라인</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/109</guid>
      <comments>https://jaykos96.tistory.com/109#entry109comment</comments>
      <pubDate>Fri, 29 Aug 2025 00:25:54 +0900</pubDate>
    </item>
    <item>
      <title>피벗 추적에 대해 알아보자</title>
      <link>https://jaykos96.tistory.com/108</link>
      <description>&lt;h2 data-end=&quot;145&quot; data-start=&quot;126&quot; data-ke-size=&quot;size26&quot;&gt;피벗 추적이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 추적 시스템 내부의 계측 가능한 한 지점에서 측정값을 정의하고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 지점에서 지정한 그 값을 기반으로 선택,필터링,그룹화 하여 클라이언트에서 전체 시스템을 관측 할 수 있게 하는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 아래 쿼리는 HBase,맵리듀스,HDFS 클라이언트를 실행하는 다른 클라이언트의 요청을 처리하는 HDFS 데이터 노드에 적용되는 쿼리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 분산 시스템 내부에서 &quot;클라이언트가 얼마나 데이터를 읽었는지&quot;를 추적하기 위한 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754227003974&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM bytesRead IN DataNodeMetrics.incrBytesRead
JOIN client IN FIRST(ClientProtocols) ON client =&amp;gt; bytesRead
GROUP BY client.procName
SELECT cleint.procName, SUM(bytesRead.delta)&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataNodeMetrics.incrBytesRead 는 데이터 노드에서 바이트를 읽을 때 발생하는 추적 지점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 지점에서 수집된 이벤트를 bytesRead 로 alias 지정합니다.&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;ClientProtocols는 클라이언트에서 시스템에 요청을 보낼 때 발생하는 지점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;First 함수를 통해 ClientProtocols에 처음 도착한 이벤트를 client라는 alias로 가져오고, 그 요청이 bytesRead 이벤트를 유발했다고 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추적 시스템은 client.traceId == bytesRead.traceId를 기반으로 join합니다.&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;client안의 정보 안에 procName을 기준으로 데이터를 그룹화하고, 예를 들어 HDFS, Spark, Hive 등 프로세스 이름으로 그룹화 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 각 그룹별로 유발한 bytesRead.delta, 합계를 구합니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;126&quot; data-end=&quot;145&quot; data-ke-size=&quot;size26&quot;&gt;피벗 추적 흐름도&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2834&quot; data-origin-height=&quot;1390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8IHzE/btsPO87BbuY/GK0M4qTqttnFx3qRG75OO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8IHzE/btsPO87BbuY/GK0M4qTqttnFx3qRG75OO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8IHzE/btsPO87BbuY/GK0M4qTqttnFx3qRG75OO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8IHzE%2FbtsPO87BbuY%2FGK0M4qTqttnFx3qRG75OO1%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;2834&quot; height=&quot;1390&quot; data-origin-width=&quot;2834&quot; data-origin-height=&quot;1390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피벗 추적 흐름에서 주목할 만한 점은 컨텍스트 전파가 이루어 질 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 발생하는 추적 포인트에 피벗 추적에 필요한 데이터((traceId, procName 등))를 배기지에 저장해 전파합니다.&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-end=&quot;145&quot; data-start=&quot;126&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;APM의 피벗 추적의 구현에 대해서는 책에서도 많이 다루지 않고 논문을 확인하라고 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 실제 APM을 만들 때 피벗 추적에 대해 더 공부 후 추가 포스팅을 해야겠습니다.&lt;/p&gt;</description>
      <category>APM만들기</category>
      <category>apm</category>
      <category>피벗 추적</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/108</guid>
      <comments>https://jaykos96.tistory.com/108#entry108comment</comments>
      <pubDate>Sun, 3 Aug 2025 22:57:55 +0900</pubDate>
    </item>
    <item>
      <title>분산추적의 샘플링에 대해서 알아보자</title>
      <link>https://jaykos96.tistory.com/107</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;APM에서는 어플리케이션의 모든 요청을 다 추적하고 분석하지 않습니다.&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;요청을 100% 샘플링한다면 대퍼 추적기는 검색 작업에 대해 1.5%의 처리량과 16%의 시간지연 오버헤드가 발생한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 추적 시스템들은 추적의 특정 부분만을 캡쳐하기위해 샘플링을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 추적의 0.01%만 캠쳐하도록 샘플링하면 처리량 오버헤드는 0.06%, 응답시간은 0.20% 감소한다고 합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;헤드 기반((head-based)) 샘플링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;upfront sampling이라고도 하며, 들어오는 각각 추적들에 대해서 매번 샘플링 여부를 결정합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 추적은 인프라스트럭쳐, 웹서버를 지나 어플리케이션에 들어온 순간부터 시작됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;헤드 기반 샘플링은 추적 시스템이 특정 추적((Trace))의 모든 스팬을 캡쳐하거나 또는 아무것도 캡쳐하지 않기에 추적(Trace)에 대해 일관적이라는 특징이 있습니다.&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;어플리케이션에 진입하면 해당 추적이 샘플링 되어야 좋을지, 되지 않아야 좋을지 판단해야하는데, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;해당 추적에 대한 정보가 거의 없기에 판단이 어렵습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그렇지만 헤드기반 샘플링은 아직까지도 많은 추적 시스템에서 사용하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;헤드 기반의 반대로는 테일 기반 샘플링이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;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;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;이 샘플링 방식은 헤드 기반 샘플링을 이용한 추적 시스템에서 가장 많이 사용되는 샘플링 방식입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 클라우드 슬루스((Spring Cloud Sleuth))도 확률적 샘플러를 활용합니다.&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;예거((jaegor))는 요청 1000개당 1번의 추적이 만들어지도록 기본 샘플링 확률이 0.001로 설정되어 있습니다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;일정 시간동안 특정 서비스 엔드포인트 X에 대해 100개의 추적을 수집했다고 가정하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;샘플링 확률 p를 알면, 이 시간 동안 엔드포인트 X에 대한 총 호출 수를 100/p로 확률적 샘플리의 샘플링 데이터 양을 추정 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;속도 제한 샘플링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플링 방법 중 하나로 속도 제한기((rate limiter))를 사용하는 방법도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 레저부아 샘플링 알고리즘을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속도 제한기((rate limiter))는 초당 10개의 추적 또는 1분당 하나의 추적 등 지정된 시간 간격마다 고정된 수의 추적만 샘플링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속도 제한 샘플링은 불규칙한 트래픽 패턴을 보이는 MSA에 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레저부아 샘플링에 대해서는 다른 포스팅에서 다루었으니 혹시,, 궁금하신분은 아래 글에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jaykos96.tistory.com/104&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jaykos96.tistory.com/104&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1752848701506&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;실시간 대용량 데이터에서 사용되는 알고리즘(Reservoir Sampling, HLL, CMS,Bloom Filter)들에 대해 알아보&quot; data-og-description=&quot;스트리밍 시스템에서 모은 많은 데이터들은 시스템 안에서 효율적으로 분석되고 집계되어야합니다.아무래도 실시간이고 방대한 데이터를 대상으로 하는 만큼 '확률적인 알고리즘'이 많이 사용&quot; data-og-host=&quot;jaykos96.tistory.com&quot; data-og-source-url=&quot;https://jaykos96.tistory.com/104&quot; data-og-url=&quot;https://jaykos96.tistory.com/104&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Z2r7K/hyZjrdM49Y/ZapULyXIkuRC83WNWvnLO0/img.png?width=800&amp;amp;height=436&amp;amp;face=0_0_800_436,https://scrap.kakaocdn.net/dn/fJwcN/hyZndEUDLV/DMmCsPCnGZmw4gvbmks0LK/img.png?width=800&amp;amp;height=436&amp;amp;face=0_0_800_436,https://scrap.kakaocdn.net/dn/pAZFh/hyZjFQCjj2/lsuG7R8NMY1vlaLfgaXDyK/img.png?width=1760&amp;amp;height=1504&amp;amp;face=0_0_1760_1504&quot;&gt;&lt;a href=&quot;https://jaykos96.tistory.com/104&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jaykos96.tistory.com/104&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Z2r7K/hyZjrdM49Y/ZapULyXIkuRC83WNWvnLO0/img.png?width=800&amp;amp;height=436&amp;amp;face=0_0_800_436,https://scrap.kakaocdn.net/dn/fJwcN/hyZndEUDLV/DMmCsPCnGZmw4gvbmks0LK/img.png?width=800&amp;amp;height=436&amp;amp;face=0_0_800_436,https://scrap.kakaocdn.net/dn/pAZFh/hyZjFQCjj2/lsuG7R8NMY1vlaLfgaXDyK/img.png?width=1760&amp;amp;height=1504&amp;amp;face=0_0_1760_1504');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;실시간 대용량 데이터에서 사용되는 알고리즘(Reservoir Sampling, HLL, CMS,Bloom Filter)들에 대해 알아보&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스트리밍 시스템에서 모은 많은 데이터들은 시스템 안에서 효율적으로 분석되고 집계되어야합니다.아무래도 실시간이고 방대한 데이터를 대상으로 하는 만큼 '확률적인 알고리즘'이 많이 사용&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jaykos96.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;처리량 보장 확률적 샘플링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리량 보장 샘플러((guraranteed-throughput sampler))를 사용해 특정 상황에 트래픽이 급증하는 서비스가 있는 서비스에 적절한 샘플링 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확률적 샘플러와 속도 제한기((rate limiter))를 결합한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로는 확률적샘플링을 사용합니다, 하지만 서비스가 off-peak 기간이라면 요청이 추적되지 않을 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 지정된 시간 간격 이후에는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;속도 제한기((rate limiter))를 사용해 요청이 하나도 샘플링되지 않는 상황을 방지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사실 현재 이 방식은 현재 거의 사용되지 않으나, 이 샘플링을 토대로 적응형 샘플링이 만들어졌습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;적응형 샘플링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 API들이 받는 트레픽은 다양합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 사용하는 API는 트레픽이 많을 것이고, 어떤 API는 요청이 적을 것 입니다.&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;b&gt;적응형 샘플링은 기본적으로 수집한 처리량이 목표량을 넘어서면 샘플링 확률을 낮추는 방식입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;샘플링 확률은 동적으로 변경되며 각 서버에서 환경설정 파라미터를 읽어서 적용합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적응형 샘플링은 각 서비스의 트래픽에 따라 자동으로 샘플링 비율을 조절하여,&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;&lt;br /&gt;&lt;b&gt;1. TPS ((Traces per Second))&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 인스턴스당 평균 초당 몇 개의 트레이스를 수집할지 목표를 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. SPS ((Spans per Second))&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스팬 수의 양을 고려하여 목표를 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. BPS ((Bytes per Second))&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스팬 크기를 고려하여 목표를 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;적응형 샘플링 이론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예거((jaegor))의 샘플링을 가지고 샘플링에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예거의 샘플링은 고전적인 PID ((proportional-integral-derivative)) 제어기와 유사합니다.&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;2188&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2svuU/btsPqxAV24w/HFhukR2dfnt3A1GKyKjmu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2svuU/btsPqxAV24w/HFhukR2dfnt3A1GKyKjmu0/img.png&quot; data-alt=&quot;출처 : https://yunslog.tistory.com/78&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2svuU/btsPqxAV24w/HFhukR2dfnt3A1GKyKjmu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2svuU%2FbtsPqxAV24w%2FHFhukR2dfnt3A1GKyKjmu0%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;2188&quot; height=&quot;780&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://yunslog.tistory.com/78&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PID는 현재 output y((t))) , 프로세스 값을 관찰하고, output은 Process 처리되기 전에 보정신호 u((t)) 로 인해 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 입력인 원하는 프로세스 값과 output 사이의 오차((Error))를 최소화하기 위해 PID는 비례식(P), 적분 항(I), 미분 항(D)로 구성된 제어기의 가중치의 합계로 보정신호 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;u((t))&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;이런 PID의 개념을 샘플링에 적용해 보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;y((t))는 실측값, 특정 시간 동안 모든 인스턴스를 대상으로 특정 서비스에 대해 샘플링된 추적의 수, 최종 Output TPS입니다.&lt;/li&gt;
&lt;li&gt;r((t))는 허용추적률, 어플리케이션에서 오버헤드 수준과 추적 백엔드의 용량을 고려해 허용 가능하다고 생각하는 TPS, 원하는 Input TPS입니다.&lt;/li&gt;
&lt;li&gt;u((t))는 알고리즘으로 계산하는 샘플링 확률입니다.&lt;/li&gt;
&lt;li&gt;오류는 e((t)) = r((t))-y((t)), 목표값 - 현재값으로 계산합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;예거(Jaeger)의 적응형 샘플링&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;1124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsjI9e/btsPt0QR1sa/zmrHJwFV66DkfRL2VvyeE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsjI9e/btsPt0QR1sa/zmrHJwFV66DkfRL2VvyeE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsjI9e/btsPt0QR1sa/zmrHJwFV66DkfRL2VvyeE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsjI9e%2FbtsPt0QR1sa%2FzmrHJwFV66DkfRL2VvyeE1%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;1882&quot; height=&quot;1124&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;1124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카운터((Counter))는 수집기가 수집한 모든 루트 스팬을 받아서 루트 스팬의 카운트 수를 집계합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정 시간 동안 카운트 수를 수집하고, 이후 데이터베이스의 Counts 테이블에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 샘플링 여부와 상관없이 전체 요청수를 저장하는 위한 테이블입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;m분 동안 수집한 Counts 테이블 데이터들을 사용해 각 엔드포인트에 대한 y((t)), TPS를 계산합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;m은 loopback period 인데 보통 10분으로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;m, loopback period 값을 사용하는 이유는, 순간적인 트레픽의 증가와 같은 상황을 평균화해서 이후 적용될 샘플링 확률이 부드럽게 조정되도록 할 수 있기 때문입니다.&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;y((t))를 이후 Input TPS와 비교하여 개별 서비스나 엔드포인트에 대한 보정 신호((샘플링 확률)) u((t)) 계산 후 Targets 테이블에 저장합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이후 제어기((Controller))는 Targets 테이블에서 계산된 샘플링확률을 읽어 메모리에 캐시한 다음 각 추적기로 전송합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추적기는 주기적으로 수집기를 폴링하여 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;샘플링 전략을 가져옵니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;예거((Jaeger))의 리더 선출&lt;/h2&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;리더 선출을 위해 리더 임대((leader lease)) 정책을 사용하는데,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 수집기에서 일정주기로 카산드라((Cassandra))의 compare-and-set 연산으로 리더 임대((leader lease)) 튜플 ((N,T)) 을 저장합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 N은 경쟁자의 이름 또는 ID이고 T는 임대 만료 시간입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수집기는 리더 임대 튜플을 사용해 통해 T시간 까지 자신((N))이 리더를 하겠다는 의도를 전달합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 튜플을 전달했을 때 그 기간에 리더가 없으면 튜플을 보낸 노드가 리더가 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리더 임대 방식은 완전한 리더 선출 알고리즘((Paxos, Raft 등))에 비해서는 불완전합니다.&lt;br /&gt;네트워크 지연이나 타이밍 이슈로 인해 잠시 동안 두 수집기가 동시에 리더가 될수도 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;928&quot; data-start=&quot;845&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그러나 적응형 샘플링의 목적은 트래픽 기반의 확률 조정이며, 수 초 단위의 오차는 큰 문제가 되지 않는다고 판단하기에, 현실적인 해결책입니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;샘플링 확률 u((t)) 계산법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계산기(Calculator)가 m, loopback period 기간에 대한 누적 카운트 수를 읽었을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;y((t))의 현재 값을 얻기 위해 이들을 모두 합하고 (m)으로 나눕니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;u((t))의 예상값은 아래와 같이 계산합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\[&lt;br /&gt;u'(t)&amp;nbsp;=&amp;nbsp;u(t&amp;nbsp;-&amp;nbsp;1)&amp;nbsp;\times&amp;nbsp;q,\quad&amp;nbsp;q&amp;nbsp;=&amp;nbsp;\frac{r(t)}{y(t)}&lt;br /&gt;\]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;u'((t))은 새로 계산된 식, q는 보정계수, u((t-1)) 이전에 가지고 있던 보정값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(t)는loopback period의 시간 인덱스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\[&lt;br /&gt;q&amp;nbsp;=&amp;nbsp;\frac{r(t)}{y(t)}&amp;nbsp;=&amp;nbsp;\frac{10}{20}&amp;nbsp;=&amp;nbsp;\frac{1}{2}&lt;br /&gt;\Rightarrow&lt;br /&gt;u'(t)&amp;nbsp;=&amp;nbsp;u(t-1)&amp;nbsp;\times&amp;nbsp;q&amp;nbsp;=&amp;nbsp;\frac{u(t-1)}{2}&lt;br /&gt;\]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 수식은 r((t))=10TPS이고 현재 속도는 y((t))=20TPS라고 가정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상황에서는 보정계수 q는 1/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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 상황이 된다면 과다한 샘플링을 하고 있다고 판단한 것이고, 추적 벡엔드에 과부하가 걸렸던 상황이기에 가능한 한 빠르게 적용하며 어플리케이션의 특이사항 없이 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\[&lt;br /&gt;q&amp;nbsp;=&amp;nbsp;\frac{r(t)}{y(t)}&amp;nbsp;=&amp;nbsp;\frac{20}{10}&amp;nbsp;=&amp;nbsp;2&lt;br /&gt;\Rightarrow&lt;br /&gt;u'(t)&amp;nbsp;=&amp;nbsp;u(t-1)&amp;nbsp;\times&amp;nbsp;q&amp;nbsp;=&amp;nbsp;2&amp;nbsp;\times&amp;nbsp;u(t-1)&lt;br /&gt;\]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 위 처럼 r((t))=20TPS이고 현재 속도는 y((t))=10TPS이 된다면, 기존보다 샘플링해야하는 추적이 2배가 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보정값 q가 증가하게되면, 몇가지 문제가 발생할 수 있습니다.&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;간단한 예로 어플리케이션에 많은 양의 데이터가 들어오는 30분 단위의 batch 작업이 있다고 해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;batch 시간이 아닐때는 추적이 거의 없으므로 샘플링 확률을 올려서 더 많은 추적을 샘플링하려 합니다.&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;그렇기에 적응형 샘플링에서는 p &amp;gt; 1이면 샘플링값을 바로 계산하지 않고 댐핑 함수(damping function) (B)를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 PID의 제어기의 도함수 항처럼, 샘플링 확률값의 급증을 늦츨 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예거의 댐핑 함수는 확률값 증가율 (\theta)를 부과합니다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(\theta는 보통&lt;/span&gt;&amp;nbsp;0.5로 설정합니다.&lt;/p&gt;
&lt;p data-end=&quot;914&quot; data-start=&quot;885&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;914&quot; data-start=&quot;885&quot; data-ke-size=&quot;size16&quot;&gt;\[&lt;br /&gt;\beta(p_{\text{new}},&amp;nbsp;p_{\text{old}},&amp;nbsp;\theta)&amp;nbsp;=&lt;br /&gt;\begin{cases}&lt;br /&gt;p_{\text{old}}&amp;nbsp;\cdot&amp;nbsp;(1&amp;nbsp;+&amp;nbsp;\theta),&amp;nbsp;&amp;amp;&amp;nbsp;\text{if&amp;nbsp;}&amp;nbsp;\frac{p_{\text{new}}&amp;nbsp;-&amp;nbsp;p_{\text{old}}}{p_{\text{old}}}&amp;nbsp;&amp;gt;&amp;nbsp;\theta&amp;nbsp;\\&lt;br /&gt;p_{\text{new}},&amp;nbsp;&amp;amp;&amp;nbsp;\text{otherwise}&lt;br /&gt;\end{cases}&lt;br /&gt;\]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(p_\text{old}) 와 (p_\text{new}) 는 이전 확률 u((t-1))과 새로운 확률 u'((t))를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어, (p_\text{old})기존값이 0.1이고 (p_\text{new})가 0.35로 증가하는 상황이라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\[&lt;br /&gt;p_{\text{old}}&amp;nbsp;=&amp;nbsp;0.2,\;&amp;nbsp;p_{\text{new}}&amp;nbsp;=&amp;nbsp;0.35,\;&amp;nbsp;\theta&amp;nbsp;=&amp;nbsp;0.5&amp;nbsp;\Rightarrow&amp;nbsp;\frac{p_{\text{new}}&amp;nbsp;-&amp;nbsp;p_{\text{old}}}{p_{\text{old}}}&amp;nbsp;=&amp;nbsp;0.75&amp;nbsp;&amp;gt;&amp;nbsp;\theta&amp;nbsp;\Rightarrow&amp;nbsp;\beta(p_{\text{new}},&amp;nbsp;p_{\text{old}},&amp;nbsp;\theta)&amp;nbsp;=&amp;nbsp;0.2&amp;nbsp;\cdot&amp;nbsp;(1&amp;nbsp;+&amp;nbsp;0.5)&amp;nbsp;=&amp;nbsp;0.3&lt;br /&gt;\]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 샘플링 확률 0.35는 급격히 증가한 것이므로, 최종적으로 (\beta)는 0.3이 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 최종 u((t))를 구하는 수식은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\[&lt;br /&gt;u(t)&amp;nbsp;=&lt;br /&gt;\begin{cases}&lt;br /&gt;\min\left[1,\;&amp;nbsp;\beta\left(u'(t),\;&amp;nbsp;u(t-1),\;&amp;nbsp;\theta\right)\right]&amp;nbsp;&amp;amp;&amp;nbsp;\text{if&amp;nbsp;}&amp;nbsp;u'(t)&amp;nbsp;&amp;lt;&amp;nbsp;u(t-1)&amp;nbsp;\\&lt;br /&gt;u'(t)&amp;nbsp;&amp;amp;&amp;nbsp;\text{otherwise}&lt;br /&gt;\end{cases}&lt;br /&gt;\]&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 경우에 따라 적응형 샘플링은 트래픽 수 뿐만 아니라 초당 스팬 수, 초당 바이트 크기에 대해서도 최적화가 필요합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;y((t))를 아래와 같은 식을 사용해 계산하여 스팬 수, 바이트 크기에 대한 비율을 적용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1753449977552&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// S: 하나의 추적이 생성하는 스팬의 평균수
// B: 하나의 추적이 생성하는 평균 바이트 크기

(초당 스팬 수) = (초당 추적 수) &amp;times; S  
(초당 바이트 수) = (초당 추적 수) &amp;times; B&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;추가로 고려해야할 부분은 하한 속도 계산기((lower-bound rate limiter))입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 새로운 서비스가 배포 될 때, 해당 서비스의 수집기에서는 대상 샘플링 확률 u((t))를 계산할 데이터가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 u((t))는 기본값이 적용되는데, 이는 필요에 따라 보수적으로 매우 낮게 설정되어 있을수도 있습니다.&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;기존 계산식에 보정치 (r)을 사용하는 방식으로 적용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1753450989770&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;u(t)=max(u(t),r)&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 data-ke-size=&quot;size16&quot;&gt;만약 하한추적으로 1분에 한 번씩 추적하도록 설정된 서비스가 있을 때, 갑자기 1000개의 인스턴스에 서비스가 배포된다고 가정해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 각각 매분 하나의 추적을 샘플링하거나 초당 1000 / 60 = 16.7개의 추적을 샘플링하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 하한 속도 계산기((lower-bound rate limiter))에 의해 너무 많은 추적을 얻게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때는 해결 방안으로 해당 수치를 파악할 수 있는 배포 시스템과 통합하는 방법을 고려할수도 있고, 아니면 하한 속도를 계산하는 적응형 알고리즘을 사용하는 방법도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 서비스의 특정 부분에 주의를 집중시키고 더 높은 빈도로 샘플링해야하는 상황이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 서비스의 특정 버전에서 문제가 발생한다면 해당 버전에 대한 샘플링만을 더 높힌다면 더 빠르게 돌발상황에 대처 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇기에 추적 시스템에서는 이런 상황에 대처 할 수 있도록, 유연한 기능 메커니즘이 필요합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같은 상황을 대처하기 위해서는 컨텍스트-맞춤형 샘플링을 구현해서 사용해야합니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;오버샘플링 처리 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 계속 살펴보았듯 서비스에서 적절한 샘플링을 적용하는건 어려운일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 추적 시스템을 사용하는 클라이언트는 아마도 모든 샘플링들을 감지하고 처리하는 추적 시스템을 원할 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 샘플링확률을 올리다보면 오버샘플링이 될 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예거에서는 과부하로부터 서비스를 보호하기 위한 두가지 방법, 다운샘플링과 스로틀링이 존재합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;스로틀링과 포스트-컬렉션 다운샘플링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스로틀링 방식은 서비스에서 지나치게 많은 추적이 시작되었다고 판단되면 Drop Policy을 사용해서 추적을 조절하는 방식이며,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다운샘플링은 추적이 수집계층에 도착한 후, 2차 샘플링을 수행해서 오버샘플링을 처리하는 방법입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 traceId를 해시로 변환해서 0~1 사이의 실수로 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 지정한 확률보다 작으면 샘플링하고 크면 버립니다.&lt;/p&gt;
&lt;pre id=&quot;code_1753452823032&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val downSamplingProbability: Double = 0.1

def downSample(span: Span): Boolean = {
  if (span.isDebug) {
    return false
  }

  val z: Double = hash(span.traceId)
  return z &amp;lt; downSamplingProbability
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;테일 기반의 일관성 있는 샘플링&lt;/h2&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;하지만 테일 기반 샘플링은 일단 모든 추적을 수집한 후 샘플링을 진행합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일종의 pull model과 비슷합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반면 모든 트래픽에 대한 추적을 일단 수집해야하기에, 헤드기반에 비해 큰 오버헤드가 발생합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&quot;&gt;&lt;b&gt;또한 일단 수집된 데이터가 샘플링이 되기 전까지 어딘가에 데이터를 보관해야합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&quot;&gt;보통 어플리케이션에서 헤드 기반과 테일 기반 샘플링은 모두 동일한 비율로 데이터를 샘플링하기에 추가 오버헤드를 피하기 위해 메모리에 추적 데이터를 보관하는게 합리적이라고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&quot;&gt;하지만 서비스 상황에 따라 적절하게 저장소를 활용하는게 중요할 것 같습니다..&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&quot;&gt;아래 그림은 2개의 마이크로서비스에서 2개의 요청 T1, T2를 테일기반으로 샘플링하는 상황입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;모든 요청은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;수집기(Controller)로 전달됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&quot;&gt;수집기는 Stateless하기에 수집기에 잠시 저장될 때, TraceId 기반의 데이터 파티션이 필요합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;450&quot; data-start=&quot;365&quot; data-ke-size=&quot;size16&quot;&gt;예를들어 'hash(traceId) % 수집기 수'와 같은 방법으로 TraceID를 구분하고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;450&quot; data-start=&quot;365&quot; data-ke-size=&quot;size16&quot;&gt;파티션을 통해 각각의 Span들이 TraceId로 파티션되어 서로 다른 수집기로 가는 상황을 방지합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;450&quot; data-start=&quot;365&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 샘플링 정책을 통해 T2요청만 샘플링되어 저장되며 T1은 버려집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;1174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLTfNA/btsPBKl5oCn/n1vZmLdiwAT8PjVEy4CZ10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLTfNA/btsPBKl5oCn/n1vZmLdiwAT8PjVEy4CZ10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLTfNA/btsPBKl5oCn/n1vZmLdiwAT8PjVEy4CZ10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLTfNA%2FbtsPBKl5oCn%2Fn1vZmLdiwAT8PjVEy4CZ10%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;502&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;1174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;365&quot; data-end=&quot;450&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;p data-ke-size=&quot;size16&quot;&gt;추적의 대기 시간((latency))를 기준으로 샘플링합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 lantecy가 0ms ~ 2ms 구간에 들어오는 요청은 버리거나 낮은 확률로 샘플링합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면 100ms 이상 또는 1초 이상 대기가 있던 요청은 중요하게 판단하여 우선으로 샘플링합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;추적 중 에러가 발생한 트레이스는 무조건 적으로 샘플링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;450&quot; data-start=&quot;365&quot; data-ke-size=&quot;size18&quot;&gt;3. 클러스터링 기반 샘플링&lt;/p&gt;
&lt;p data-end=&quot;450&quot; data-start=&quot;365&quot; data-ke-size=&quot;size16&quot;&gt;유사한 유형의 트레이스, 예를들어 경로, 사용자, 쿼리 등을 클러스터링해서 그룹화된 요청들의 대표값들만 샘플링합니다.&lt;/p&gt;
&lt;p data-end=&quot;450&quot; data-start=&quot;365&quot; data-ke-size=&quot;size16&quot;&gt;특정 요청에 가중치를 두어 샘플링 할수도 있습니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅하면서 찾아보니 Datadog, X-ray와 같은 유명 APM들을 비롯해 많은 APM들은 헤드기반 샘플링에 보조적으로 중요하거나 데이터가 큰 스팬들은 테일기반 샘플링하는 경우가 많은 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉 헤드 기반으로 1차로 샘플링하고, 큰 요청이나 오류가 있는 요청들은 테일기반으로 수집해서 구분하여 저장하는 방식인 것 같습니다!&lt;/b&gt;&lt;/p&gt;</description>
      <category>APM만들기</category>
      <category>APM 만들기</category>
      <category>샘플러</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/107</guid>
      <comments>https://jaykos96.tistory.com/107#entry107comment</comments>
      <pubDate>Fri, 18 Jul 2025 23:24:46 +0900</pubDate>
    </item>
    <item>
      <title>분산추적에서 다른 프로세스, 외부 서비스간 전파는 어떻게 이루어질까</title>
      <link>https://jaykos96.tistory.com/106</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 분산추적APM을 사용하며 가장 궁금했던 부분은,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'다른 프로세스, 외부 서비스간 이동이 발생이 있었는데, 어떻게 하나의 추적이라고 계속 인식하는거지?' 였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈소스인 OTel 자바 프로젝트를 통해 분산추적 구현의 중요한 챕터 중 하나인 Context 전파에 대해서 알아보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/open-telemetry/opentelemetry-java-instrumentation&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1752503680386&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - open-telemetry/opentelemetry-java-instrumentation: OpenTelemetry auto-instrumentation and instrumentation libraries for&quot; data-og-description=&quot;OpenTelemetry auto-instrumentation and instrumentation libraries for Java - open-telemetry/opentelemetry-java-instrumentation&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot; data-og-url=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b1lItG/hyZnrvOUBi/K9gPP3snMB6DXa4e9ROhWk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/KWp45/hyZjzClq38/YYuFEgHxrJfKrJWCnRQIek/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b1lItG/hyZnrvOUBi/K9gPP3snMB6DXa4e9ROhWk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/KWp45/hyZjzClq38/YYuFEgHxrJfKrJWCnRQIek/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - open-telemetry/opentelemetry-java-instrumentation: OpenTelemetry auto-instrumentation and instrumentation libraries for&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry auto-instrumentation and instrumentation libraries for Java - open-telemetry/opentelemetry-java-instrumentation&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 Context는 APM의 추적, Trace에서 계속해서 생성되는 Span들이 하나의 Trace안에서 순차적으로 이어지기 위해 필요한 정보를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Span과 Trace에 대해서는 다른 포스팅에서 다루겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;APM에서 어떻게 프로세스, 스레드 변경을 탐지할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel의 추적은 LocalThread 기반으로 만들어져 있어, 기본적으로 동일 스레드 안에서만 APM의 Context를 공유합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇기에 새로운 프로세스, 스레드에 대한 추적을 적용하려면 LocalThread -&amp;gt; NewThread간 Context를 공유해야하기에&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;에이전트는 새로운 스레드의 생성을 지속적으로 감시하고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;새롭게 스레드를 생성하는 Runnable, Callable, ExecutorService 등을 감지하면, 에이전트에서 내부적으로 Context.wrap()를 호출하여 새로운 스레드에 Context가 추가된 Context 객체로 바꿔치기 합니다.&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;wrap은 Context 객체의 메소드로 자바의 스레드 관련 메소드가 실행될 때 현재 Context를 추가해주는 메소드입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1752498921756&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  default Runnable wrap(Runnable runnable) {
    return () -&amp;gt; {
      try (Scope ignored = makeCurrent()) {
        runnable.run();
      }
    };
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;HTTP, RPC 클라이언트 호출과 같은 경우에는 에이전트가 Context 정보를 헤더에 실어 보냅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수신하는 쪽에서는 Context를 추출하여 Cotext를 사용합니다.&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;추가로 OTel에서 Spring Batch의 추적과 계측은 아직 experimental상태로 OTel의 프로퍼티 옵션을 통해 활성은 가능하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 비활성화 되어있기에 다루지 않으려고 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멀티스레드가 발생 했을 때&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel은 기본적으로 LocalThread에 Context를 저장하기에 새롭게 스레드가 생긴다면, 해당 스레드에서 생성되는 Span은 이전 Context를 가지고 있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 하나의 Trace로 연결될 수 없기에, 새로운 스레드에 Context를 넘겨주는 작업이 필요합니다.&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;자바에서 Executor, ExecutorService같은 스레드 관련 클래스가 실행되면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트가 bytecode 레벨에서 해당 코드를 변경하여, 새로운 스레드가 실행 될때 현재 wrap메소드를 사용해 현재 Context를 넣어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751893861513&quot; class=&quot;livescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Executor.submit(() -&amp;gt; { ... })
Executor.submit(Context.current().wrap(() -&amp;gt; { ... }))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 잠깐 언급하였듯 wrap메소드는 각 Executor관련 메소드들을 wrap하여 makeCurrent로 현재 Context를 넣어주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 Runnable 말고도 여러 메소드에 대한 wrap이 존재하며, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Context.taskWrapping()를 사용해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Executor 자체를 감싸서 사용 할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751466955363&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  default Runnable wrap(Runnable runnable) {
    return () -&amp;gt; {
      try (Scope ignored = makeCurrent()) {
        runnable.run();
      }
    };
  }

  default &amp;lt;T&amp;gt; Callable&amp;lt;T&amp;gt; wrap(Callable&amp;lt;T&amp;gt; callable) {
    return () -&amp;gt; {
      try (Scope ignored = makeCurrent()) {
        return callable.call();
      }
    };
  }

  default Executor wrap(Executor executor) {
    return command -&amp;gt; executor.execute(wrap(command));
  }

  default ExecutorService wrap(ExecutorService executor) {
    if (executor instanceof ContextExecutorService) {
      return executor;
    }
    return new ContextExecutorService(this, executor);
  }

  default ScheduledExecutorService wrap(ScheduledExecutorService executor) {
    if (executor instanceof ContextScheduledExecutorService) {
      return executor;
    }
    return new ContextScheduledExecutorService(this, executor);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;비동기 호출 시&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CompletableFuture, @Async 등의 비동기 호출 또한 내부적으로 작업 스레드가 분리되어 새로운 스레드에서 실행되므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 멀티 스레드에서의 Context전파와 동일한 방식이 사용됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;HTTP, RPC, Kafka 호출이 발생 했을 때&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;멀티스레드가 아닌 http, rpc, 메시징 시스템 kafka 등을 활용한 호출이 발생했을때는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;각 방법들에 맞게 Context를 옮겨주는 작업이 필요합니다.&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: #000000; text-align: start;&quot;&gt;이 때 OTel에서는 Context를 전파할 때 Instrumenter 객체를 공통적으로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Instrumenter&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;에이전트에서 span을 생성하는데 필요한 객체입니다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&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: #000000; text-align: start;&quot;&gt;요청이 들어오면 에이전트는 Instrumenter를 사용해 span을 생성하고 종료시킵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;샘플링 여부 등을 파악해서 span의 시작여부를 결정하는 shouldStart() ,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;span을 시작하는 start(), 종료하는 end() 메소드로 구성되어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752242120566&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Instrumenter&amp;lt;REQUEST, RESPONSE&amp;gt; {

  ...

  public boolean shouldStart(Context parentContext, REQUEST request) {
    SpanKind kind = spanKindExtractor.extract(request);
    return enabled &amp;amp;&amp;amp; !spanSuppressor.shouldSuppress(parentContext, kind);
  }

  public Context start(Context parentContext, REQUEST request) {
    return doStart(parentContext, request, null);
  }

  public void end(Context context, REQUEST request, RESPONSE response, Throwable error) {
    doEnd(context, request, response, error, null);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTP 호출이 발생 했을 때&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http 모듈이 감지되면 에이전트에서는 헤더에 traceparent, tracestate라는 key에 Context를 담아서 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel에서는 Propagator객체를 사용하여 이 작업을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Inject()를 호출하여 헤더에 Context를 담고 , Extract()를 통해 헤더에 담긴 Context들을 추출해서 사용합니다.&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;RestTemplate를 사용하는 경우&lt;span&gt;&amp;nbsp;에이전트에서는&amp;nbsp;&lt;/span&gt;RestTemplateInterceptor를 bean-post-processor를 통해 모든 RestTemplate인스턴스에 추가하고, &lt;span&gt;WebClient의 경우에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;WebClient.Builder Bean을 후처리하여 http요청을 감지합니다.&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&gt;참고로 http호출이 발생하면 APM입장에서는 http호출이 시작되는 시점에 span을 생성하고, http응답을 받는 시점에 종료되는 'http호출'에 대한 하나의 span이 존재해야합니다.&lt;/span&gt;&lt;span&gt;&amp;nbsp;해당 span을 통해 trace를 더 정확하게 관리 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;traceparent, tracestate&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;traceId와 Context를 담아 보내는 W3C의 규격 헤더입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;traceparent는 필수지만, tracestate는 부가정보입니다.&amp;nbsp;&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;아래는 traceparent를 규정하는 W3C규격이며, trace-flags는 샘플링된 추적인지 여부를 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;01은 샘플링되는 추적, 00은 샘플링되지 않는 추적을 의미합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751897683277&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;traceparent: {version}-{trace-id}-{span-id}-{trace-flags}

00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;W3CTraceContextPropagator&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;propagator 객체는 http뿐만 아니라 rpc, 메시징 시스템에서 Context를 전달하기 위해 사용하는 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;inject(), extract()메소드를 사용해 Context를 주입, 추출합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Inject&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;W3CTraceContextPropagator 객체의 Inject() 메소드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context에 담긴 SpanContext를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;traceparent, tracestate&lt;span&gt; &lt;/span&gt;&lt;/span&gt;헤더에 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터의 C carrier는 헤더를 담을 객체, HttpHeaders 또는 Map입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751897220220&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
  public &amp;lt;C&amp;gt; void inject(Context context, @Nullable C carrier, TextMapSetter&amp;lt;C&amp;gt; setter) {
    if (context == null || setter == null) {
      return;
    }

    SpanContext spanContext = Span.fromContext(context).getSpanContext();
    if (!spanContext.isValid()) {
      return;
    }

    char[] chars = TemporaryBuffers.chars(TRACEPARENT_HEADER_SIZE);
    chars[0] = VERSION.charAt(0);
    chars[1] = VERSION.charAt(1);
    chars[2] = TRACEPARENT_DELIMITER;

    String traceId = spanContext.getTraceId();
    traceId.getChars(0, traceId.length(), chars, TRACE_ID_OFFSET);

    chars[SPAN_ID_OFFSET - 1] = TRACEPARENT_DELIMITER;

    String spanId = spanContext.getSpanId();
    spanId.getChars(0, spanId.length(), chars, SPAN_ID_OFFSET);

    chars[TRACE_OPTION_OFFSET - 1] = TRACEPARENT_DELIMITER;
    String traceFlagsHex = spanContext.getTraceFlags().asHex();
    chars[TRACE_OPTION_OFFSET] = traceFlagsHex.charAt(0);
    chars[TRACE_OPTION_OFFSET + 1] = traceFlagsHex.charAt(1);
    setter.set(carrier, TRACE_PARENT, new String(chars, 0, TRACEPARENT_HEADER_SIZE));
    TraceState traceState = spanContext.getTraceState();
    if (traceState.isEmpty()) {
      // No need to add an empty &quot;tracestate&quot; header.
      return;
    }
    setter.set(carrier, TRACE_STATE, encodeTraceState(traceState));
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Extract&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 도착하면 에이전트는 http헤더에서 traceparent, tracestate를 꺼내서 Context를 복원합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751897459198&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Override
  public &amp;lt;C&amp;gt; Context extract(Context context, @Nullable C carrier, TextMapGetter&amp;lt;C&amp;gt; getter) {
    if (context == null) {
      return Context.root();
    }
    if (getter == null) {
      return context;
    }

    SpanContext spanContext = extractImpl(carrier, getter);
    if (!spanContext.isValid()) {
      return context;
    }

    return context.with(Span.wrap(spanContext));
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RPC 호출이 발생 했을 때&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rpc에서는 메타데이터에 Context를 넣어서 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http와 동일하게 propagator클래스의&amp;nbsp; Extract(), Inject() 메소드를 사용하며, traceparent, tracestate는 RPC Metadata 키로서 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트에서 TracingClientInterceptor, TracingServerInterceptor에서 rpc을 감지하여 context를 주입합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;sendMessage, OnMessage&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rpc 요청을 보내고 수신할 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interceptor에서는 어떤 메시지가 몇 개 오갔는지, 또 언제 어떤 순서로 처리됐는지를 분석하기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sendMessage(), on onMessage() 메소드를 사용해 자동으로 증가하는 메시지ID와 메시지 타입 SEND, RECEIVED를 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 ID와 메시지 타입은 sendMessage()와 onMessage() 안에서 Span 이벤트로 기록됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751975863081&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Override
    public void sendMessage(REQUEST message) {
      try (Scope ignored = context.makeCurrent()) {
        super.sendMessage(message);
      } catch (Throwable e) {
        instrumenter.end(context, request, Status.UNKNOWN, e);
        throw e;
      }
      long messageId = SENT_MESSAGE_ID_UPDATER.incrementAndGet(this);
      if (emitMessageEvents) {
        Attributes attributes = Attributes.of(MESSAGE_TYPE, SENT, MESSAGE_ID, messageId);
        Span.fromContext(context).addEvent(&quot;message&quot;, attributes);
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1751976054085&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public void onMessage(REQUEST message) {
  long messageId = RECEIVED_MESSAGE_ID_UPDATER.incrementAndGet(TracingServerCall.this);
  if (emitMessageEvents) {
    Attributes attributes = Attributes.of(MESSAGE_TYPE, RECEIVED, MESSAGE_ID, messageId);
    Span.fromContext(context).addEvent(&quot;message&quot;, attributes);
  }
  delegate().onMessage(message);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TracingClientInterceptor-interceptCall&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC의 ClientInterceptor를 상속받아 interceptCall를 오버라이드하여, rpc가 나갈 때 Context를 추기해주는 역할을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751973166392&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class TracingClientInterceptor implements ClientInterceptor {

  ...

  @Override
  public &amp;lt;REQUEST, RESPONSE&amp;gt; ClientCall&amp;lt;REQUEST, RESPONSE&amp;gt; interceptCall(
      MethodDescriptor&amp;lt;REQUEST, RESPONSE&amp;gt; method, CallOptions callOptions, Channel next) {
    GrpcRequest request = new GrpcRequest(method, null, null, next.authority());
    Context parentContext = Context.current();
    if (!instrumenter.shouldStart(parentContext, request)) {
      return next.newCall(method, callOptions);
    }

    Context context = instrumenter.start(parentContext, request);
    ClientCall&amp;lt;REQUEST, RESPONSE&amp;gt; result;
    try (Scope ignored = context.makeCurrent()) {
      // call other interceptors
      result = next.newCall(method, callOptions);
    } catch (Throwable t) {
      instrumenter.end(context, request, Status.UNKNOWN, t);
      throw t;
    }

    return new TracingClientCall&amp;lt;&amp;gt;(result, parentContext, context, request);
  }

  
  final class TracingClientCall&amp;lt;REQUEST, RESPONSE&amp;gt;
      extends ForwardingClientCall.SimpleForwardingClientCall&amp;lt;REQUEST, RESPONSE&amp;gt; {

    private final Context parentContext;
    private final Context context;
    private final GrpcRequest request;

    // Used by SENT_MESSAGE_ID_UPDATER
    @SuppressWarnings(&quot;UnusedVariable&quot;)
    volatile long sentMessageId;

    // Used by RECEIVED_MESSAGE_ID_UPDATER
    @SuppressWarnings(&quot;UnusedVariable&quot;)
    volatile long receivedMessageId;

    TracingClientCall(
        ClientCall&amp;lt;REQUEST, RESPONSE&amp;gt; delegate,
        Context parentContext,
        Context context,
        GrpcRequest request) {
      super(delegate);
      this.parentContext = parentContext;
      this.context = context;
      this.request = request;
    }

    @Override
    public void start(Listener&amp;lt;RESPONSE&amp;gt; responseListener, Metadata headers) {
      propagators.getTextMapPropagator().inject(context, headers, MetadataSetter.INSTANCE);
      // store metadata so that it can be used by custom AttributesExtractors
      request.setMetadata(headers);
      try (Scope ignored = context.makeCurrent()) {
        super.start(
            new TracingClientCallListener(responseListener, parentContext, context, request),
            headers);
      } catch (Throwable e) {
        instrumenter.end(context, request, Status.UNKNOWN, e);
        throw e;
      }
    }
  
  ...
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TracingClientCallListener&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rpc의 interceptCall 메소드를 호출하기 위해서는 인자로 Listener를 전달해야하는데 이 때 OTel에서는 커스텀한 리스너 TracingClientCallListener를 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TracingClientCallListener는 클라이언트가 rpc 호출할 때 start() 내부에서 생성되며 gRPC의 ClientCall.Listener를 상속받아서 클라이언트가 서버 응답을 받을 때마다, Span에 이벤트를 찍고, Span을 종료하는 실질적인 역할을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751975067944&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class TracingClientInterceptor implements ClientInterceptor {

  ...

    final class TracingClientCallListener
        extends ForwardingClientCallListener.SimpleForwardingClientCallListener&amp;lt;RESPONSE&amp;gt; {

      private final Context parentContext;
      private final Context context;
      private final GrpcRequest request;

      TracingClientCallListener(
          Listener&amp;lt;RESPONSE&amp;gt; delegate,
          Context parentContext,
          Context context,
          GrpcRequest request) {
        super(delegate);
        this.parentContext = parentContext;
        this.context = context;
        this.request = request;
      }

      @Override
      public void onMessage(RESPONSE message) {
        long messageId = RECEIVED_MESSAGE_ID_UPDATER.incrementAndGet(TracingClientCall.this);
        if (emitMessageEvents) {
          Attributes attributes = Attributes.of(MESSAGE_TYPE, RECEIVED, MESSAGE_ID, messageId);
          Span.fromContext(context).addEvent(&quot;message&quot;, attributes);
        }
        try (Scope ignored = context.makeCurrent()) {
          delegate().onMessage(message);
        }
      }

      @Override
      public void onClose(Status status, Metadata trailers) {
        request.setPeerSocketAddress(getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
        if (captureExperimentalSpanAttributes) {
          Span span = Span.fromContext(context);
          span.setAttribute(
              GRPC_RECEIVED_MESSAGE_COUNT, RECEIVED_MESSAGE_ID_UPDATER.get(TracingClientCall.this));
          span.setAttribute(
              GRPC_SENT_MESSAGE_COUNT, SENT_MESSAGE_ID_UPDATER.get(TracingClientCall.this));
        }
        instrumenter.end(context, request, status, status.getCause());
        try (Scope ignored = parentContext.makeCurrent()) {
          delegate().onClose(status, trailers);
        }
      }

      @Override
      public void onReady() {
        try (Scope ignored = context.makeCurrent()) {
          delegate().onReady();
        }
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TracingServerInterceptor-interceptCall&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC의 ServerInterceptor를&lt;span&gt;&amp;nbsp;&lt;/span&gt;상속받아&lt;span&gt;&amp;nbsp;&lt;/span&gt;interceptCall를&amp;nbsp; 오버라이드하여,&lt;span&gt;&amp;nbsp;&lt;/span&gt;rpc가 들어오면 컨텍스트를 추출하고 Context에 저장합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751974220140&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class TracingServerInterceptor implements ServerInterceptor {

  ...

  @Override
  public &amp;lt;REQUEST, RESPONSE&amp;gt; ServerCall.Listener&amp;lt;REQUEST&amp;gt; interceptCall(
      ServerCall&amp;lt;REQUEST, RESPONSE&amp;gt; call,
      Metadata headers,
      ServerCallHandler&amp;lt;REQUEST, RESPONSE&amp;gt; next) {
    String authority = call.getAuthority();
    if (authority == null) {
      // Armeria grpc server call does not implement getAuthority(). In
      // ArmeriaServerCallInstrumentation we store the value for the authority header in a virtual
      // field.
      authority = GrpcAuthorityStorage.getAuthority(call);
    }
    GrpcRequest request =
        new GrpcRequest(
            call.getMethodDescriptor(),
            headers,
            call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR),
            authority);
    Context parentContext = Context.current();
    if (!instrumenter.shouldStart(parentContext, request)) {
      return next.startCall(call, headers);
    }

    Context context = instrumenter.start(parentContext, request);

    try (Scope ignored = context.makeCurrent()) {
      return new TracingServerCall&amp;lt;&amp;gt;(call, context, request).start(headers, next);
    } catch (Throwable e) {
      instrumenter.end(context, request, Status.UNKNOWN, e);
      throw e;
    }
  }

  final class TracingServerCall&amp;lt;REQUEST, RESPONSE&amp;gt;
      extends ForwardingServerCall.SimpleForwardingServerCall&amp;lt;REQUEST, RESPONSE&amp;gt; {
    private final Context context;
    private final GrpcRequest request;
    private Status status;

    // Used by SENT_MESSAGE_ID_UPDATER
    @SuppressWarnings(&quot;UnusedVariable&quot;)
    volatile long sentMessageId;

    // Used by RECEIVED_MESSAGE_ID_UPDATER
    @SuppressWarnings(&quot;UnusedVariable&quot;)
    volatile long receivedMessageId;

    TracingServerCall(
        ServerCall&amp;lt;REQUEST, RESPONSE&amp;gt; delegate, Context context, GrpcRequest request) {
      super(delegate);
      this.context = context;
      this.request = request;
    }

  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TracingServerInterceptor&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 rpc 요청을 수신할 때 interceptCall() 내부에서 생성되며, 요청을 받는 동안 메시지의 수신,완료,취소 처리를 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751976698868&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class TracingServerInterceptor implements ServerInterceptor {

    ...
    
    final class TracingServerCallListener
        extends ForwardingServerCallListener.SimpleForwardingServerCallListener&amp;lt;REQUEST&amp;gt; {
      private final Context context;
      private final GrpcRequest request;

      TracingServerCallListener(Listener&amp;lt;REQUEST&amp;gt; delegate, Context context, GrpcRequest request) {
        super(delegate);
        this.context = context;
        this.request = request;
      }

      private void end(Context context, GrpcRequest request, Status response, Throwable error) {
        if (captureExperimentalSpanAttributes) {
          Span span = Span.fromContext(context);
          span.setAttribute(
              GRPC_RECEIVED_MESSAGE_COUNT, RECEIVED_MESSAGE_ID_UPDATER.get(TracingServerCall.this));
          span.setAttribute(
              GRPC_SENT_MESSAGE_COUNT, SENT_MESSAGE_ID_UPDATER.get(TracingServerCall.this));
          if (Status.CANCELLED.equals(status)) {
            span.setAttribute(GRPC_CANCELED, true);
          }
        }
        instrumenter.end(context, request, response, error);
      }

      @Override
      public void onHalfClose() {
        try {
          delegate().onHalfClose();
        } catch (Throwable e) {
          instrumenter.end(context, request, Status.UNKNOWN, e);
          throw e;
        }
      }

      @Override
      public void onCancel() {
        try {
          delegate().onCancel();
        } catch (Throwable e) {
          end(context, request, Status.UNKNOWN, e);
          throw e;
        }
        end(context, request, Status.CANCELLED, null);
      }

      @Override
      public void onComplete() {
        try {
          delegate().onComplete();
        } catch (Throwable e) {
          end(context, request, Status.UNKNOWN, e);
          throw e;
        }
        if (status == null) {
          status = Status.UNKNOWN;
        }
        end(context, request, status, status.getCause());
      }

      @Override
      public void onReady() {
        try {
          delegate().onReady();
        } catch (Throwable e) {
          end(context, request, Status.UNKNOWN, e);
          throw e;
        }
      }
    }
  }
}&lt;/code&gt;&lt;/pre&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;컨슈머와 프로듀서를 사용해서 데이터를 전달할때는 kafka의 send(), poll() 메소드를 wrap해서 propagator의 inject(), extract()를 사용하여 Context를 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context 값의 key는 다른 방식들과 동일하게 traceparent입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시징 시스템에서 각 요청,응답 span은 process, received로 표현됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Producer-wrap&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java의 동적 Proxy로 kafka의 Producer를 감싸서 kafka의 send메소드를 인터셉트해서 값들을 buildAndInjectSpan에 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kafka의 콜백이 있으면 콜백도 넣어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1752069103687&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class KafkaTelemetry {
  
  ...


  /** Returns a decorated {@link Producer} that emits spans for each sent message. */
  @SuppressWarnings(&quot;unchecked&quot;)
  public &amp;lt;K, V&amp;gt; Producer&amp;lt;K, V&amp;gt; wrap(Producer&amp;lt;K, V&amp;gt; producer) {
    return (Producer&amp;lt;K, V&amp;gt;)
        Proxy.newProxyInstance(
            KafkaTelemetry.class.getClassLoader(),
            new Class&amp;lt;?&amp;gt;[] {Producer.class},
            (proxy, method, args) -&amp;gt; {
              // Future&amp;lt;RecordMetadata&amp;gt; send(ProducerRecord&amp;lt;K, V&amp;gt; record)
              // Future&amp;lt;RecordMetadata&amp;gt; send(ProducerRecord&amp;lt;K, V&amp;gt; record, Callback callback)
              if (&quot;send&quot;.equals(method.getName())
                  &amp;amp;&amp;amp; method.getParameterCount() &amp;gt;= 1
                  &amp;amp;&amp;amp; method.getParameterTypes()[0] == ProducerRecord.class) {
                ProducerRecord&amp;lt;K, V&amp;gt; record = (ProducerRecord&amp;lt;K, V&amp;gt;) args[0];
                Callback callback =
                    method.getParameterCount() &amp;gt;= 2
                            &amp;amp;&amp;amp; method.getParameterTypes()[1] == Callback.class
                        ? (Callback) args[1]
                        : null;
                return buildAndInjectSpan(record, producer, callback, producer::send);
              }
              try {
                return method.invoke(producer, args);
              } catch (InvocationTargetException exception) {
                throw exception.getCause();
              }
            });
  }

  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;buildAndInjectSpan&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buildAndInjectSpan에서는 Context를 꺼내고, OTel의 Kafka DTO, KafkaProducerRequest객체를 이용해 요청과 producer를 wrap 후, shouldStart 메소드로 해당 요청이 샘플링 되는 요청인지 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플링이 필요없으면 span생성 없이 카프카로 요청을 전달하고 필요하다면 span을 inject하여 요청을 전달합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1752069952190&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;K, V&amp;gt; Future&amp;lt;RecordMetadata&amp;gt; buildAndInjectSpan(
      ProducerRecord&amp;lt;K, V&amp;gt; record,
      Producer&amp;lt;K, V&amp;gt; producer,
      Callback callback,
      BiFunction&amp;lt;ProducerRecord&amp;lt;K, V&amp;gt;, Callback, Future&amp;lt;RecordMetadata&amp;gt;&amp;gt; sendFn) {
    Context parentContext = Context.current();

    KafkaProducerRequest request = KafkaProducerRequest.create(record, producer);
    if (!producerInstrumenter.shouldStart(parentContext, request)) {
      return sendFn.apply(record, callback);
    }

    Context context = producerInstrumenter.start(parentContext, request);
    propagator().inject(context, record.headers(), SETTER);

    try (Scope ignored = context.makeCurrent()) {
      return sendFn.apply(record, new ProducerCallback(callback, parentContext, context, request));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Consumer-wrap&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer와 동일하게 동적 Proxy를 사용해 kafka의 Consumer 를 wrap합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;proxy는 람다함수를 가로채서 poll() 메소드가 발생하면 먼저 시간 체크 작업을 시작합니다.&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;poll() 결과가 ConsumerRecords이면, ConsumerRecords는 여러 kafka메시지를 가지고 있는 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buildAndFinishSpan() 메소드를 실행시켜 poll()한 kafka메시지로 Context를 만들고 반환합니다.&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;이후 receiveContext, KafkaConsumer를 인자로 KafkaConsumerContext를 만들어 addTracing() 메소드를 호출해 반복문을 돌며 각 메시지마다 span을 만듭니다.&lt;/p&gt;
&lt;pre id=&quot;code_1752069149366&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class KafkaTelemetry {
  
  ...

  /** Returns a decorated {@link Consumer} that consumes spans for each received message. */
  @SuppressWarnings(&quot;unchecked&quot;)
  public &amp;lt;K, V&amp;gt; Consumer&amp;lt;K, V&amp;gt; wrap(Consumer&amp;lt;K, V&amp;gt; consumer) {
    return (Consumer&amp;lt;K, V&amp;gt;)
        Proxy.newProxyInstance(
            KafkaTelemetry.class.getClassLoader(),
            new Class&amp;lt;?&amp;gt;[] {Consumer.class},
            (proxy, method, args) -&amp;gt; {
              Object result;
              Timer timer = &quot;poll&quot;.equals(method.getName()) ? Timer.start() : null;
              try {
                result = method.invoke(consumer, args);
              } catch (InvocationTargetException exception) {
                throw exception.getCause();
              }
              // ConsumerRecords&amp;lt;K, V&amp;gt; poll(long timeout)
              // ConsumerRecords&amp;lt;K, V&amp;gt; poll(Duration duration)
              if (&quot;poll&quot;.equals(method.getName()) &amp;amp;&amp;amp; result instanceof ConsumerRecords) {
                ConsumerRecords&amp;lt;K, V&amp;gt; consumerRecords = (ConsumerRecords&amp;lt;K, V&amp;gt;) result;
                Context receiveContext = buildAndFinishSpan(consumerRecords, consumer, timer);
                if (receiveContext == null) {
                  receiveContext = Context.current();
                }
                KafkaConsumerContext consumerContext =
                    KafkaConsumerContextUtil.create(receiveContext, consumer);
                result = addTracing(consumerRecords, consumerContext);
              }
              return result;
            });
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;addTracing&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 메시지들에 span을 붙이는 메소드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 파티션의 메시지들을 consumerProcessInstrumenter를 사용하여 span의 시작과 종료를 감지하도록합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1752073040733&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  &amp;lt;K, V&amp;gt; ConsumerRecords&amp;lt;K, V&amp;gt; addTracing(
      ConsumerRecords&amp;lt;K, V&amp;gt; consumerRecords, KafkaConsumerContext consumerContext) {
    if (consumerRecords.isEmpty()) {
      return consumerRecords;
    }

    Map&amp;lt;TopicPartition, List&amp;lt;ConsumerRecord&amp;lt;K, V&amp;gt;&amp;gt;&amp;gt; records = new LinkedHashMap&amp;lt;&amp;gt;();
    for (TopicPartition partition : consumerRecords.partitions()) {
      List&amp;lt;ConsumerRecord&amp;lt;K, V&amp;gt;&amp;gt; list = consumerRecords.records(partition);
      if (list != null &amp;amp;&amp;amp; !list.isEmpty()) {
        list = TracingList.wrap(list, consumerProcessInstrumenter, () -&amp;gt; true, consumerContext);
      }
      records.put(partition, list);
    }
    return new ConsumerRecords&amp;lt;&amp;gt;(records);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reactive 환경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reactive 체인에서는 스레드가 언제든지 변경 될 수 있기에, OTel의 LocalThread 기반의 Context 전파를 사용할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reactor 관련 메소드가 실행되면 wrap 후 hook을 만들어 체인간의 Context전파를 해주어야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 webflux환경, 즉 netty의 멀티플렉싱 환경에서 추적은 bossGroup의 스레드가 아닌 Worker스레드에서부터 시작합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ContextPropagationOperator&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reactor 연산자들에 hook을 추가해주는 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reactor의 연산자들은 내부적으로 모두 Subscriber를 만들고 다음 연산자에게 넘깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 모든 체인은 내부적으로 CoreSubscriber 클래스가 사용됩니다.&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; text-align: start;&quot;&gt;ContextPropagaionOperator 클래스에서 registerOnEachOperator의 tracingLift() 함수로 연산자마다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;TracingSubscriber가 들어가도록 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1752496856642&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class ContextPropagationOperator {

   ...
   
      public void registerOnEachOperator() {
        synchronized (lock) {
          if (enabled) {
            return;
          }
          Hooks.onEachOperator(
              TracingSubscriber.class.getName(), tracingLift(asyncOperationEndStrategy));
          AsyncOperationEndStrategies.instance().registerStrategy(asyncOperationEndStrategy);
          registerScheduleHook(RunnableWrapper.class.getName(), RunnableWrapper::new);
          enabled = true;
        }
      }
   
   ...
   
  }&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;tracingLift 함수에서 reactor가 연산자마다 Subscriber를 생성할 때 그걸 감싸주는 Lifter를 등록합니다.&lt;br /&gt;Lifter를 통해 원래 subscriber를 TracingSubscriber로 감싸게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1752496878460&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static &amp;lt;T&amp;gt; Function&amp;lt;? super Publisher&amp;lt;T&amp;gt;, ? extends Publisher&amp;lt;T&amp;gt;&amp;gt; tracingLift(...) {
  return Operators.lift(
      ContextPropagationOperator::shouldInstrument, 
      new Lifter&amp;lt;&amp;gt;(...));
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1752496961243&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public CoreSubscriber&amp;lt;? super T&amp;gt; apply(Scannable publisher, CoreSubscriber&amp;lt;? super T&amp;gt; sub) {
  return new TracingSubscriber&amp;lt;&amp;gt;(sub, sub.currentContext());
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;마무리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산추적에서 각 상황에 대해서 Context를 어떻게 전파하는지 OTel프로젝트를 보며 정리해 보았는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈소스를 처음으로 분석하다 보니 이런식으로 분석하는게 맞는건지 또 좋은건지 판단이 어렵네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느정도 흐름은 이해했는데, 아무래도 직접 코드를 작성해봐야 어떤 디테일들이 있는지 알 수 있을 것 같습니다.&lt;/p&gt;</description>
      <category>APM만들기</category>
      <category>apm</category>
      <category>OTEL</category>
      <category>분산추적</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/106</guid>
      <comments>https://jaykos96.tistory.com/106#entry106comment</comments>
      <pubDate>Sun, 29 Jun 2025 23:41:41 +0900</pubDate>
    </item>
    <item>
      <title>OpenTelemetry 프로젝트를 보며 공부하는 분산추적의 핵심요소들</title>
      <link>https://jaykos96.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;APM을 직접 만들어 저의 사이드 프로젝트들에 쓰고싶다는 작은 소망이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금부터 APM을 만들기 위한 공부들과 만드는 과정들을 같이 포스팅하려고 합니다.&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;첫번째로 CNCF 표준이자 분산 추적 프레임워크인 OpenTelemetry, OTel의 구조와 핵심 구성요소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trace, Span, Baggage, Metric, Log 에 대해 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OTel이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 분산추적을 구현하는 방식은 매우 다양했고 대부분 직접 만들어 사용했기에 각 서비스들에 종속되어 있는 방식이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 지날수록 표준의 필요성이 느껴지게 되었고 두가지 오픈 소스가 만들어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나는 구글의 내부 라이브러리 집합인 센서스((Census))에서 시작된 오픈센서스((OpenCensus))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나는 CNCF 프로젝트인 OpenTracing입니다.&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;지금은 이 두 프로젝트가 합쳐저 OpenTelementry가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenTelementry는 Jaeger, Zipkin과 같은 어플리케이션이 아니고 예시와 같은 추적서버에 데이터를 전송하는 역할을 합니다.&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;SDK와 API는 openTelementry-java프로젝트에 있고 Agent관련 파일은 openTelementry-java-instrumentation 프로젝트에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 먼저 자바 APM을 만들고자 해당 프로젝트를 좀 살펴보려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 생태계는 JavaAPM 먼저 만들고 한번 도전해 볼까 합니다,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-java&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/open-telemetry/opentelemetry-java&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750423022580&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - open-telemetry/opentelemetry-java: OpenTelemetry Java SDK&quot; data-og-description=&quot;OpenTelemetry Java SDK. Contribute to open-telemetry/opentelemetry-java development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/open-telemetry/opentelemetry-java&quot; data-og-url=&quot;https://github.com/open-telemetry/opentelemetry-java&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cCXFHq/hyZcngk1aB/TVEFdrbuPkKv7YTIruhN41/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cBt8xi/hyZcliwBiS/qpch9Xzkx7d4AJylj6Wr2K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/open-telemetry/opentelemetry-java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cCXFHq/hyZcngk1aB/TVEFdrbuPkKv7YTIruhN41/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cBt8xi/hyZcliwBiS/qpch9Xzkx7d4AJylj6Wr2K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - open-telemetry/opentelemetry-java: OpenTelemetry Java SDK&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry Java SDK. Contribute to open-telemetry/opentelemetry-java development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/open-telemetry/opentelemetry-java-instrumentation&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750423057157&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - open-telemetry/opentelemetry-java-instrumentation: OpenTelemetry auto-instrumentation and instrumentation libraries for&quot; data-og-description=&quot;OpenTelemetry auto-instrumentation and instrumentation libraries for Java - open-telemetry/opentelemetry-java-instrumentation&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot; data-og-url=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nwTks/hyZbDKm5c8/Rfhdl069zKYIt0xJbBbye1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/c0iOoz/hyY77ffjdl/TLnKAxKlkwIjn28GlhNSXk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/open-telemetry/opentelemetry-java-instrumentation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nwTks/hyZbDKm5c8/Rfhdl069zKYIt0xJbBbye1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/c0iOoz/hyY77ffjdl/TLnKAxKlkwIjn28GlhNSXk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - open-telemetry/opentelemetry-java-instrumentation: OpenTelemetry auto-instrumentation and instrumentation libraries for&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry auto-instrumentation and instrumentation libraries for Java - open-telemetry/opentelemetry-java-instrumentation&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트레이서 ((Tracer))&lt;/h2&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;분산 추적 시스템에서 요청은 여러 서비스와 컴포넌트 등 다양한 홉((hop))을 거치게 되는데 Trace는 그 전체에 대한 경로를 의미하며 각 Trace들은Trace_Id로 구분됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trace들은 여러 hop들을 통과하면서 스팬((Span))을 생성하는 역할을 합니다.&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;Tracer는 TracerProvider로 부터 생성되며, Provider는 TracerProvider는 어플리케이션의 수명주기와 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Expoter를 통해 Tracer를 Consumer, 다른 서버에 보낼수도 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 코드를 찾아보니 TracerExpoter는 따로 없고 SpanExtoter에서 해당 기능을 수행합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 Tracer쪽 소스코드를 간략히 살펴보려고합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tracer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tracer 인터페이스는 Span을 생성하는 &lt;b&gt;SpanBuilder를 생성하는 메소드 하나만 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1750422107781&quot; class=&quot;puppet&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ThreadSafe
public interface Tracer {

  /**
   * Returns a {@link SpanBuilder} to create and start a new {@link Span}.
   *
   * &amp;lt;p&amp;gt;See {@link SpanBuilder} for usage examples.
   *
   * @param spanName The name of the returned Span.
   * @return a {@code Span.Builder} to create and start a new {@code Span}.
   */
  SpanBuilder spanBuilder(String spanName);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Tracer_ID&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tracer_ID가 어떻게 생성되는지 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 이런 오픈소스를 통해서 공부하는게 처음이라, 어떤식으로 ID를 관리하는지 궁금했었는데 이제야 확인해보게 되네요&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; text-align: start;&quot;&gt;trace_id의 크기는 16byte입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;id를 사용할때는 이를 절반, 8byte로 나누어 high, low로 구분합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이렇게 구분하여 사용하는 이유는 byte[16]과 같이 사용하면 jvm에서 heap객체로 사용되어서 G.C의 영향을 받게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;반면 원시타입 long을 사용하면 G.C의 영향을 받지 않기에 이런식으로 구분하여 사용한다고 합니다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;또한 값이 모두 0이면 유효하지 않은 Trace로 취급합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;string으로 출력할때는 16진수 문자열로 변환하여 32자리의 string을 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750685057997&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class TraceId {
  private static final int BYTES_LENGTH = 16;
  private static final int HEX_LENGTH = 2 * BYTES_LENGTH;
  private static final String INVALID = &quot;00000000000000000000000000000000&quot;;

  private TraceId() {}

  ...
 
   public static String fromLongs(long traceIdLongHighPart, long traceIdLongLowPart) {
    if (traceIdLongHighPart == 0 &amp;amp;&amp;amp; traceIdLongLowPart == 0) {
      return getInvalid();
    }
    char[] chars = TemporaryBuffers.chars(HEX_LENGTH);
    OtelEncodingUtils.longToBase16String(traceIdLongHighPart, chars, 0);
    OtelEncodingUtils.longToBase16String(traceIdLongLowPart, chars, 16);
    return new String(chars, 0, HEX_LENGTH);
  }
 
 ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Trace_ID의 생성은 RandomIdGenerator 객체를 이용해 high, low id값을 만들어 조합후 리턴해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750686894376&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum RandomIdGenerator implements IdGenerator {
  INSTANCE;

  private static final long INVALID_ID = 0;
  private static final Supplier&amp;lt;Random&amp;gt; randomSupplier = RandomSupplier.platformDefault();

  @Override
  public String generateSpanId() {
    long id;
    Random random = randomSupplier.get();
    do {
      id = random.nextLong();
    } while (id == INVALID_ID);
    return SpanId.fromLong(id);
  }

  @Override
  public String generateTraceId() {
    Random random = randomSupplier.get();
    long idHi = random.nextLong();
    long idLo;
    do {
      idLo = random.nextLong();
    } while (idLo == INVALID_ID);
    return TraceId.fromLongs(idHi, idLo);
  }

  @Override
  public String toString() {
    return &quot;RandomIdGenerator{}&quot;;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Tracer가 가지고 있는 정보들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tracer는 span을 만드는 간단한 업무만 하지만, Tracer가 자체적으로 가지고 있는 정보도 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tracer에 어떤 값들이 있어야하는지 SdkTracerBuilder와 Tracer의 구현체 SdkTracer 코드를 통해 알아보겠습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;TracerBuilder&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SdkTracerBuilder 부터 먼저 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tracer를 만드는 빌더 클래스입니다.&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;TracerBuilder에는 가 어떤 기능에 사용될지 지정하는 instrumentationScopeName, 주로 라이브러리나 모듈을 지정합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해당 Version, URL 정보를 넣기위한 schemaUrl이 존재합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리고 이렇게 만들어둔 Tracer 인스턴스를 재사용하기 위한 registry가 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;build() 메소드는 레지스트리에 해당 트레이서가 없으면 생성하는 역할까지 합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Builder를 통해 생성된 각 Tracer는 고유한 이름을 사용해 해당 Tracer가 어떤 라이브러리나 모듈에 속하는지 식별합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를들어 &quot;io.opentelemetry.contrib.mongodb&quot;라는 이름((instrumentationScopeName))의 Tracer를 통해 생성된 Span들은 MongoDB 관련 작업을 의미하도록 추적에 명시 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Tracer는&lt;span&gt;&amp;nbsp;&lt;/span&gt;여러 개 만들어질 수 있지만, 일반적으로 구성요소별로 하나씩만 생성하여 재사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750561681133&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SdkTracerBuilder implements TracerBuilder {

  private final ComponentRegistry&amp;lt;SdkTracer&amp;gt; registry;
  private final String instrumentationScopeName;
  @Nullable private String instrumentationScopeVersion;
  @Nullable private String schemaUrl;

  SdkTracerBuilder(ComponentRegistry&amp;lt;SdkTracer&amp;gt; registry, String instrumentationScopeName) {
    this.registry = registry;
    this.instrumentationScopeName = instrumentationScopeName;
  }

  @Override
  public TracerBuilder setSchemaUrl(String schemaUrl) {
    this.schemaUrl = schemaUrl;
    return this;
  }

  @Override
  public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersion) {
    this.instrumentationScopeVersion = instrumentationScopeVersion;
    return this;
  }

  @Override
  public Tracer build() {
    return registry.get(
        instrumentationScopeName, instrumentationScopeVersion, schemaUrl, Attributes.empty());
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SdkTracer&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SDK에서 사용하는 Tracer의 구현체입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 부분을 보면 3가지 객체가 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 객체들을 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750563759254&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SdkTracer implements Tracer {
  static final String FALLBACK_SPAN_NAME = &quot;&amp;lt;unspecified span name&amp;gt;&quot;;
  private static final Tracer NOOP_TRACER = TracerProvider.noop().get(&quot;noop&quot;);
  private static final boolean INCUBATOR_AVAILABLE;

  static {
    boolean incubatorAvailable = false;
    try {
      Class.forName(&quot;io.opentelemetry.api.incubator.trace.ExtendedDefaultTracerProvider&quot;);
      incubatorAvailable = true;
    } catch (ClassNotFoundException e) {
      // Not available
    }
    INCUBATOR_AVAILABLE = incubatorAvailable;
  }

  private final TracerSharedState sharedState;
  private final InstrumentationScopeInfo instrumentationScopeInfo;
  protected boolean tracerEnabled;

  SdkTracer(
      TracerSharedState sharedState,
      InstrumentationScopeInfo instrumentationScopeInfo,
      TracerConfig tracerConfig) {
    this.sharedState = sharedState;
    this.instrumentationScopeInfo = instrumentationScopeInfo;
    this.tracerEnabled = tracerConfig.isEnabled();
  }&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;1. TracerSharedState&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tracer가 공통적으로 가지고 있는 설정값들입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750563636051&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class TracerSharedState {

 ...

  TracerSharedState(
      Clock clock,
      IdGenerator idGenerator,
      Resource resource,
      Supplier&amp;lt;SpanLimits&amp;gt; spanLimitsSupplier,
      Sampler sampler,
      List&amp;lt;SpanProcessor&amp;gt; spanProcessors,
      ExceptionAttributeResolver exceptionAttributeResolver) {
		...
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;clock : Span의 시작/종료 시간을 측정&lt;/li&gt;
&lt;li&gt;idGenerator : TraceId, SpanId를 만드는 객체&lt;/li&gt;
&lt;li&gt;idGeneratorSafeToSkipIdValidation : ID 생성 검증 생략 여부&lt;/li&gt;
&lt;li&gt;resource : 해당 Trace가 속한 서비스/환경 정보&lt;/li&gt;
&lt;li&gt;activeSpanProcessor : Span종료 시 Export 등 종료 처리 객체&lt;/li&gt;
&lt;li&gt;exceptionAttributeResolver : 예외처리&lt;/li&gt;
&lt;li&gt;shutdownResult : 종료 상태 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. InstrumentationScopeInfo&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 살펴보았던 Tracer의 기본 정보(ScopeName, Version, SchemaUrl)를 담는 객체입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750683527088&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class InstrumentationScopeInfoBuilder {

  private final String name;
  @Nullable private String version;
  @Nullable private String schemaUrl;
  @Nullable private Attributes attributes;
  
   ... 
  
  }&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;3. TracerConfig&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tracer가 생성 될 때 Tracer의 생성을 막는 메소드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한 계측을 할 것 같다거나, 라이브러리의 성능의 이슈가 있을 수 있을경우 명시적으로 해당 scope의 tracer를 막기 위한 설정값으로 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750684269298&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class TracerConfig {

  private static final TracerConfig DEFAULT_CONFIG =
      new AutoValue_TracerConfig(/* enabled= */ true);
  private static final TracerConfig DISABLED_CONFIG =
      new AutoValue_TracerConfig(/* enabled= */ false);

  /** Returns a disabled {@link TracerConfig}. */
  public static TracerConfig disabled() {
    return DISABLED_CONFIG;
  }

  /** Returns an enabled {@link TracerConfig}. */
  public static TracerConfig enabled() {
    return DEFAULT_CONFIG;
  }
  
  ...
  
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 생성자 아래 부분 코드도 살펴보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개념적으로 계속 Tracer는 Span을 생성한다고 했지만 코드상으로는 Span을 만드는 SpanBuilder를 리턴합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750426161327&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SdkTracer implements Tracer {
  ...
  
  /**
   * Note that {@link ExtendedSdkTracer#spanBuilder(String)} calls this and depends on it returning
   * {@link ExtendedSdkTracer} in all cases when the incubator is present.
   */
  @Override
  public SpanBuilder spanBuilder(String spanName) {
    if (!tracerEnabled) {
      return NOOP_TRACER.spanBuilder(spanName);
    }
    if (spanName == null || spanName.trim().isEmpty()) {
      spanName = FALLBACK_SPAN_NAME;
    }
    if (sharedState.hasBeenShutdown()) {
      return NOOP_TRACER.spanBuilder(spanName);
    }
    return INCUBATOR_AVAILABLE
        ? IncubatingUtil.createExtendedSpanBuilder(
            spanName, instrumentationScopeInfo, sharedState, sharedState.getSpanLimits())
        : new SdkSpanBuilder(
            spanName, instrumentationScopeInfo, sharedState, sharedState.getSpanLimits());
  }

  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;TraceProvider&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tracer들의 팩토리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 어플리케이션 시작 시 TraceProvider를 초기화하여 전역으로 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TracerProvider는 Stateful한 객체로서 샘플링, SpanProcessor, Exporter 등 추적 구성((config)) 정보를 보유하여 해당 Provider에서 만들어진 모든 Tracer와 Span에 일관된 설정을 적용합니다.&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;메소드에 get밖에 없어서 어디서 Tracer를 만드는거지? 했지만 타고타고 살펴보면 구현체의 get()메소드가 실행되면 Tracer들의 저장소인 ComponentRegistry에서 원하는 Tracer가 없으면 생성하는 역할까지 하고있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750423945211&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package io.opentelemetry.api.trace;

import javax.annotation.concurrent.ThreadSafe;

@ThreadSafe
public interface TracerProvider {

  /**
   * Returns a no-op {@link TracerProvider} which only creates no-op {@link Span}s which do not
   * record nor are emitted.
   */
  static TracerProvider noop() {
    return DefaultTracerProvider.getInstance();
  }

  /**
   * Gets or creates a named tracer instance.
   *
   * @param instrumentationScopeName A name uniquely identifying the instrumentation scope, such as
   *     the instrumentation library, package, or fully qualified class name. Must not be null.
   * @return a tracer instance.
   */
  Tracer get(String instrumentationScopeName);

  /**
   * Gets or creates a named and versioned tracer instance.
   *
   * @param instrumentationScopeName A name uniquely identifying the instrumentation scope, such as
   *     the instrumentation library, package, or fully qualified class name. Must not be null.
   * @param instrumentationScopeVersion The version of the instrumentation scope (e.g., &quot;1.0.0&quot;).
   * @return a tracer instance.
   */
  Tracer get(String instrumentationScopeName, String instrumentationScopeVersion);

  /**
   * Creates a TracerBuilder for a named {@link Tracer} instance.
   *
   * @param instrumentationScopeName A name uniquely identifying the instrumentation scope, such as
   *     the instrumentation library, package, or fully qualified class name. Must not be null.
   * @return a TracerBuilder instance.
   * @since 1.4.0
   */
  default TracerBuilder tracerBuilder(String instrumentationScopeName) {
    return DefaultTracerBuilder.getInstance();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SdkTraceProvider&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SDK에서 사용하는 TraceProvider의 구현체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tracer의 설정값을 지정하는 생성자, 익스포터로 전송하는 flush메소드, 가장 핵심인 Tracer를 만드는 get()메소드도 구현되어있습니다.&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;또한 이미 만들어둔 Tracer 객체를 TracerBuilder를 통해 재사용하기 위한 ComponentRegistry 객체와,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 따라 기존 설정값이 아닌 다른 설정값을 새로이 지정할 수 있도록 하는 setTracerConfigurator메소드도 보입니다.&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;참고로 ComponentRegistry는 Map을 통해 Tracer들을 관리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750424955344&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class SdkTracerProvider implements TracerProvider, Closeable {
  private static final Logger logger = Logger.getLogger(SdkTracerProvider.class.getName());
  static final String DEFAULT_TRACER_NAME = &quot;&quot;;
  private final TracerSharedState sharedState;
  private final ComponentRegistry&amp;lt;SdkTracer&amp;gt; tracerSdkComponentRegistry;
  // deliberately not volatile because of performance concerns
  // - which means its eventually consistent
  private ScopeConfigurator&amp;lt;TracerConfig&amp;gt; tracerConfigurator;

  /**
   * Returns a new {@link SdkTracerProviderBuilder} for {@link SdkTracerProvider}.
   *
   * @return a new {@link SdkTracerProviderBuilder} for {@link SdkTracerProvider}.
   */
  public static SdkTracerProviderBuilder builder() {
    return new SdkTracerProviderBuilder();
  }

  @SuppressWarnings(&quot;NonApiType&quot;)
  SdkTracerProvider(
      Clock clock,
      IdGenerator idsGenerator,
      Resource resource,
      Supplier&amp;lt;SpanLimits&amp;gt; spanLimitsSupplier,
      Sampler sampler,
      List&amp;lt;SpanProcessor&amp;gt; spanProcessors,
      ScopeConfigurator&amp;lt;TracerConfig&amp;gt; tracerConfigurator,
      ExceptionAttributeResolver exceptionAttributeResolver) {
    this.sharedState =
        new TracerSharedState(
            clock,
            idsGenerator,
            resource,
            spanLimitsSupplier,
            sampler,
            spanProcessors,
            exceptionAttributeResolver);
    this.tracerSdkComponentRegistry =
        new ComponentRegistry&amp;lt;&amp;gt;(
            instrumentationScopeInfo -&amp;gt;
                SdkTracer.create(
                    sharedState,
                    instrumentationScopeInfo,
                    getTracerConfig(instrumentationScopeInfo)));
    this.tracerConfigurator = tracerConfigurator;
  }

  private TracerConfig getTracerConfig(InstrumentationScopeInfo instrumentationScopeInfo) {
    TracerConfig tracerConfig = tracerConfigurator.apply(instrumentationScopeInfo);
    return tracerConfig == null ? TracerConfig.defaultConfig() : tracerConfig;
  }

  @Override
  public Tracer get(String instrumentationScopeName) {
    return tracerBuilder(instrumentationScopeName).build();
  }

  @Override
  public Tracer get(String instrumentationScopeName, String instrumentationScopeVersion) {
    return tracerBuilder(instrumentationScopeName)
        .setInstrumentationVersion(instrumentationScopeVersion)
        .build();
  }

  @Override
  public TracerBuilder tracerBuilder(@Nullable String instrumentationScopeName) {
    // Per the spec, both null and empty are &quot;invalid&quot; and a default value should be used.
    if (instrumentationScopeName == null || instrumentationScopeName.isEmpty()) {
      logger.fine(&quot;Tracer requested without instrumentation scope name.&quot;);
      instrumentationScopeName = DEFAULT_TRACER_NAME;
    }
    return new SdkTracerBuilder(tracerSdkComponentRegistry, instrumentationScopeName);
  }

  /** Returns the {@link SpanLimits} that are currently applied to created spans. */
  public SpanLimits getSpanLimits() {
    return sharedState.getSpanLimits();
  }

  /** Returns the configured {@link Sampler}. */
  public Sampler getSampler() {
    return sharedState.getSampler();
  }

  /**
   * Updates the tracer configurator, which computes {@link TracerConfig} for each {@link
   * InstrumentationScopeInfo}.
   *
   * &amp;lt;p&amp;gt;This method is experimental so not public. You may reflectively call it using {@link
   * SdkTracerProviderUtil#setTracerConfigurator(SdkTracerProvider, ScopeConfigurator)}.
   *
   * @see TracerConfig#configuratorBuilder()
   */
  void setTracerConfigurator(ScopeConfigurator&amp;lt;TracerConfig&amp;gt; tracerConfigurator) {
    this.tracerConfigurator = tracerConfigurator;
    this.tracerSdkComponentRegistry
        .getComponents()
        .forEach(
            sdkTracer -&amp;gt;
                sdkTracer.updateTracerConfig(
                    getTracerConfig(sdkTracer.getInstrumentationScopeInfo())));
  }

  /**
   * Attempts to stop all the activity for {@link Tracer}s created by this provider. Calls {@link
   * SpanProcessor#shutdown()} for all registered {@link SpanProcessor}s.
   *
   * &amp;lt;p&amp;gt;The returned {@link CompletableResultCode} will be completed when all the Spans are
   * processed.
   *
   * &amp;lt;p&amp;gt;After this is called, newly created {@code Span}s will be no-ops.
   *
   * &amp;lt;p&amp;gt;After this is called, further attempts at re-using this instance will result in undefined
   * behavior. It should be considered a terminal operation for the SDK.
   *
   * @return a {@link CompletableResultCode} which is completed when all the span processors have
   *     been shut down.
   */
  public CompletableResultCode shutdown() {
    if (sharedState.hasBeenShutdown()) {
      logger.log(Level.INFO, &quot;Calling shutdown() multiple times.&quot;);
      return CompletableResultCode.ofSuccess();
    }
    return sharedState.shutdown();
  }

  /**
   * Requests the active span processor to process all span events that have not yet been processed
   * and returns a {@link CompletableResultCode} which is completed when the flush is finished.
   *
   * @see SpanProcessor#forceFlush()
   */
  public CompletableResultCode forceFlush() {
    return sharedState.getActiveSpanProcessor().forceFlush();
  }

  /**
   * Attempts to stop all the activity for {@link Tracer}s created by this provider. Calls {@link
   * SpanProcessor#shutdown()} for all registered {@link SpanProcessor}s.
   *
   * &amp;lt;p&amp;gt;This operation may block until all the Spans are processed. Must be called before turning
   * off the main application to ensure all data are processed and exported.
   *
   * &amp;lt;p&amp;gt;After this is called, newly created {@code Span}s will be no-ops.
   *
   * &amp;lt;p&amp;gt;After this is called, further attempts at re-using this instance will result in undefined
   * behavior. It should be considered a terminal operation for the SDK.
   */
  @Override
  public void close() {
    shutdown().join(10, TimeUnit.SECONDS);
  }

  @Override
  public String toString() {
	// ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;스팬 ((Span))&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스팬은 어플리케이션이 수행하는 작업 단위를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;span들은 부모-자식 구조로 모여져 하나의 trace를 구성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel에서는 이벤트기반 설계같은 아키텍쳐도 커버하기 위해 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;부모-자식 관계를 &lt;/span&gt;Link관계라고도 표현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;span은 hop마다 시작시간과 종료시간, 예외와 이벤트를 기록하는 역할이기에 추적에서 굉장히 중요한 요소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 Elastic APM의 Tracing 관련 그림과 제가 작성한 코드인데 이를 보면 span이 어떤 느낌인지 쉽게 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwbEfx/btsOOaF7FQL/2k3PQJtFt51HNAkTPVhj6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwbEfx/btsOOaF7FQL/2k3PQJtFt51HNAkTPVhj6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwbEfx/btsOOaF7FQL/2k3PQJtFt51HNAkTPVhj6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwbEfx%2FbtsOOaF7FQL%2F2k3PQJtFt51HNAkTPVhj6k%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;713&quot; height=&quot;389&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;686&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;pre id=&quot;code_1750768736575&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Span span = tracer.spanBuilder(&quot;span name&quot;)
    .setSpanKind(SpanKind.INTERNAL)               // Span 종류 설정 (Internal 기본값)
    .setAttribute(&quot;com.acme.string-key&quot;, &quot;value&quot;) // 속성 설정 (여러 타입 지원)
    // ... 추가 속성들 설정 ...
    .addLink(linkContext, linkAttributes)         // 링크 추가 (옵션)
    .startSpan();                                 // Span 시작

if (span.isRecording()) {                        // 샘플링되어 기록중인 경우에만
    span.updateName(&quot;new span name&quot;);            // Span 이름 변경 (필요시)
    span.setAttribute(&quot;com.acme.new-attr&quot;, &quot;v&quot;); // 동적으로 속성 추가
    span.addLink(otherCtx);                      // 실행 중 링크 추가
    span.addEvent(&quot;my-event&quot;);                   // 이벤트 추가
    span.addEvent(&quot;my-event&quot;, someAttributes);   // 속성 포함 이벤트 추가
    span.recordException(new RuntimeException(&quot;error&quot;)); // 예외 기록 이벤트
    span.setStatus(StatusCode.OK, &quot;done&quot;);       // 상태 설정 (OK 또는 ERROR)
}
span.end(); // Span 종료&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;아래는 실제 코드는 아니고, 이해를 돕기 위해 제가 적어본 Span이 생성되고 종료되는 과정에 대한 코드입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리는 Tracer의 spanBuilder메소드로 빌더를 만들고 빌더에 각 옵션값들을 넣은뒤 span을 실행시킵니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기록이 되어야하는 span이라면 스팬의 값들을 채우게됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;OTel에서 Span의 이름은 낮은 카더널리티를 추구합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 많은 정보가 들어가야한다면 Attribute속성 사용을 권장합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 span이 종료되지 않으면 메모리누수와 Trace가 종료되지 않는 문제가 생기기에 반드시 적절하게 종료되어야합니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Span-Context&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;trace에서 span을 식별하는 불변 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 서비스가 다른 서비스에게 요청 할 때 Trace_ID와 Span_ID를 실어 보내면 수신 서비스에서는 이를 SpanContext로 받아들여 새로운 Span의 부모로 취급하게 됩니다.&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;Trace_ID, Span_ID, 샘플링 여부를 결정하는 TraceFlags, Trace의 상태값들을 저장하는 TraceState가 주된 구성요소이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TraceState는 key/value 리스트로 불변객체 특성상 새로운 key가 추가되면 새로운 TraceState를 반환하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;W3C 표준 형식에 맞게 작성해야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750770270477&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Immutable
@AutoValue
public abstract class ImmutableSpanContext implements SpanContext {

  public static final SpanContext INVALID =
      createInternal(
          TraceId.getInvalid(),
          SpanId.getInvalid(),
          TraceFlags.getDefault(),
          TraceState.getDefault(),
          /* remote= */ false,
          /* valid= */ false);

  private static AutoValue_ImmutableSpanContext createInternal(
      String traceId,
      String spanId,
      TraceFlags traceFlags,
      TraceState traceState,
      boolean remote,
      boolean valid) {
    return new AutoValue_ImmutableSpanContext(
        traceId, spanId, traceFlags, traceState, remote, valid);
  }

  public static SpanContext create(
      String traceIdHex,
      String spanIdHex,
      TraceFlags traceFlags,
      TraceState traceState,
      boolean remote,
      boolean skipIdValidation) {
    if (skipIdValidation || (SpanId.isValid(spanIdHex) &amp;amp;&amp;amp; TraceId.isValid(traceIdHex))) {
      return createInternal(
          traceIdHex, spanIdHex, traceFlags, traceState, remote, /* valid= */ true);
    }
    return createInternal(
        TraceId.getInvalid(),
        SpanId.getInvalid(),
        traceFlags,
        traceState,
        remote,
        /* valid= */ false);
  }

  @Override
  public abstract boolean isValid();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SpanBuilder&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;span은 tracer에서 호출하는 SpanBuilder에서 만들어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌더가 생성되면 위에서 살펴본 Tracer관련 설정 파라미터 4개를 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래서 부터는 Span에서 새롭게 등장하는 값들에 대해 알아보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750774385631&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SdkSpanBuilder implements SpanBuilder {

  private final String spanName;
  private final InstrumentationScopeInfo instrumentationScopeInfo;
  private final TracerSharedState tracerSharedState;
  private final SpanLimits spanLimits;

  @Nullable private Context parent;
  private SpanKind spanKind = SpanKind.INTERNAL;
  @Nullable private AttributesMap attributes;
  @Nullable private List&amp;lt;LinkData&amp;gt; links;
  private int totalNumberOfLinksAdded = 0;
  private long startEpochNanos = 0;

  SdkSpanBuilder(
      String spanName,
      InstrumentationScopeInfo instrumentationScopeInfo,
      TracerSharedState tracerSharedState,
      SpanLimits spanLimits) {
    this.spanName = spanName;
    this.instrumentationScopeInfo = instrumentationScopeInfo;
    this.tracerSharedState = tracerSharedState;
    this.spanLimits = spanLimits;
  }

  ...
 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Attributes&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Span의 전반적인 정보를 key/value로 보관하는 클래스입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를들어 HTTP라면 method = GET, status_code = 200 DB쿼리 span이라면 system = mysql, statement = SELECT 1 FROM DUAL 등 우리가 span하면 떠오르는 값들을 저장합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 여기에 넣어둔 Attribute 값들을 클라이언트나 샘플러에서 쿼리, 필터링하여 이용하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750774931820&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class SdkSpanBuilder implements SpanBuilder {
  ...

  private AttributesMap attributes() {
    AttributesMap attributes = this.attributes;
    if (attributes == null) {
      this.attributes =
          AttributesMap.create(
              spanLimits.getMaxNumberOfAttributes(), spanLimits.getMaxAttributeValueLength());
      attributes = this.attributes;
    }
    return attributes;
  }

 ... 
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Parent , Span-Link와 LinkData&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parent는 Span간의 부모-자식 관계, Span-Link는 일종의 원인-결과관계를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Parent는 Span이 생성될때 부모의 spanContext를 가져와 세팅하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Span-Link는 Fan-Out패턴 처럼 부모는 따로 있어도 지금 Span과 관련된 다른 Span들을 참조하고 싶을 때 사용합니다.&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;OTel에서 Parent는 spanContext 객체를 사용해 표현하고 Link는 LinkData라는 객체를 사용해서 Span-Link를 구현합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LinkData는 SpanContext, Attribute 객체를 가지고 있으며, Span에서 LinkData의 배열을 가짐으로서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1대1이 아닌 1대N관계도&lt;span&gt; &lt;/span&gt;&lt;/span&gt;표현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750856667057&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AutoValue
@Immutable
abstract class ImmutableLinkData implements LinkData {
  private static final Attributes DEFAULT_ATTRIBUTE_COLLECTION = Attributes.empty();
  private static final int DEFAULT_ATTRIBUTE_COUNT = 0;

  static LinkData create(SpanContext spanContext) {
    return new AutoValue_ImmutableLinkData(
        spanContext, DEFAULT_ATTRIBUTE_COLLECTION, DEFAULT_ATTRIBUTE_COUNT);
  }

  static LinkData create(SpanContext spanContext, Attributes attributes) {
    return new AutoValue_ImmutableLinkData(spanContext, attributes, attributes.size());
  }

  static LinkData create(SpanContext spanContext, Attributes attributes, int totalAttributeCount) {
    return new AutoValue_ImmutableLinkData(spanContext, attributes, totalAttributeCount);
  }

  ImmutableLinkData() {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Link 세팅&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spanBuilder에서 필요에 따라 SpanContext와 Attribute를 추가 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751097066320&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SdkSpanBuilder implements SpanBuilder {
  ...
  
  @Override
  public SpanBuilder addLink(SpanContext spanContext) {
    if (spanContext == null || !spanContext.isValid()) {
      return this;
    }
    addLink(LinkData.create(spanContext));
    return this;
  }

  @Override
  public SpanBuilder addLink(SpanContext spanContext, Attributes attributes) {
    if (spanContext == null || !spanContext.isValid()) {
      return this;
    }
    if (attributes == null) {
      attributes = Attributes.empty();
    }
    int totalAttributeCount = attributes.size();
    addLink(
        LinkData.create(
            spanContext,
            AttributeUtil.applyAttributesLimit(
                attributes,
                spanLimits.getMaxNumberOfAttributesPerLink(),
                spanLimits.getMaxAttributeValueLength()),
            totalAttributeCount));
    return this;
  }

  private void addLink(LinkData link) {
    totalNumberOfLinksAdded++;
    if (links == null) {
      links = new ArrayList&amp;lt;&amp;gt;(spanLimits.getMaxNumberOfLinks());
    }

    // don't bother doing anything with any links beyond the max.
    if (links.size() == spanLimits.getMaxNumberOfLinks()) {
      return;
    }

    links.add(link);
  }
 ... 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Patent 세팅&lt;/h4&gt;
&lt;p data-end=&quot;47026&quot; data-start=&quot;46641&quot; data-ke-size=&quot;size16&quot;&gt;tracer에서 span을 만드는 OTel의 예시 코드를 보게되면 Context 객체를 Link에 넣어줍니다.&lt;/p&gt;
&lt;p data-end=&quot;47026&quot; data-start=&quot;46641&quot; data-ke-size=&quot;size16&quot;&gt;Span-Link는 부모의 SpanContext를&lt;span&gt;&amp;nbsp;&lt;/span&gt;세팅하지만, Link와 달리 Parent가 필요로 하는 Context는 SpanContext가 아닌 다른 Context, Context.java의 객체입니다.&lt;/p&gt;
&lt;p data-end=&quot;47026&quot; data-start=&quot;46641&quot; data-ke-size=&quot;size16&quot;&gt;Context객체는 ContextKey&amp;lt;V&amp;gt; 와 같은 형태로 구현되어있어 다양한 값을 저장 할 수 있는 불변객체이자 저장소입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750777262416&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Span span = tracer.spanBuilder(&quot;sb&quot;)
    .setParent(Context.current())  // 현재 컨텍스트를 부모로 설정
    .startSpan();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;47026&quot; data-start=&quot;46641&quot; data-ke-size=&quot;size16&quot;&gt;Context.current() 메소드를 살펴보면 ContextStorage라는 저장소에서 Context객체를 꺼내오는걸 확인 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751094560993&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Context {

 ...
 
  /** Return the context associated with the current {@link Scope}. */
  static Context current() {
    Context current = ContextStorage.get().current();
    return current != null ? current : root();
  }
  
  ...
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;47026&quot; data-start=&quot;46641&quot; data-ke-size=&quot;size23&quot;&gt;ContextStorage&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum 싱글톤 방식을 사용하여 Context를 자바의 ThreadLocal에 보관하는 Stoarge입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ThreadLocal에 보관하여 Context가 스레드별 전역참조가 가능하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Context.current()메소드를 사용하면 THREAD_LOCAL_STORAGE에 보관되어 있는 Context객체를 리턴합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Context객체는 주로 span객체 자체를 저장하는데 사용됩니다.&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;사실 Context 객체, Context.java는 프로젝트에서 interface 파일인데 구현체가 프로젝트에 없는 것 같아서,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링 + 공식문서 + GPT로 찾아봤더니&lt;span&gt; Context는 immutable이고, 대부분 내부 구현체를 사용한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 정도로만 설명이 되어있어서 흠,, Context구현체에 대해서는 조금 더 찾아봐야 할 것 같습니다 ㅠㅠ&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1750777498850&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum ThreadLocalContextStorage implements ContextStorage {
  INSTANCE;

  private static final Logger logger = Logger.getLogger(ThreadLocalContextStorage.class.getName());

  private static final ThreadLocal&amp;lt;Context&amp;gt; THREAD_LOCAL_STORAGE = new ThreadLocal&amp;lt;&amp;gt;();

  @Override
  public Scope attach(Context toAttach) {
    if (toAttach == null) {
      // Null context not allowed so ignore it.
      return NoopScope.INSTANCE;
    }

    Context beforeAttach = current();
    if (toAttach == beforeAttach) {
      return NoopScope.INSTANCE;
    }

    THREAD_LOCAL_STORAGE.set(toAttach);

    return new ScopeImpl(beforeAttach, toAttach);
  }

  private class ScopeImpl implements Scope {
    @Nullable private final Context beforeAttach;
    private final Context toAttach;
    private boolean closed;

    private ScopeImpl(@Nullable Context beforeAttach, Context toAttach) {
      this.beforeAttach = beforeAttach;
      this.toAttach = toAttach;
    }

    @Override
    public void close() {
      if (!closed &amp;amp;&amp;amp; current() == toAttach) {
        closed = true;
        THREAD_LOCAL_STORAGE.set(beforeAttach);
      } else {
        logger.log(
            Level.FINE,
            &quot; Trying to close scope which does not represent current context. Ignoring the call.&quot;);
      }
    }
  }

  @Override
  @Nullable
  public Context current() {
    return THREAD_LOCAL_STORAGE.get();
  }

  enum NoopScope implements Scope {
    INSTANCE;

    @Override
    public void close() {}
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;46641&quot; data-end=&quot;47026&quot;&gt;Context와 Scope&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scope는 이전 Context를 기억하고 있는 객체입니다.&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;현재 Span을 Context에 값을 저장하기 위해서는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Span이 상속받는 인터페이스 중 하나인 ImplicitContextKeyed의 makeCurrent()메소드를 통해 현재 span을 Context에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 this는 Span입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751095528355&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ImplicitContextKeyed {
  default Scope makeCurrent() {
    return Context.current().with(this).makeCurrent();
  }
}&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;makeCurrent메소드가 종료되면 Scope가 반환되는데 Scoep가 close되기 전 까지는 지금 Span이 현재 Thread에서 Current상태가 됩니다. close되면 다시 이전 상태의 Context가 Current가 됩니다.&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;pre id=&quot;code_1751093586489&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 현재 Context를 부모로 설정 하여 span생성
Span span = tracer.spanBuilder(&quot;sb&quot;)
    .setParent(Context.current()) 
    .startSpan();
    
// 해당 span을 Context에 등록
try (Scope scope = span.makeCurrent()) {
  
  ...
  
} finally {
    span.end(); // 이전 Context로 돌아감
}&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; text-align: start;&quot;&gt;하지만 일반적으로 Context들은 ThreadLocalStoarge에 저장되기에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;만약 trace가 다른 스레드로 넘어가거나, http요청으로 인해 다른 서비스로 가야할때는 Context를 wrapp해서 전달하는 등 다른 방식이 필요합니다.&lt;/p&gt;
&lt;h2 data-end=&quot;47050&quot; data-start=&quot;47028&quot; data-ke-size=&quot;size26&quot;&gt;Span Status ((스팬 상태))&lt;/h2&gt;
&lt;pre id=&quot;code_1751098752574&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum StatusCode {
  UNSET,
  OK,
  ERROR
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값은 UNSET이고 오류 없음을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 별도로 status가 지정되지 않은 스팬은 오류없음, 즉 성공 스팬으로 취급합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel에서도 오류가 있을때만 ERROR로 사용하도록 권고합니다.&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;span에서 status의 우선순위는 OK, ERROR, UNSET 이며 이미 OK상태가 된 span은 ERROR로 변경해도 무시됩니다.&lt;/p&gt;
&lt;h2 data-end=&quot;52166&quot; data-start=&quot;52100&quot; data-ke-size=&quot;size26&quot;&gt;Span Kind&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;SpanKind는 5가지 종류 Client, Server, Internal, Producer, Consumer가 있으며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Span이 어디서 실행되는지 구분하는 역할을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751099436042&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum SpanKind {
  INTERNAL,
  SERVER,
  CLIENT,
  PRODUCER,
  CONSUMER
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 SpanKind는 Internal입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시징 아키텍쳐에서 사용하기 위한 producer, consumer 타입도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel에서 HTTP모듈, 데이터베이스를 향한 호출은 CLIENT로 처리한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 RPC의 양 끝점은 CLIENT, SERVER로 처리한다고 합니다.&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;대신 SpanKind를 세팅 할 때 한가지 주의 사항이 있는데 하나의 스팬은 하나의 역할만 해야한다는 것 입니다.&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;이런 경우에는 클라이언트 스팬을 따로 만들어 httpCall을 해야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751101281810&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Span span = tracer.spanBuilder(&quot;processRequest&quot;)
  .setSpanKind(SpanKind.SERVER)  // 서버 스팬
  .startSpan();

// 이 안에서 외부로 HTTP 요청을 날림
makeHttpCall(); // CLIENT 역할도 동시에 수행하는 코드

span.end();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;35570&quot; data-start=&quot;35547&quot; data-ke-size=&quot;size26&quot;&gt;Span Events&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;span의 타임라인에서 발생한 시점의 이벤트를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트는 이름, 타임스탬프, Attribute를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;span의 로그정도라고 생각하면 될 것 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751101739676&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Span span = tracer.spanBuilder(&quot;processOrder&quot;)
  .startSpan();

try (Scope scope = span.makeCurrent()) {
  // 이런식으로 로그처럼 사용합니다.
  span.addEvent(&quot;order-fetched-from-db&quot;);
  processBusinessLogic();
  span.addEvent(&quot;order-validated&quot;);
  callExternalPaymentService();
  span.addEvent(&quot;payment-completed&quot;);
} finally {
  span.end();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Span Attribute(속성)와 Span Event(이벤트) 의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Span에는 이미 Event와 비슷한 역할을 할 수 있는 Attribute가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두개의 쓰임새는 상황에 따라 조금 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;간단하게 이야기하면 특정 타이밍이 중요한 정보라면 Event, 시간과 무관한 정보라면 Attribute에 넣는게 권장됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떤 일이 발생한 순간&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;자체가 중요한 경우 이벤트를 사용하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;어떤 상태나 결과를 나타내는 정보로서 시점이 중요하지 않다면 속성이 적절합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel에서 Attribute와 Event를 구분하는 몇가지 기준이 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;41622&quot; data-start=&quot;40408&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;40662&quot; data-start=&quot;40408&quot;&gt;시점이 의미 있는가? : Y 이벤트 , N : 속성&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li data-end=&quot;40838&quot; data-start=&quot;40663&quot;&gt;값이 변하는가? : Y : 이벤트, N : 속성&lt;/li&gt;
&lt;li data-end=&quot;41011&quot; data-start=&quot;40839&quot;&gt;발생 기록: 이벤트, 단순 설명 : 속성&lt;/li&gt;
&lt;li data-end=&quot;41289&quot; data-start=&quot;41012&quot;&gt;로그성 : 이벤트,&amp;nbsp; 대표값: 속성&amp;nbsp;&lt;/li&gt;
&lt;li data-end=&quot;41622&quot; data-start=&quot;41290&quot;&gt;순간: 이벤트, 기간 : 속성&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-end=&quot;1516&quot; data-start=&quot;1499&quot; data-ke-size=&quot;size23&quot;&gt;startSpan() ((SdkSpan에서 startSpan()코드도 살펴보기))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 span의 속성들을 알아봤으니, span을 시작하는 startSpan 메소드를 살펴보려합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751105784534&quot; class=&quot;groovy&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class SdkSpanBuilder implements SpanBuilder {
  ...
  

  @Override
  @SuppressWarnings({&quot;unchecked&quot;, &quot;rawtypes&quot;})
  public Span startSpan() {
    Context parentContext = parent == null ? Context.current() : parent;
    Span parentSpan = Span.fromContext(parentContext);
    SpanContext parentSpanContext = parentSpan.getSpanContext();
    String traceId;
    IdGenerator idGenerator = tracerSharedState.getIdGenerator();
    String spanId = idGenerator.generateSpanId();
    if (!parentSpanContext.isValid()) {
      // New root span.
      traceId = idGenerator.generateTraceId();
    } else {
      // New child span.
      traceId = parentSpanContext.getTraceId();
    }
    List&amp;lt;LinkData&amp;gt; currentLinks = links;
    List&amp;lt;LinkData&amp;gt; immutableLinks =
        currentLinks == null ? Collections.emptyList() : Collections.unmodifiableList(currentLinks);
    // Avoid any possibility to modify the links list by adding links to the Builder after the
    // startSpan is called. If that happens all the links will be added in a new list.
    links = null;
    Attributes immutableAttributes = attributes == null ? Attributes.empty() : attributes;
    SamplingResult samplingResult =
        tracerSharedState
            .getSampler()
            .shouldSample(
                parentContext, traceId, spanName, spanKind, immutableAttributes, immutableLinks);
    SamplingDecision samplingDecision = samplingResult.getDecision();

    TraceState samplingResultTraceState =
        samplingResult.getUpdatedTraceState(parentSpanContext.getTraceState());
    SpanContext spanContext =
        ImmutableSpanContext.create(
            traceId,
            spanId,
            isSampled(samplingDecision) ? TraceFlags.getSampled() : TraceFlags.getDefault(),
            samplingResultTraceState,
            /* remote= */ false,
            tracerSharedState.isIdGeneratorSafeToSkipIdValidation());

    if (!isRecording(samplingDecision)) {
      return Span.wrap(spanContext);
    }
    Attributes samplingAttributes = samplingResult.getAttributes();
    if (!samplingAttributes.isEmpty()) {
      samplingAttributes.forEach((key, value) -&amp;gt; attributes().put((AttributeKey) key, value));
    }

    // Avoid any possibility to modify the attributes by adding attributes to the Builder after the
    // startSpan is called. If that happens all the attributes will be added in a new map.
    AttributesMap recordedAttributes = attributes;
    attributes = null;

    return SdkSpan.startSpan(
        spanContext,
        spanName,
        instrumentationScopeInfo,
        spanKind,
        parentSpan,
        parentContext,
        spanLimits,
        tracerSharedState.getActiveSpanProcessor(),
        tracerSharedState.getExceptionAttributesResolver(),
        tracerSharedState.getClock(),
        tracerSharedState.getResource(),
        recordedAttributes,
        currentLinks,
        totalNumberOfLinksAdded,
        startEpochNanos);
  }
  }
  ... 
}&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;p data-ke-size=&quot;size16&quot;&gt;1.&lt;b&gt;부모 Context 확인&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모의 context를 가져와서 세팅합니다. 없다면 Current.Context를 호출해서 Context를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. SpanId / TraceId 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 있다면 TraceId를 그대로 사용하고 없다면 IdGenerator를 사용해 각 ID를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 샘플링(Sampling)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플링 방식을 지정합니다. 샘플링에 대해서는 별도 포스팅으로 다루려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. SpanContext 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpanContext를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5.기록 여부 판단 &amp;rarr; SdkSpan 생성 or NoopSpan 리턴&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recording 여부를 판단합니다. 이 부분도 샘플링에서 같이 다루려고 합니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;배기지 ((Baggage))&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배기지는 컨텍스트에 정의되는 key-value 정보입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Context 인터페이스의 with 메소드로 Baggage를 Context에 저장 할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Baggage에 자체 메소드로도 Context에 추가 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자ID나 테넌트ID같은 값을 Baggage에 넣어두면, 해당 추적의 Trace를 따라가는 모든 스팬에서도 Baggage를 참조 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751204273430&quot; class=&quot;mipsasm&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;  &amp;lt;V&amp;gt; Context with(ContextKey&amp;lt;V&amp;gt; k1, V v1);&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;Baggage는 http모듈을 통해 외부 서비스로 전달될때는 W3CBaggagePropagator를 이용해 헤더에 값이 들어가서 전파됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 무분별하게 많은 데이터나 민감한 데이터를 넣는 것은 권장되지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751204926971&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;baggage: userId=alice,projectId=telemetry&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;메트릭 ((Metric))&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Metric은 기본적으로 Trace와 관계 없이 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭은 주기적으로 수집되는데 특정 조건((timeout 등))에 맞는 메트릭이 수집되면 해당 샘플에 메트릭 값을 추가하여 클라이언트에서 관측 가능하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;APM에서는 Metric은 Exporter를 통해 자체적인 클라이언트에 보낼 수도 있고, 아니면 프로메테우스같은 메트릭 수집기에 전달 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cpu와 메모리는 비동기 측정 방식을 사용하고 Count와 같은 메트릭은 동기적 측정 방식을 사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Meter&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LongCounter : 카운터, 누적치 계산&lt;/li&gt;
&lt;li&gt;LongUpDownCounter : 업다운 카운터, 증가 감소 둘다 있음&lt;/li&gt;
&lt;li&gt;DoubleHistogram : 히스토그램, 분포를 보고 싶은 값 기록용&lt;/li&gt;
&lt;li&gt;DoubleGauge : 게이지, 현재 값 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1755005878012&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package io.opentelemetry.api.metrics;
import javax.annotation.concurrent.ThreadSafe;


@ThreadSafe
public interface Meter {

  LongCounterBuilder counterBuilder(String name);

  LongUpDownCounterBuilder upDownCounterBuilder(String name);

  DoubleHistogramBuilder histogramBuilder(String name);

  DoubleGaugeBuilder gaugeBuilder(String name);

  default BatchCallback batchCallback(
      Runnable callback,
      ObservableMeasurement observableMeasurement,
      ObservableMeasurement... additionalMeasurements) {
    return DefaultMeter.getInstance()
        .batchCallback(callback, observableMeasurement, additionalMeasurements);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LongCounter&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계측기가 여러개 있지만, 그 중 LongCounter의 구현체 하나만 알아보려고 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755008920737&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SdkLongCounter extends AbstractInstrument implements LongCounter {

  private final ThrottlingLogger throttlingLogger = new ThrottlingLogger(logger);
  final SdkMeter sdkMeter;
  final WriteableMetricStorage storage;

  SdkLongCounter(
      InstrumentDescriptor descriptor, SdkMeter sdkMeter, WriteableMetricStorage storage) {
    super(descriptor);
    this.sdkMeter = sdkMeter;
    this.storage = storage;
  }
  
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WriteableMetricStorage, sdkMeter, InstrumentDescriptor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WriteableMetricStorage는 들어온 메트릭을 Attributes와 Context를 기준으로 모아두는 storage입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동기 비동기 각 상황에 맞게 다른 구현체가 존재하고 있습니다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Context를 파라미터로 받기에 이 값을 통해 추적에 메트릭을 추가 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SdkMeter는 Meter의 구현체로 각 메트릭 수집기들이 등록되며 storage들과 비동기에 사용될 콜백함수도 등록됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;InstrumentDescriptor 메타데이터를 담는 객체입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755010643252&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface WriteableMetricStorage {

  /** Records a measurement. */
  void recordLong(long value, Attributes attributes, Context context);

  /** Records a measurement. */
  void recordDouble(double value, Attributes attributes, Context context);

  /**
   * Returns {@code true} if the storage is actively recording measurements, and {@code false}
   * otherwise (i.e. noop / empty metric storage is installed).
   */
  boolean isEnabled();
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;add()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context와 Attributes를 key로 사용해서 값을 올리는 간단한 메소드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755008932238&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ...
  
  @Override
  public void add(long increment, Attributes attributes, Context context) {
    if (increment &amp;lt; 0) {
      throttlingLogger.log(
          Level.WARNING,
          &quot;Counters can only increase. Instrument &quot;
              + getDescriptor().getName()
              + &quot; has recorded a negative value.&quot;);
      return;
    }
    storage.recordLong(increment, attributes, context);
  }
  
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SdkLongCounterBuilder&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 내부에 static class로 빌더가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 buildWithCallback 함수를 빌더에 추가하면 비동기 측정 방식으로 사용 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동기로 측정하면 런타임에 add() 가 호출되고 비동기로 사용하면 수집 주기마다 콜백을 호출하는 방식으로 측정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755008975139&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   ...

static class SdkLongCounterBuilder implements LongCounterBuilder { 

    @Override
    public LongCounterBuilder setDescription(String description) {
      builder.setDescription(description);
      return this;
    }

    @Override
    public LongCounterBuilder setUnit(String unit) {
      builder.setUnit(unit);
      return this;
    }

    @Override
    public SdkLongCounter build() {
      return builder.buildSynchronousInstrument(SdkLongCounter::new);
    }

    @Override
    public DoubleCounterBuilder ofDoubles() {
      return builder.swapBuilder(SdkDoubleCounter.SdkDoubleCounterBuilder::new);
    }

    @Override
    public ObservableLongCounter buildWithCallback(Consumer&amp;lt;ObservableLongMeasurement&amp;gt; callback) {
      return builder.buildLongAsynchronousInstrument(InstrumentType.OBSERVABLE_COUNTER, callback);
    }

    @Override
    public ObservableLongMeasurement buildObserver() {
      return builder.buildObservableMeasurement(InstrumentType.OBSERVABLE_COUNTER);
    }

    @Override
    public String toString() {
      return builder.toStringHelper(getClass().getSimpleName());
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;714&quot; data-end=&quot;745&quot;&gt;PeriodicMetricReader&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정한 주기마다 Metric을 읽어서 Exporter로 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자에서 Expoter와 Interval, Scheduler를 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;doRun() 메소드를 사용해 메트릭 수집과 추출을 진행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755175996605&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class PeriodicMetricReader implements MetricReader {
  private static final Logger logger = Logger.getLogger(PeriodicMetricReader.class.getName());

  private final MetricExporter exporter;
  private final long intervalNanos;
  private final ScheduledExecutorService scheduler;
  private final Scheduled scheduled;
  private final Object lock = new Object();
  private volatile CollectionRegistration collectionRegistration = CollectionRegistration.noop();

  @Nullable private volatile ScheduledFuture&amp;lt;?&amp;gt; scheduledFuture;

  public static PeriodicMetricReader create(MetricExporter exporter) {
    return builder(exporter).build();
  }

  public static PeriodicMetricReaderBuilder builder(MetricExporter exporter) {
    return new PeriodicMetricReaderBuilder(exporter);
  }

  PeriodicMetricReader(
      MetricExporter exporter, long intervalNanos, ScheduledExecutorService scheduler) {
    this.exporter = exporter;
    this.intervalNanos = intervalNanos;
    this.scheduler = scheduler;
    this.scheduled = new Scheduled();
  }


  ... 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Flush, Shutdown, Register&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Register는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Provider가 storage에 저장되어 있던 메트릭을 가져와서 Exporter에 넘기는 작업, 즉 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;forceFlush 메소드는 지금 시점에 강제로 수집과 추출을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 스케줄러의 doRun을 실행시키고, Exporter.flush()를 실행시켜서 즉시 추출을 진행합니다.&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;shutdown()은 주기 스케줄을 취소하여 수집을 중지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄러를 종료 대기 상태로 바꾸고 이후 doRun을 실행시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 exporter.shutdown()을 호출합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755176635977&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class PeriodicMetricReader implements MetricReader {

  ...

  @Override
  public CompletableResultCode forceFlush() {
    CompletableResultCode result = new CompletableResultCode();
    CompletableResultCode doRunResult = scheduled.doRun();
    doRunResult.whenComplete(
        () -&amp;gt; {
          CompletableResultCode flushResult = exporter.flush();
          flushResult.whenComplete(
              () -&amp;gt; {
                if (doRunResult.isSuccess() &amp;amp;&amp;amp; flushResult.isSuccess()) {
                  result.succeed();
                } else {
                  result.fail();
                }
              });
        });
    return result;
  }

  @Override
  public CompletableResultCode shutdown() {
    CompletableResultCode result = new CompletableResultCode();
    ScheduledFuture&amp;lt;?&amp;gt; scheduledFuture = this.scheduledFuture;
    if (scheduledFuture != null) {
      scheduledFuture.cancel(false);
    }
    scheduler.shutdown();
    try {
      scheduler.awaitTermination(5, TimeUnit.SECONDS);
      CompletableResultCode flushResult = scheduled.doRun();
      flushResult.join(5, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      // force a shutdown if the export hasn't finished.
      scheduler.shutdownNow();
      // reset the interrupted status
      Thread.currentThread().interrupt();
    } finally {
      CompletableResultCode shutdownResult = scheduled.shutdown();
      shutdownResult.whenComplete(
          () -&amp;gt; {
            if (!shutdownResult.isSuccess()) {
              result.fail();
            } else {
              result.succeed();
            }
          });
    }
    return result;
  }

  @Override
  public void register(CollectionRegistration collectionRegistration) {
    this.collectionRegistration = collectionRegistration;
    start();
  }
  
  void start() {
    synchronized (lock) {
      if (scheduledFuture != null) {
        return;
      }
      scheduledFuture =
          scheduler.scheduleAtFixedRate(
              scheduled, intervalNanos, intervalNanos, TimeUnit.NANOSECONDS);
    }
  }

  ... 
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-end=&quot;943&quot; data-start=&quot;880&quot; data-ke-size=&quot;size26&quot;&gt;스케줄러&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄러는 틱 마다 run()을 호출되고 내부에서 doRun()을 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ScheduledExecutorService 라는 타이머가 있는 스레드 풀을 사용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scheduleAtFixedRate만큼의 간격마다 doRun을 실행합니다.&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;doRun 메소드를 살펴보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exportAvailable값을 읽어 false면, 즉 아직 export되지 않은 사이클이 있다면, 이번 사이클을 drop합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Reader는 Exporter와 독립적인 관계이고 만약&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Exporter가 느려지는 등 이슈가 발생은 Reader에서 알 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그렇기에 추가적인 부하를 줄 수 있는 상황이라고 판단되면 drop을 진행합니다.&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; text-align: start;&quot;&gt;추출이 가능하다면, Storage에서 모든 메트릭을 가져와 Exporter에 전송합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그 결과는 비동기 콜백으로 전달받습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이후 exportAvailable값을 true로 변경합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1755176955140&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; private final class Scheduled implements Runnable {
    private final AtomicBoolean exportAvailable = new AtomicBoolean(true);

    private Scheduled() {}

    @Override
    public void run() {
      // Ignore the CompletableResultCode from doRun() in order to keep run() asynchronous
      doRun();
    }

    // Runs a collect + export cycle.
    CompletableResultCode doRun() {
      CompletableResultCode flushResult = new CompletableResultCode();
      if (exportAvailable.compareAndSet(true, false)) {
        try {
          Collection&amp;lt;MetricData&amp;gt; metricData = collectionRegistration.collectAllMetrics();
          if (metricData.isEmpty()) {
            logger.log(Level.FINE, &quot;No metric data to export - skipping export.&quot;);
            flushResult.succeed();
            exportAvailable.set(true);
          } else {
            CompletableResultCode result = exporter.export(metricData);
            result.whenComplete(
                () -&amp;gt; {
                  if (!result.isSuccess()) {
                    logger.log(Level.FINE, &quot;Exporter failed&quot;);
                  }
                  flushResult.succeed();
                  exportAvailable.set(true);
                });
          }
        } catch (Throwable t) {
          exportAvailable.set(true);
          logger.log(Level.WARNING, &quot;Exporter threw an Exception&quot;, t);
          flushResult.fail();
        }
      } else {
        logger.log(Level.FINE, &quot;Exporter busy. Dropping metrics.&quot;);
        flushResult.fail();
      }
      return flushResult;
    }

    CompletableResultCode shutdown() {
      return exporter.shutdown();
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;880&quot; data-end=&quot;943&quot;&gt;OtlpHTTPMetricExporter&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reader가 넘긴 데이터를 OTLP포맷으로 변환해 Collector에 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collector에서 다시 Prometheous, Datadog으로 전송 가능합니다.&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;MetricReusableDataMarshaler를 사용해&amp;nbsp;데이터를 OTLP 바이트로 마샬링 후 GrpcExporter를 사용해Http, gRPC등 적절한 채널로 실어 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AggregationTemporalitySelector를 통해 CUMULATIVE, DELTA 방식을 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUMULATIVE 방식은 총합을 유지하는 방식이고 DELTA 방식은 직전 값과 이전값의 차이만 보내는 방식입니다.&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;DefaultAggregationSelector로 계측기의 타입별 기본 집계 방식을 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마샬링 시 메모리를 REUSEABLE, 재사용해서 G.C의 부하를 줄이지만 데이터가 덮어 쓰거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IMMUTABLE, 메모리를 바꾸지 않아 안전하지만 G.C의 부하를 조금 줄지 결정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755181047991&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ThreadSafe
public final class OtlpHttpMetricExporter implements MetricExporter {

  private final HttpExporterBuilder&amp;lt;Marshaler&amp;gt; builder;
  private final HttpExporter&amp;lt;Marshaler&amp;gt; delegate;
  // Visible for testing
  final AggregationTemporalitySelector aggregationTemporalitySelector;
  // Visible for testing
  final DefaultAggregationSelector defaultAggregationSelector;
  private final MetricReusableDataMarshaler marshaler;

  OtlpHttpMetricExporter(
      HttpExporterBuilder&amp;lt;Marshaler&amp;gt; builder,
      HttpExporter&amp;lt;Marshaler&amp;gt; delegate,
      AggregationTemporalitySelector aggregationTemporalitySelector,
      DefaultAggregationSelector defaultAggregationSelector,
      MemoryMode memoryMode) {
    this.builder = builder;
    this.delegate = delegate;
    this.aggregationTemporalitySelector = aggregationTemporalitySelector;
    this.defaultAggregationSelector = defaultAggregationSelector;
    this.marshaler = new MetricReusableDataMarshaler(memoryMode, delegate::export);
  }

  /**
   * Returns a new {@link OtlpHttpMetricExporter} using the default values.
   *
   * &amp;lt;p&amp;gt;To load configuration values from environment variables and system properties, use &amp;lt;a
   * href=&quot;https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure&quot;&amp;gt;opentelemetry-sdk-extension-autoconfigure&amp;lt;/a&amp;gt;.
   *
   * @return a new {@link OtlpHttpMetricExporter} instance.
   */
  public static OtlpHttpMetricExporter getDefault() {
    return builder().build();
  }

  /**
   * Returns a new builder instance for this exporter.
   *
   * @return a new builder instance for this exporter.
   */
  public static OtlpHttpMetricExporterBuilder builder() {
    return new OtlpHttpMetricExporterBuilder();
  }

  /**
   * Returns a builder with configuration values equal to those for this exporter.
   *
   * &amp;lt;p&amp;gt;IMPORTANT: Be sure to {@link #shutdown()} this instance if it will no longer be used.
   *
   * @since 1.29.0
   */
  public OtlpHttpMetricExporterBuilder toBuilder() {
    return new OtlpHttpMetricExporterBuilder(
        builder.copy(),
        aggregationTemporalitySelector,
        defaultAggregationSelector,
        marshaler.getMemoryMode());
  }

  @Override
  public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
    return aggregationTemporalitySelector.getAggregationTemporality(instrumentType);
  }

  @Override
  public Aggregation getDefaultAggregation(InstrumentType instrumentType) {
    return defaultAggregationSelector.getDefaultAggregation(instrumentType);
  }

  @Override
  public MemoryMode getMemoryMode() {
    return marshaler.getMemoryMode();
  }

  ...
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;943&quot; data-start=&quot;880&quot; data-ke-size=&quot;size26&quot;&gt;Exporter의 주요 메소드들&lt;/h2&gt;
&lt;h3 data-end=&quot;652&quot; data-start=&quot;608&quot; data-ke-size=&quot;size23&quot;&gt;export((Collection&amp;lt;MetricData&amp;gt; metrics))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 사이클에 있는 배치를 Marshaler가 직렬화하고 즉시 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리턴값은 CompletableResultCode, 비동기로 전달 받습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755264210259&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Override
  public CompletableResultCode export(Collection&amp;lt;MetricData&amp;gt; metrics) {
    return marshaler.export(metrics);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;834&quot; data-start=&quot;821&quot; data-ke-size=&quot;size23&quot;&gt;flush()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC같은 경우는 내부적으로 버퍼를 둬서 값을 보내는데 위와 같은 경우, 버퍼에&amp;nbsp;남아있는 데이터를 모두 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 버퍼를 사용하지 않기에 즉시 리턴값을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리턴값은 동기로 받습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755264270807&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Override
  public CompletableResultCode flush() {
    return CompletableResultCode.ofSuccess();
  }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;912&quot; data-start=&quot;896&quot; data-ke-size=&quot;size23&quot;&gt;shutdown()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 누수 등을 방지하기 위해 사용하던 전송 채널, 스레드, 버퍼 등을 닫습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755264641736&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Override
  public CompletableResultCode shutdown() {
    return delegate.shutdown();
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;로그 ((Log))&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel의 레코드는 일반적으로 생각하는 서비스의 printf 와 같은 문자열 로그가 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 이벤트 값입니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;LogRecordBuilder&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그의 값을 담고, emit() 메소드를 통해 로그를 Collector로 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그에는 로그 발생 시각, 관측 시각, 심각도, 본문 메시지, TraceId, SpanId, Flag, 환경 정보, 이벤트 명과 같은 값들이 들어갑니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755353943124&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** SDK implementation of {@link LogRecordBuilder}. */
class SdkLogRecordBuilder implements LogRecordBuilder {

  protected final LoggerSharedState loggerSharedState;
  protected final LogLimits logLimits;

  protected final InstrumentationScopeInfo instrumentationScopeInfo;
  protected long timestampEpochNanos;
  protected long observedTimestampEpochNanos;
  @Nullable protected Context context;
  protected Severity severity = Severity.UNDEFINED_SEVERITY_NUMBER;
  @Nullable protected String severityText;
  @Nullable protected Value&amp;lt;?&amp;gt; body;
  @Nullable protected String eventName;
  @Nullable private AttributesMap attributes;

  SdkLogRecordBuilder(
      LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) {
    this.loggerSharedState = loggerSharedState;
    this.logLimits = loggerSharedState.getLogLimits();
    this.instrumentationScopeInfo = instrumentationScopeInfo;
  }

  @Override
  public SdkLogRecordBuilder setEventName(String eventName) {
    this.eventName = eventName;
    return this;
  }

  @Override
  public SdkLogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) {
    this.timestampEpochNanos = unit.toNanos(timestamp);
    return this;
  }

  @Override
  public SdkLogRecordBuilder setTimestamp(Instant instant) {
    this.timestampEpochNanos =
        TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano();
    return this;
  }

  @Override
  public LogRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit) {
    this.observedTimestampEpochNanos = unit.toNanos(timestamp);
    return this;
  }

  @Override
  public LogRecordBuilder setObservedTimestamp(Instant instant) {
    this.observedTimestampEpochNanos =
        TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano();
    return this;
  }

  @Override
  public SdkLogRecordBuilder setContext(Context context) {
    this.context = context;
    return this;
  }

  @Override
  public SdkLogRecordBuilder setSeverity(Severity severity) {
    this.severity = severity;
    return this;
  }

  @Override
  public SdkLogRecordBuilder setSeverityText(String severityText) {
    this.severityText = severityText;
    return this;
  }

  @Override
  public SdkLogRecordBuilder setBody(String body) {
    return setBody(Value.of(body));
  }

  @Override
  public SdkLogRecordBuilder setBody(Value&amp;lt;?&amp;gt; value) {
    this.body = value;
    return this;
  }

  @Override
  public &amp;lt;T&amp;gt; SdkLogRecordBuilder setAttribute(AttributeKey&amp;lt;T&amp;gt; key, @Nullable T value) {
    if (key == null || key.getKey().isEmpty() || value == null) {
      return this;
    }
    if (this.attributes == null) {
      this.attributes =
          AttributesMap.create(
              logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength());
    }
    this.attributes.put(key, value);
    return this;
  }
  
  ...
 
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로그 json 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1755353904593&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;time_unix_nano&quot;: &quot;1723782150000000000&quot;,
  &quot;observed_time_unix_nano&quot;: &quot;1723782151000000000&quot;,
  &quot;severity_number&quot;: 17,
  &quot;severity_text&quot;: &quot;ERROR&quot;,
  &quot;body&quot;: {
    &quot;stringValue&quot;: &quot;Payment failed&quot;
  },
  &quot;attributes&quot;: [
    {
      &quot;key&quot;: &quot;order.id&quot;,
      &quot;value&quot;: { &quot;stringValue&quot;: &quot;912345&quot; }
    },
    {
      &quot;key&quot;: &quot;user.id&quot;,
      &quot;value&quot;: { &quot;intValue&quot;: &quot;1001&quot; }
    }
  ],
  &quot;trace_id&quot;: &quot;4fd0b9b76e4c9f6cb1f4431a6a68e5b2&quot;,
  &quot;span_id&quot;: &quot;6e0c63257de34c92&quot;,
  &quot;flags&quot;: 1,
  &quot;resource&quot;: {
    &quot;attributes&quot;: [
      {
        &quot;key&quot;: &quot;service.name&quot;,
        &quot;value&quot;: { &quot;stringValue&quot;: &quot;payment-service&quot; }
      },
      {
        &quot;key&quot;: &quot;service.version&quot;,
        &quot;value&quot;: { &quot;stringValue&quot;: &quot;1.2.3&quot; }
      }
    ]
  },
  &quot;instrumentation_scope&quot;: {
    &quot;name&quot;: &quot;io.opentelemetry.example.PaymentLogger&quot;,
    &quot;version&quot;: &quot;1.0.0&quot;,
    &quot;schemaUrl&quot;: &quot;https://opentelemetry.io/schemas/1.18.0&quot;
  },
  &quot;event_name&quot;: &quot;order.payment.failed&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;emit()&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들어진 로그를 Collector로 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정하지 않은 속성들은 emit()가 실행될 때 자동으로 채우고 LogRecord객체로 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Processor로 넘깁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755353670307&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void emit() {
  if (loggerSharedState.hasBeenShutdown()) { return; }

  Context ctx = (this.context == null) ? Context.current() : this.context;

  long observed = (this.observedTimestampEpochNanos == 0)
      ? loggerSharedState.getClock().now()
      : this.observedTimestampEpochNanos;

  loggerSharedState.getLogRecordProcessor().onEmit(
      ctx,
      SdkReadWriteLogRecord.create(
          loggerSharedState.getLogLimits(),
          loggerSharedState.getResource(),
          instrumentationScopeInfo,
          timestampEpochNanos,
          observed,
          Span.fromContext(ctx).getSpanContext(), // traceId/spanId 여기서 추출
          severity,
          severityText,
          body,
          attributes,
          eventName));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;LogRecordProcessor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogRecord를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;emit()을 통해 전달받습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이후&amp;nbsp;LogRecord들을 Exporter를 통해 Collector나 외부로 전송합니다.&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;Processor가 SimpleLogRecordProcessor라면 즉시 Exporter로 보내고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;BatchLogRecordProcessor라면&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;큐에 넣고 배치 단위로 Exporter로 전송합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Composite&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SimpleLogRecordProcessor, BatchLogRecordProcessor, 사용자 정의 Processor 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 LogRecordProcessor를 하나로 묶어주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 composite을 사용해서 SdkLoggerProvider를 만드는 예시코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755527560563&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SdkLoggerProvider loggerProvider =
    SdkLoggerProvider.builder()
        .addLogRecordProcessor(
            LogRecordProcessor.composite(
                SimpleLogRecordProcessor.create(exporterA),
                BatchLogRecordProcessor.builder(exporterB).build()
            )
        )
        .build();&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1755526103381&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ThreadSafe
public interface LogRecordProcessor extends Closeable {

  static LogRecordProcessor composite(LogRecordProcessor... processors) {
    return composite(Arrays.asList(processors));
  }

  static LogRecordProcessor composite(Iterable&amp;lt;LogRecordProcessor&amp;gt; processors) {
    List&amp;lt;LogRecordProcessor&amp;gt; processorList = new ArrayList&amp;lt;&amp;gt;();
    for (LogRecordProcessor processor : processors) {
      processorList.add(processor);
    }
    if (processorList.isEmpty()) {
      return NoopLogRecordProcessor.getInstance();
    }
    if (processorList.size() == 1) {
      return processorList.get(0);
    }
    return MultiLogRecordProcessor.create(processorList);
  }

 ...
 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;onEmit&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 Exporter로 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현체 ((SimpleLogRecordProcessor, BatchLogRecordProcessor))에 따라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 Exporter로 전송하거나 큐에 적재하고 나중에 Exporter로 전송합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755526236765&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ...
  void onEmit(Context context, ReadWriteLogRecord logRecord);
  ...
 }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SimpleLogRecordProcessor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logRecord.toLogRecordData()을 통해 로그 리스트로 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logRecordExporter.export(logs) 추출합니다.&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;반환된 CompletableResultCode를 pendingExports 콜백을 전달받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 로그 한 줄당 Export가 한번씩 발생합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755528519013&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Override
  public void onEmit(Context context, ReadWriteLogRecord logRecord) {
    try {
      List&amp;lt;LogRecordData&amp;gt; logs = Collections.singletonList(logRecord.toLogRecordData());
      CompletableResultCode result;

      synchronized (exporterLock) {
        result = logRecordExporter.export(logs);
      }

      pendingExports.add(result);
      result.whenComplete(
          () -&amp;gt; {
            pendingExports.remove(result);
            if (!result.isSuccess()) {
              logger.log(Level.FINE, &quot;Exporter failed&quot;);
            }
          });
    } catch (RuntimeException e) {
      logger.log(Level.WARNING, &quot;Exporter threw an Exception&quot;, e);
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;662&quot; data-start=&quot;591&quot; data-ke-size=&quot;size23&quot;&gt;BatchLogRecordProcessor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SimpleLogRecordProcessor는 들어온 로그를 바로 전송하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BatchLogRecordProcessor는 큐에 넣어두고 일정 크기만큼 모였거나, 특정 주기가 지나면 배치를 통해 전송합니다.&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;BatchLogRecordProcessor는 고정 길이의 큐를 가지고 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐가 가득 차 있는 상태에서 새로운 로그가 emit()되면 drop하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;drop된 로그의 내용은 기록하지는 않지만 drop된 count는 체크합니다.&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;추가적인 스레드를 사용하기에 emit() 호출은 큐에 넣는 작업만 하게되고 export하는 호출은 만들어진 워커 스레드에서 백그라운드에서 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 메인스레드와 워커스레드를 분리하기에, 실제 추적 시스템이 존재하는 메인스레드의 부하를 줄일 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치도 maxExportBatchSize 라는 크기와 scheduleDelayNanos라는 주기를 기준으로 워커 스레드가 배치의 값들을 진짜 exporter로 전송합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;run()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 updateNextExportTime()을 사용해서 scheduleDelayNanos 값을 기준으로 다음 export시간을 계산합니다.&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;이때 maxExportBatchSize 차거나, 지정된 시간, scheduleDelayNanos이 경과하면 내보냅니다.&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;배치에 모인 로그가 Exporter로 전달되면, 성공 여부에 따라 processedLogsCounter를 기록합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐가 비어 있으면 다음 export 시간까지 대기하고, 일시적으로 대기((poll))상태로 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 워커 스레드는 block상태가 되는게 emit()메소드가 실행되며 signal값이 true가 되면 run()이 다시 실행됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755571465548&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    ...
 
    @Override
    public void run() {
      updateNextExportTime();

      while (continueWork) {
        if (flushRequested.get() != null) {
          flush();
        }
        while (!queue.isEmpty() &amp;amp;&amp;amp; batch.size() &amp;lt; maxExportBatchSize) {
          batch.add(queue.poll().toLogRecordData());
        }
        if (batch.size() &amp;gt;= maxExportBatchSize || System.nanoTime() &amp;gt;= nextExportTime) {
          exportCurrentBatch();
          updateNextExportTime();
        }
        if (queue.isEmpty()) {
          try {
            long pollWaitTime = nextExportTime - System.nanoTime();
            if (pollWaitTime &amp;gt; 0) {
              logsNeeded.set(maxExportBatchSize - batch.size());
              signal.poll(pollWaitTime, TimeUnit.NANOSECONDS);
              logsNeeded.set(Integer.MAX_VALUE);
            }
          } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
          }
        }
      }
    }
    
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1905&quot; data-start=&quot;1883&quot; data-ke-size=&quot;size26&quot;&gt;자동 연동(로깅 프레임워크 브리지)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel은 OTel Java 에이전트를 사용해, 서비스에서 사용하는 로깅 프레임워크에 자동으로 TraceId, SpanId를 주입해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동연동을 사용하는 방법은 간단한데 -javaagent:opentelemetry-javaagent.jar 와 같은 명령어를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 xml파일에 추가하는 방법도 존재합니다.&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;아래는 logback에 OTel의 TraceId, SpanId를 추가하는 예시입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755686286662&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- logback.xml --&amp;gt; 
&amp;lt;configuration&amp;gt; 
&amp;lt;appender name=&quot;CONSOLE&quot; class=&quot;ch.qos.logback.core.ConsoleAppender&quot;&amp;gt; 
&amp;lt;encoder&amp;gt; &amp;lt;!-- %X{trace_id}, %X{span_id} : MDC 값 --&amp;gt; &amp;lt;pattern&amp;gt;%d %-5level [%thread] trace=%X{trace_id} span=%X{span_id} %logger - %msg%n&amp;lt;/pattern&amp;gt; &amp;lt;/encoder&amp;gt; &amp;lt;/appender&amp;gt;
&amp;lt;root level=&quot;INFO&quot;&amp;gt; 
&amp;lt;appender-ref ref=&quot;CONSOLE&quot;/&amp;gt; &amp;lt;/root&amp;gt; 
&amp;lt;/configuration&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OTel은 MDC를 사용해서 TraceId와 SpanId를 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MDC는 SLF4J 계열에서 사용하는 스레드 로컬 기반 key-value 저장소입니다.&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;예를 들어 지금 스레드에서 MDC.put(&quot;userId&quot;, &quot;1&quot;) 을 저장하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 스레드에서 찍는 모든 로그 패턴에서 {userId}와 같은 방식으로 value를 사용할 수 있게되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 사용해&amp;nbsp;OTel 에이전트에서 현재 TraceContext((TraceId, SpanId)) MDC에 자동으로 주입하여 로깅 프레임워크와 연동됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 : &lt;a href=&quot;https://opentelemetry.io/docs/concepts/signals/traces/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://opentelemetry.io/docs/concepts/signals/traces/&lt;/a&gt;&lt;/p&gt;</description>
      <category>APM만들기</category>
      <category>apm</category>
      <category>log</category>
      <category>OTEL</category>
      <category>span</category>
      <category>SpanId</category>
      <category>TRACE</category>
      <category>traceId</category>
      <category>분산추적</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/105</guid>
      <comments>https://jaykos96.tistory.com/105#entry105comment</comments>
      <pubDate>Fri, 20 Jun 2025 00:19:41 +0900</pubDate>
    </item>
    <item>
      <title>실시간 대용량 데이터에서 사용되는 알고리즘(Reservoir Sampling, HLL, CMS,Bloom Filter)들에 대해 알아보자</title>
      <link>https://jaykos96.tistory.com/104</link>
      <description>&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;대량의 데이터에 대해서 랜덤 추출, 카더널리티, 빈도, 이전 유입 여부를 확인하는 4개의 알고리즘들에 알아보겠습니다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;무작위 샘플 데이터 추출 - 레저부아 샘플링(Reservior Sampling)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스트림 데이터에서 무작위로 샘플 데이터 추출이 필요할 때가 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 시스템에서 분당 1,000만 건의 로그가 들어온다고 했을 때, 우리는 모든 로그를 다 메모리에 저장해서 빠르게 분석할수는 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 상황에서는 랜덤 표본 데이터를 추출해서 원하는 비즈니스 결과를 만들 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;레저부아 샘플링 알고리즘은 스트림 데이터 중 무작위 k개를 동일한 확률로 선택하는 알고리즘 입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉 전체 데이터를 모두 확인하지 않아도 공평하게 샘플을 확인 할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c22oNJ/btsOmhZbHhl/OVG5SkxR9JGZJErPXL5Hv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c22oNJ/btsOmhZbHhl/OVG5SkxR9JGZJErPXL5Hv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c22oNJ/btsOmhZbHhl/OVG5SkxR9JGZJErPXL5Hv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc22oNJ%2FbtsOmhZbHhl%2FOVG5SkxR9JGZJErPXL5Hv0%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;719&quot; height=&quot;392&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 개념은 이렇습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;k개의 데이터를 저장 할 수 있는 저장소 '레저부아'를 가지고 있고, '레저부아'가 가득 찬 상태에서 새로운 데이터가 도착하면 그 데이터를 '풀' 안에 데이터로서 추가, 즉 교채할 것인지, 추가하지 않을 것인지를 결정합니다.&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;n번째 데이터가 들어오면 &quot;&lt;b&gt;k&lt;/b&gt;&lt;b&gt;/n&lt;/b&gt;&quot; 확률로 레저부아 풀에 저장 여부를 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k는 풀의 크기, n의 n번째로 들어온 데이터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림과 같은 상황이라면 15/16확률입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 확률을 확인하기 위해 0과 1사이 난수를 생성합니다. 만약 n/k 미만이라면 저장소에 저장하고, 저장소의 데이터 중 하나를 랜덤으로 결정해 내보내고, n번째 데이터를 풀에 넣습니다.&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/n이 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대한 수학적 증명은 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;294&quot; data-start=&quot;267&quot;&gt;$k$: Reservoir 크기&lt;/li&gt;
&lt;li data-end=&quot;310&quot; data-start=&quot;295&quot;&gt;$n$: 전체 데이터 수&lt;/li&gt;
&lt;li data-end=&quot;343&quot; data-start=&quot;311&quot;&gt;$A_1, A_2, \dots, A_n$: 입력 데이터&lt;/li&gt;
&lt;li data-end=&quot;379&quot; data-start=&quot;344&quot;&gt;$P_j$: $A_j$가 최종 Reservoir에 남을 확률&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;j번째 데이터&amp;nbsp; $A_j$가 저장소에 저장될 확률은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$$&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;P_{\text{select}}(j)&amp;nbsp;=&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;\begin{cases}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1&amp;nbsp;&amp;amp;&amp;nbsp;\text{if&amp;nbsp;}&amp;nbsp;j&amp;nbsp;\le&amp;nbsp;k&amp;nbsp;\\&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;\frac{k}{j}&amp;nbsp;&amp;amp;&amp;nbsp;\text{if&amp;nbsp;}&amp;nbsp;j&amp;nbsp;&amp;gt;&amp;nbsp;k&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;\end{cases}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span aria-hidden=&quot;true&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 데이터는 계속해서 저장소에 들어오게되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$A_j$ 이후에 들어오는 모든 데이터 $j+1, j+2, \dots, n$번째 데이터에 들어와도 $A_j$가 유지될 확률을 구합니다.&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;$t &amp;gt; j$ 라고 했을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$t$번째 데이터가 들어오면 $\quad \frac{k}{t}$ 확률로 레저부아 저장소 중 하나의 데이터를 교체하고 그 중 $\quad \frac{1}{k}$ 확률로 $A_j$가 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 $A_j$가 $t$에서 교체될 확률은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \frac{k}{t} \cdot \frac{1}{k} = \frac{1}{t} $$&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;즉 $A_j$가 에서 살아남을 확률은 아래와 같습니다.&lt;br /&gt;$$&lt;br /&gt;1&amp;nbsp;-&amp;nbsp;\frac{1}{t}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 $A_j$가 이후 모든 단계 $t = j+1$ 부터 $n$까지 저장소에서 교체되지 않고 유지될 확률은 아래와 같이 계산 할 수 있습니다.&lt;br /&gt;$$&lt;br /&gt;P_{\text{keep}}(j) = \prod_{t = j+1}^{n} \left(1 - \frac{1}{t} \right)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 따라서 $A_j$가 저장소에 남을 확률은 이렇게 계산 할 수 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;P_j&amp;nbsp;=&amp;nbsp;P_{\text{select}}(j)&amp;nbsp;\times&amp;nbsp;P_{\text{keep}}(j)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, $j &amp;gt; k$일 때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;P_j&amp;nbsp;=&amp;nbsp;\frac{k}{j}&amp;nbsp;\cdot&amp;nbsp;\left(1&amp;nbsp;-&amp;nbsp;\frac{1}{j+1}\right)&amp;nbsp;\cdot&amp;nbsp;\left(1&amp;nbsp;-&amp;nbsp;\frac{1}{j+2}\right)&amp;nbsp;\cdots&amp;nbsp;\left(1&amp;nbsp;-&amp;nbsp;\frac{1}{n}\right)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 이를 전개하면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;P_j&amp;nbsp;=&amp;nbsp;\frac{k}{j}&amp;nbsp;\cdot&amp;nbsp;\frac{j}{j+1}&amp;nbsp;\cdot&amp;nbsp;\frac{j+1}{j+2}&amp;nbsp;\cdots&amp;nbsp;\frac{n-1}{n}&amp;nbsp;=&amp;nbsp;\frac{k}{n}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 모든 $j \in \{1, 2, \dots, n\}$ 에 대하여 아래와 같은 공식을 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;P_j&amp;nbsp;=&amp;nbsp;\frac{k}{n}&lt;br /&gt;$$&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카더널리티 구하기 - HyperLogLog(HLL) 알고리즘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림 데이터의 카더널리티를 카운트하는 기능은 시스템에서 필요합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;거의 실시간 환경에서 수많은 로그를 비교적 정확하게 카운트하려면 확률적 알고리즘을 적용해야하고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 중 비트 패턴 기반(Bit-pattern based) 알고리즘 HyperLogLog이&lt;span&gt;&amp;nbsp;&lt;/span&gt;일반적으로 많이 사용되고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HyperLog++알고리즘이라고도 합니다.&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;1716&quot; data-origin-height=&quot;1638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1TWFF/btsOmgfwpFJ/3gZNd0DtOKLgLe6KWGVsA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1TWFF/btsOmgfwpFJ/3gZNd0DtOKLgLe6KWGVsA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1TWFF/btsOmgfwpFJ/3gZNd0DtOKLgLe6KWGVsA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1TWFF%2FbtsOmgfwpFJ%2F3gZNd0DtOKLgLe6KWGVsA1%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;721&quot; height=&quot;688&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;1638&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: #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;color: #333333; text-align: start;&quot;&gt;1. 데이터의 id를 해시함수로 &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: #333333; text-align: start;&quot;&gt;2. 이후 그렇게 나온 &lt;b&gt;해시 데이터를 이진수 값으로 변경&lt;/b&gt;하고 &lt;b&gt;정해둔 최하위 몇자리 비트(m)를 10진수 변환하여 인덱스로 사용&lt;/b&gt;합니다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3. 이진수의 &lt;b&gt;앞자리부터 1이 나올때 까지의 0의 수 (leading zeros) 를 새고 그 값을 idx로 하여 &lt;b&gt;레지스터[idx],value 를 대상으로&lt;/b&gt; max연산을 사용해 큰 값을 레지스터에 넣습니다&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;위 그림처럼 만약 값이 '0000101100010'이고 m=4로 한다면 인덱스는 '0010', 배열의 인덱스는 '2'가 되고&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 인덱스에 들어갈 데이터는 0이 연속된 횟수 + 1 즉, (leading zeros) +1, 즉 5가 됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;0으로 시작하지 않는 데이터의 (leading zeros)값은 1이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;레지스터에 높은 값들이 들어 있다면 높은 (leading zeros) 값이 나온 데이터라는 의미고, 엄청나게 많은 유니크 값들이 시스템에 들어왔다는 의미가 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 배열의 &lt;b&gt;조화평균&lt;/b&gt;을 구하여 전체 데이터의 카더널리티의 근사치를 알 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;Java의 HashSet으로 10MB의 메모리가 필요한 계산이 있을때, HLL을 사용하면 결과는 정확한 값과 3%의 차이가 있지만 HashSet이 필요로 하는 메모리 크기의 1/2,500인 512바이트만 사용해서 구현할 수 있다고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;추가로 HLL의 논문에 따르면 1.5kb의 메모리 만으로 2%이내의 상대 오차로 10억개의 구별되는 데이터 개수를 계산 할 수 있다고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HLL에서 m값이 커질수록 정밀도는 높아지지만 메모리 사용량도 커집니다.&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;HLL의 동작원리는 간단하지만 어떻게 &quot;저 방식이 카더널리티를 확인 할 수 있다는거지?&quot;라는 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서부터 궁금한 부분 하나씩 해결해보려 합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;왜 leading zeros가 높다고 많은 데이터가 들어왔다고 하는걸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 수의 (leading-zeros)값이 k가 될 확률은 $10^L$&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;hash함수를 통해 이진수로 변경된 수를 기준으로 계산하면 $2^L$ 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLL의 레지스터에 높은 값들이 들어 있다면 높은 (leading-zeros) 값이 나온 데이터라는 의미고, 즉 엄청나게 많은 유니크 값들이 시스템에 들어왔다는 의미가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 개념이 HLL알고리즘의 기본적인 뼈대가 되는 &lt;b&gt;Flajolet-Martin Algorithm&lt;/b&gt; 이며, HLL알고리즘은 Flajolet-Martin Algorithm의 틀을 가지고 문제점을 개선한 알고리즘입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Flajolet-Martin Algorithm&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 잠깐 설명하였듯이 Flajolet-Martin Algorithm은 데이터를 해시하고, 그 결과를 이진값으로 변경한 뒤, 해당 값에서의 (leading-zeros) 값(L)을 읽어 레지스터에 저장하면서 들어온 데이터들의 카더널리티를 판별하는 알고리즘입니다.&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;이 결과는 $2^L$이 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 계산한 $2^L$은 이론상으로는 균등하지만 현실적으로는 데이터 수가 너무 적으면 (L)값이 편향이 생긴다는걸 통계를 통해 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보정값 $\phi \approx 0.77351$을 나누어 최종적으로 Flajolet-Martin Algorithm에서 통해 집합의 카더널리티를 구하는 공식은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\\{Cardinality}_{\text{Flajolet-Martin}} = \frac{2^L}{\phi}&lt;br /&gt;$$&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;LogLog Algorithm&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Flajolet-Martin Algorithm에서 발생하는 이상치 문제를 더 개선한 알고리즘입니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLL과 이제 조금 유사해지는데, 배열에 (L)값을 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이진수의 앞의 몇자리(b)를 잘라 십진수로 변환 후 배열의 인덱스로 사용하고, 나머지 이진수의 값에서 (leading-zeros) 값을 구합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배열의 길이(m)는 앞에서 자르는 수 b을 활용합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$m = 2^b$$&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: #555555; text-align: start;&quot;&gt; 가장 값이 큰 (leading-zeros)들의 평균 (L) 을 낸 다음 (m)을 곱해 카더널리티(k)를 구할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$ \text{Cardinality}_{\text{LogLog}}&amp;nbsp;= m2^L$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 보정상수를 추가해서 최종적인 수식을 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;표준 오차는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222d39; text-align: start;&quot;&gt;1.3/&amp;radic;m 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{Cardinality}_{\text{LogLog}}&amp;nbsp;=&amp;nbsp;m&amp;nbsp;\cdot&amp;nbsp;\frac{2^L}{\phi}&lt;br /&gt;$$&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;SuperLogLog Algorithm&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogLog보다 더 개선된 알고리즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 값들의 평균(L)을 내기 전에 가장 큰&amp;nbsp; (leading-zeros) 값들을 제외하면 더 정확한 카더널리티를 추출 할 수 있음을 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 SuperLogLog는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;평균(L)을 계산할 때 하위 70%까지의 값들 만을 사용해서 평균을 내고 단순한 보정 상수 (0.7)을 곱해서 계산합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{Cardinality}_{\text{SuperLogLog}} = 0.7m \cdot \frac{2^L}{\phi},&amp;nbsp; \quad L = \text{Avg}(\text{Smallest } 70\% \text{ of } L)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;이렇게 해서 개선된 SuperLogLog의 표준 오차는&lt;span&gt; &lt;span style=&quot;background-color: #ffffff; color: #222d39; text-align: start;&quot;&gt;1.05/&amp;radic;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222d39; text-align: start;&quot;&gt;&amp;nbsp;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;HyperLogLog Algorithm&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLL알고리즘은 SuperLogLog&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLL알고리즘에서는 배열의 데이터들에 대해 조화평균(H)이 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조화평균은 큰 이상치 (outlier)를 처리하기에 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\[&lt;br /&gt;H&amp;nbsp;=&amp;nbsp;\frac{n}{\displaystyle\sum_{i=1}^{n}&amp;nbsp;\frac{1}{x_i}}&lt;br /&gt;\]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLL 수식은 배열의 값들의 평균(L)에 대해 조화평균을 구한 값을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{Cardinality}_{\text{HyperLogLog}}&amp;nbsp;=&amp;nbsp;m&amp;nbsp;\cdot&amp;nbsp;\frac{\text{Harmonic&amp;nbsp;Mean}\left(2^{L_i}\right)}{\phi}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;m은 12~14의 값으로 권장된다고 하며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;상대 오차 (Relative Error) 는 1.04/&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;&amp;radic;m로 개선되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;빈도 추정 - Count-Min Sketch (CMS) 알고리즘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림 시스템에서는 어떤 범위에서 특정 데이터가 몇번 발생했는지 아는것은 중요합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;요구사항을 만족하기 위해 일반적으로는 Count-Min Sketch (CMS) 알고리즘을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;CMS는 적은 메모리를 사용해서 데이터에서 빈도를 측정하는 알고리즘입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;HLL처림 확률적 알고리즘이고, 약깐의 오차는 있을 수 있지만 과소추정은 하지않는다는 특징이 있습니다.&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;CMS 알고리즘을 사용하면 아래와 같은 기능을 구현할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포인트 쿼리(Point Query) : 특정 데이터가 얼마나 많이 존재하는지&lt;/li&gt;
&lt;li&gt;레인지 쿼리(Range Query): 주어진 범위에서 데이터 빈도를 알고 싶은 경우&lt;/li&gt;
&lt;li&gt;이너 프로덕트 쿼리(Inner Product Query) : 두 스케치 알고리즘의 결합을 알고 싶은 경우&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;&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;1418&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAkaf0/btsOl8hG5xa/zH1wBWg1sffb8y7kNKyrdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAkaf0/btsOl8hG5xa/zH1wBWg1sffb8y7kNKyrdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAkaf0/btsOl8hG5xa/zH1wBWg1sffb8y7kNKyrdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAkaf0%2FbtsOl8hG5xa%2FzH1wBWg1sffb8y7kNKyrdK%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;659&quot; height=&quot;348&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어온 데이터를 각 해시함수에 넣어 정수값을 각각 추출하고 그 정수값을 인덱스로하여 행에 +1을 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;같은 데이터가 들어오면 같은 방식으로 계속해서 배열에 값을 증가시킵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1760&quot; data-origin-height=&quot;1504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz6uvY/btsOmv4Nlvf/yd1whfpfqh0ggbpJAsN9N0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz6uvY/btsOmv4Nlvf/yd1whfpfqh0ggbpJAsN9N0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz6uvY/btsOmv4Nlvf/yd1whfpfqh0ggbpJAsN9N0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz6uvY%2FbtsOmv4Nlvf%2Fyd1whfpfqh0ggbpJAsN9N0%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;747&quot; height=&quot;638&quot; data-origin-width=&quot;1760&quot; data-origin-height=&quot;1504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 들어온 수를 조회할때는 데이터를 추가할때 사용했던 동일한 해시함수(h1,h2,h3,h4)를 사용해서 해당 데이터의 인덱스 값들을 찾습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;1516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rld7R/btsOm7vDaR2/YADLJgGTa6Re2zJZJeFf21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rld7R/btsOm7vDaR2/YADLJgGTa6Re2zJZJeFf21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rld7R/btsOm7vDaR2/YADLJgGTa6Re2zJZJeFf21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRld7R%2FbtsOm7vDaR2%2FYADLJgGTa6Re2zJZJeFf21%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;696&quot; height=&quot;608&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;1516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 찾은 인덱스를 통해 카운터 안의 값들 중 가장 작은 값을 해당 횟수 데이터가 들어온 근사치로 취급합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 위의 사진과 같은 상황에서는 해당 데이터가 들어온 횟수는 '4'회가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에 따르면 배열의 길이가 128인 배열 8개를 사용할 때 상대 오차는 약 1.5%이고 추정치가 정확할 확률은 99.6%라고 합니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;유입된 데이터 확인&lt;span&gt;&amp;nbsp; - &lt;/span&gt;블룸 필터 (Bloom Filter)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'이전에 유입된 데이터 인지' 확인하는 요구사항이 있을 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 수많은 스트림 데이터들에 모두 id를 지정하는건 힘든일이고 id를 이용해서 데이터가 이전에 들어왔었는지, 확인하는것은 더욱 더 어려운 일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 '블룸 필터'를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;볼룸 필터의 특징은 블룸 필터를 통해 들어온 데이터가 '이전에 입수된 적 있다'는 응답이 100% 믿을 수 없다는 것 입니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 '이전에 입수된 적 있다'는 응답은 100% 믿을 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 긍정오류(False Positive)는 가능하지만 부정오류(Flase negative)는 불가능하다고 할 수 있습니다.&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;크롬에서 악성URL을 판별하거나 CDN에서 cache hit여부를 확인하는데도 블룸 필터가 쓰인다고 합니다!&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;b&gt;먼저 블룸 필터는 길이가 m인 이진수 배열과 k개의 독립적인 해시 함수가 필요합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2338&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8SbRR/btsOp9UWWAk/2FFuJE5v3WDbPLYb8R5i81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8SbRR/btsOp9UWWAk/2FFuJE5v3WDbPLYb8R5i81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8SbRR/btsOp9UWWAk/2FFuJE5v3WDbPLYb8R5i81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8SbRR%2FbtsOp9UWWAk%2F2FFuJE5v3WDbPLYb8R5i81%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;816&quot; height=&quot;314&quot; data-origin-width=&quot;2338&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 들어오면 각 해시 함수를 통해 해시값을 추출 후,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출된 해시값을 인덱스로 하여 이진수 배열에 값이 0이면 1을 더하고 이미 1이라면 변경하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림처럼 데이터의 해시 값이 2,5,7,9가 나온다면 각 인덱스의 값을 1로 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이후 같은 데이터가 들어오면 동일하게 해시값이 나오게 되고, 그 해시 값들의 인덱스가 모두 1이라면 해당 데이터는 이전에 들어온 적 있다고 판단하고, 아니라면 해당 데이터는 들어온적 없는 데이터라고 판단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 배열의 크기 (m)을 결정하는게 아주 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;m은 아래와 같은 공식으로 정할 수 있으며 bit 크기를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서 n은 삽입할 예상 데이터 수,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;p는 허용 가능한 오차입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;m&amp;nbsp;=&amp;nbsp;-&amp;nbsp;\frac{n&amp;nbsp;\cdot&amp;nbsp;\ln&amp;nbsp;p}{(\ln&amp;nbsp;2)^2}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;k는 m을 이용해 구합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;k&amp;nbsp;=&amp;nbsp;\frac{m}{n}&amp;nbsp;\cdot&amp;nbsp;\ln&amp;nbsp;2&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 1,000,000개 데이터에 대해 1% 허용 오차를 만들고 싶으면 아래와 같이 데이터를 넣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 결과물을 8로 나누어 byte 배열 크기로 만들면 약 1.15가 나옵니다.&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;br /&gt;m&amp;nbsp;=&amp;nbsp;-&amp;nbsp;\frac{1,000,000&amp;nbsp;\cdot&amp;nbsp;\ln(0.01)}{(\ln&amp;nbsp;2)^2}&amp;nbsp;\approx&amp;nbsp;9,585,058&amp;nbsp;\text{&amp;nbsp;bits}&lt;br /&gt;$$&lt;br /&gt;$$&lt;br /&gt;\frac{9,585,058}{8&amp;nbsp;\times&amp;nbsp;1024&amp;nbsp;\times&amp;nbsp;1024}&amp;nbsp;\approx&amp;nbsp;1.15&amp;nbsp;\text{&amp;nbsp;MB}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실시간 데이터 파이프라인 아키텍쳐&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chengweihu.com/hyperloglog&quot;&gt;https://chengweihu.com/hyperloglog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>데이터 파이프라인 아키텍쳐</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/104</guid>
      <comments>https://jaykos96.tistory.com/104#entry104comment</comments>
      <pubDate>Fri, 30 May 2025 22:53:51 +0900</pubDate>
    </item>
    <item>
      <title>슬라이딩 윈도우와 텀블링 윈도우에 대해 알아보자</title>
      <link>https://jaykos96.tistory.com/103</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;타임 스큐 (Time skew)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 만들고 운영하다 보면 시간을 다루는일이 참 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템에 진입한 시점을 말하는 스트림 시간(Stream time) 실제 이벤트가 발생한 이벤트 시간(Event time) 크게 두가지로 볼 수 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 스마트 워치에서 피트니스 데이터를 수집 할 때 스트리밍 시스템으로 데이터가 전송된다고 합시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 스트림 시간은 피트니스 데이터가 시스템의 어떤 단계에 들어온 시점이고, 이벤트 시간은 워치의 센서가 데이터를 수집한 시점을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 이 두 숫자는 다를 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스트림 시간과 이벤트 시간의 차이를 타임 스큐 (Time skew)라고 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시간 윈도우(Windows of time)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 스트림 데이터는 개념상 무한하기에 메모리에 모두 보관할 수는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 시간단위로 잘라서 사용하는데 이렇게 잘려진, 즉 계산을 수행할 수 있도록 잘려진 데이터를 &quot;윈도우(Window)&quot;라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1938&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eIuhXZ/btsOe65EpaX/c8L6MznjMLiPjFvk8mfub1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eIuhXZ/btsOe65EpaX/c8L6MznjMLiPjFvk8mfub1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eIuhXZ/btsOe65EpaX/c8L6MznjMLiPjFvk8mfub1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeIuhXZ%2FbtsOe65EpaX%2Fc8L6MznjMLiPjFvk8mfub1%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;739&quot; height=&quot;390&quot; data-origin-width=&quot;1938&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 윈도우 기법에는 트리거 조건(Trigger)과 추출 조건(Eviction policies)라는 공통 속성이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리거는 &quot;데이터를 내보낼 조건을 정하는 규칙(시간, 카운트, 이벤트 기반)&quot;이고 추출 조건은 &quot;윈도우 내부의 데이터를 어떻게 처리할지&quot;에 대한 조건입니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;슬라이딩 윈도우(Sliding windows)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;슬라이딩 윈도우는 윈도우 길이(Window lenght), 슬라이딩 간격(Sliding interval)이라는 이름으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;시간에 기반한 추출조건과 트리거 조건을 사용합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;윈도우 길이는 추출조건이고, 슬라이딩 간격은 트리거 조건입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/36ih0/btsOfX1bbbW/LanSxTlgCYUEYnCr6dKuYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/36ih0/btsOfX1bbbW/LanSxTlgCYUEYnCr6dKuYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/36ih0/btsOfX1bbbW/LanSxTlgCYUEYnCr6dKuYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F36ih0%2FbtsOfX1bbbW%2FLanSxTlgCYUEYnCr6dKuYk%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;593&quot; height=&quot;480&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 위 그림 처럼 윈도우 길이가 2초고 슬라이딩 간격이 1초라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초마다 2초간 들어온 데이터를 담을 수 있는 윈도우를 만들게 됩니다. (데이터 중복 허용가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4초간 데이터가 들어왔다면 (0,2), (1,3), (2,4), (3,5)총 4개의 윈도우가 만들어 지고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이딩 간격은 2초이기에 각 윈도우에는 순서대로 (A,B), (B,C), (C,D), (D) 이렇게 데이터가 들어가게 됩니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;텀블링 윈도우(Tumbling window)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;텀블링 윈도우에서 트리거 조건은&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt; 윈도우 내부 데이터가 일정 갯수 이상일때, 또는 미리 정해둔 시간(t) 두가지입니다.&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그렇기에 &lt;b&gt;텀블링 윈도우는 카운트기반, 시간 기반 2가지가 종류&lt;/b&gt;가 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;추출 조건은 시간일수도 있고 윈도우 갯수일수도 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;카운트 기반 윈도우&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AZ4Bs/btsOieJMeu0/2CHj38HQtZGmJZcu6zxjW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AZ4Bs/btsOieJMeu0/2CHj38HQtZGmJZcu6zxjW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AZ4Bs/btsOieJMeu0/2CHj38HQtZGmJZcu6zxjW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAZ4Bs%2FbtsOieJMeu0%2F2CHj38HQtZGmJZcu6zxjW0%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;613&quot; height=&quot;307&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;982&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;시간(t) 기반 윈도우&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chSkml/btsOiCjkooy/7voSObTzGt780q1Mrqla40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chSkml/btsOiCjkooy/7voSObTzGt780q1Mrqla40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chSkml/btsOiCjkooy/7voSObTzGt780q1Mrqla40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchSkml%2FbtsOiCjkooy%2F7voSObTzGt780q1Mrqla40%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;703&quot; height=&quot;358&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1014&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;다음 포스팅에서는 이렇게 모든 데이터들을 취합하고 분석하는 방법에 대해 다루겠습니다!&lt;/span&gt;&lt;/p&gt;</description>
      <category>ComputerScience</category>
      <category>스트리밍</category>
      <category>슬라이딩 윈도우</category>
      <category>시간 윈도우</category>
      <category>텀블링 윈도우</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/103</guid>
      <comments>https://jaykos96.tistory.com/103#entry103comment</comments>
      <pubDate>Tue, 27 May 2025 23:08:20 +0900</pubDate>
    </item>
    <item>
      <title>스트리밍 데이터 아키텍쳐- 2 저장소, 접근</title>
      <link>https://jaykos96.tistory.com/102</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에 이어서 스트리밍 시스템에서 사용하는 패턴의 아키텍쳐를 공부해보고자 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실시간 데이터 파이프라인 아키텍쳐를 보고 공부한 내용을 다룹니다!&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스트리밍 데이터 아키텍쳐&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스트리밍 아키텍쳐는 크게 6가지 단계로 나눌 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;본 포스팅에서는 수집, 접근 단계에 대해 정리해 보려합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA7AG7/btsN2pKHOgh/m8QN2lnSwEH4VO36a07Ys1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA7AG7/btsN2pKHOgh/m8QN2lnSwEH4VO36a07Ys1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA7AG7/btsN2pKHOgh/m8QN2lnSwEH4VO36a07Ys1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA7AG7%2FbtsN2pKHOgh%2Fm8QN2lnSwEH4VO36a07Ys1%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;2938&quot; height=&quot;1004&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;수집&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열심히 수집하고 분석한 데이터는 비즈니스에 맞게 적절하게 저장되어야합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장기 스토리지(Long-term storage)에 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트리밍 시스템에서 처리한 데이터를 S3, HDFS 또는 RDBMS와 같은 장기 저장소에 저장하는 경우가 있습니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;직접 저장 (Direct writing)&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;그렇기에 배치로 데이터를 가져온뒤 상황에 맞게 처리하는 경우가 많습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDh3Qc/btsN2b62gkC/w8M4gclHtJbkLW64bWw6bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDh3Qc/btsN2b62gkC/w8M4gclHtJbkLW64bWw6bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDh3Qc/btsN2b62gkC/w8M4gclHtJbkLW64bWw6bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDh3Qc%2FbtsN2b62gkC%2Fw8M4gclHtJbkLW64bWw6bK%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;730&quot; height=&quot;411&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;간접 저장 (indirect writing)&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;b&gt;먼저 분석이 완료된 스트림 데이터를 큐에 저장합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특이한 점은 최종적으로 배치 로더(Batch loader)를 사용해 장기 스토리지에 저장합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2016&quot; data-origin-height=&quot;920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRiGpY/btsN1PwFdyI/qedcBTf263lKbBvi3yKX30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRiGpY/btsN1PwFdyI/qedcBTf263lKbBvi3yKX30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRiGpY/btsN1PwFdyI/qedcBTf263lKbBvi3yKX30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRiGpY%2FbtsN1PwFdyI%2FqedcBTf263lKbBvi3yKX30%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;2016&quot; height=&quot;920&quot; data-origin-width=&quot;2016&quot; data-origin-height=&quot;920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간접 저장 방식을 사용하면 메시지 큐 단계는 스트림 데이터 처리 속도와 볼륨에 대응 할 수 있고, 대용량 저장 처리는 배치 로더에 위임해서 성능 발생이 줄어들 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;캐싱 시스템(Caching system)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱 시스템을 사용하면 메모리(DRAM)에 데이터를 저장할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Read-through&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Read-througt는 캐싱 시스템이 캐시에 없는 데이터를 요청받았을 때 영구 저장소에서 데이터를 읽어옵니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjuHVB/btsOarv2DO1/k8TVKbBOcgxwELvxQcPS81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjuHVB/btsOarv2DO1/k8TVKbBOcgxwELvxQcPS81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjuHVB/btsOarv2DO1/k8TVKbBOcgxwELvxQcPS81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjuHVB%2FbtsOarv2DO1%2Fk8TVKbBOcgxwELvxQcPS81%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;771&quot; height=&quot;434&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Refresh-ahead&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Refresh-ahead는 &lt;b&gt;캐시가 만료 또는 삭제되기 전에 최근에 접근했던 데이터로 캐시의 값을 새롭게 변경합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시가 만료되면 영구 저장소에 접근해야하는 Read-through전략에 비해 상대적인 성능이점이 있습니다.&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;2908&quot; data-origin-height=&quot;1166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDuJSV/btsObWgFHQq/ZIvxHrLK4CPD2iclBLtOHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDuJSV/btsObWgFHQq/ZIvxHrLK4CPD2iclBLtOHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDuJSV/btsObWgFHQq/ZIvxHrLK4CPD2iclBLtOHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDuJSV%2FbtsObWgFHQq%2FZIvxHrLK4CPD2iclBLtOHK%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;2908&quot; height=&quot;1166&quot; data-origin-width=&quot;2908&quot; data-origin-height=&quot;1166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Write-through&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터가 들어오면 캐시와 영속 저장소에 동시에 저장하는 방식입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상대적으로 쓰기 비용이 다른 캐싱시스템에 비해 높다는 단점이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1968&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgzbaR/btsOa4tdWnP/pIpvGGIHPqPEfopKykiIe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgzbaR/btsOa4tdWnP/pIpvGGIHPqPEfopKykiIe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgzbaR/btsOa4tdWnP/pIpvGGIHPqPEfopKykiIe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgzbaR%2FbtsOa4tdWnP%2FpIpvGGIHPqPEfopKykiIe1%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;1968&quot; height=&quot;882&quot; data-origin-width=&quot;1968&quot; data-origin-height=&quot;882&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Write-around&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;write-around에서는 캐싱시스템은 캐시가 영속 저장소를 업데이트합니다.&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;영속 저장소와 캐시 두곳에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;각각 다른 방식으로&lt;span&gt; &lt;/span&gt;&lt;/span&gt;데이터를 업데이트 해야한다는 복잡성이 있지만 두 저장소가 연결되어 있지 않을 수 있다는 장점이 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A85g9/btsOagnW1Xn/rxqZjJDQjByI4bMNQt4K41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A85g9/btsOagnW1Xn/rxqZjJDQjByI4bMNQt4K41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A85g9/btsOagnW1Xn/rxqZjJDQjByI4bMNQt4K41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA85g9%2FbtsOagnW1Xn%2FrxqZjJDQjByI4bMNQt4K41%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;2870&quot; height=&quot;1134&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Write-back (Write-behind)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;write-around에서 캐싱시스템은 캐시가 영속 저장소에 새로운 값을 쓰는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write-through와 조금 다르게 &lt;b&gt;케시에 대한 쓰기가 된 후 바로 백그라운드를 통해 영속 저장소에 값을 쓰거나 업데이트합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mphJ4/btsOaQhv7II/fkaLyNAt0XLAk5uHs7UV3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mphJ4/btsOaQhv7II/fkaLyNAt0XLAk5uHs7UV3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mphJ4/btsOaQhv7II/fkaLyNAt0XLAk5uHs7UV3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmphJ4%2FbtsOaQhv7II%2FfkaLyNAt0XLAk5uHs7UV3k%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;1962&quot; height=&quot;786&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;b&gt;캐싱 시스템은 데이터를 빠르게 읽기 위한 임시 저장소로 영속성을 보장하지 않습니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반면 IMDB(In-Memory Database), IMDG(In-Memory Data Grids)는 캐싱 시스템과 다르게 영속성을 보장하기 위해 메모리에 우서 저장을 하고 이후 디스크에 저장한다는 차이점이 있습니다.&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;IMDB와 IMDG는 캐싱 시스템이 발전함에 따라 점점 차이점이 사라지고 있지만 전통적으로 몇가지 차이점이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IMDG는 분산 아키텍쳐를 사용하여 주로 대용량 데이터를 처리하며, RDBMS에서 익숙한 저장 프로시저와 유사한 동작을 할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IMDB는 메모리에 전체 데이터를 저장해 빠른 처리와 트랜잭션을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 IMDG는 SQL을 통해 API접근을 제공하는 반면, IMDB는 비SQL API를 사용합니다.&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;데이터 접근&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트리밍 시스템의 마지막 단계입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리가 여러 단계를 거쳐 데이터를 모으고 가공한 이유는 결국 마지막에 클라이언트에 보여주기위해서 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceJXRR/btsObTEkC7Z/aBjv5SJrwm5SdyXJVb0Aq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceJXRR/btsObTEkC7Z/aBjv5SJrwm5SdyXJVb0Aq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceJXRR/btsObTEkC7Z/aBjv5SJrwm5SdyXJVb0Aq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceJXRR%2FbtsObTEkC7Z%2FaBjv5SJrwm5SdyXJVb0Aq0%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;441&quot; height=&quot;216&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 스트리밍 시스템이라도 할지라도 클라이언트와의 통신에서도 반드시 연속적인 통신이 필요한것은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 PUSH/PULL 방식뿐만 아니라 데이터 동기화 방식, RMI/RPC, 심플 메시지, 구독/발행 등 다양한 패턴이 가능합니다.&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;데이터 동기화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 동기화는 클라이언트와 API간 통신이 아닌, API의 데이터베이스 또는 저장소와 동기화하는 방식을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 스트리밍 분석을 마치고 데이터의 변경사항을 감지하여 스트리밍 클라이언트에 데이터를 보내는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴은 크게 두가지로 나뉘는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 처음 API로 연결될 때 분석된 데이터를 요청합니다.&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;1596&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Aa9l4/btsObJhBata/oUmS9qIhowD314Spj7Bp21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Aa9l4/btsObJhBata/oUmS9qIhowD314Spj7Bp21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Aa9l4/btsObJhBata/oUmS9qIhowD314Spj7Bp21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAa9l4%2FbtsObJhBata%2FoUmS9qIhowD314Spj7Bp21%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;1596&quot; height=&quot;502&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;RMI와 RPC&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RMI(Remote Method Invocation)/RPC(Remote Procedure Call)패턴은 API서버에 새로운 데이터가 도착하거나 클라이언트가 필요로 하는 데이터가 도착했을 때, 클라이언트에 있는 메서드를 원격으로 호출하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트리밍 데이터 API서버는 분석 단계에서 처리 완료된 데이터가 저장소에 저장되었는지 모니터링하고, 저장소의 데이터가 변경되면 원격 메소드를 호출하여 클라이언트에 변경된 데이터를 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 RMI와 고전적인 RPC 방식은 현재는 잘 사용되지 않으며, 구글의 gRPC가 상황에 따라 주로 사용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2248&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/414Zg/btsObvxfiZo/q5rKAStKFbFOoCtxJLAT40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/414Zg/btsObvxfiZo/q5rKAStKFbFOoCtxJLAT40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/414Zg/btsObvxfiZo/q5rKAStKFbFOoCtxJLAT40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F414Zg%2FbtsObvxfiZo%2Fq5rKAStKFbFOoCtxJLAT40%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;2248&quot; height=&quot;398&quot; data-origin-width=&quot;2248&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;심플 메시징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 스트리밍API 서버의 가장 최근에 데이터를 요청하고 응답으로 가장 최신의 데이터만을 전달하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 심플 메시징 방식은 'xxxx보다 최신인 데이터를 요청', 'xxxx기간 동안 변경되지 않은 데이터의 요청'과 같은 명령에는 적합한 방식이 아닙니다.&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;img src=&quot;https://blog.kakaocdn.net/dn/dUaqBM/btsOcaswvOR/1thBh0Vq1yKwJrK8u3FRgK/img.png&quot; data-origin-width=&quot;2270&quot; data-origin-height=&quot;396&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;발행 - 구독 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 특정 채널을 구독하고, API는 데이터가 변경될 때 해당 채널을 구독하고 있는 모든 클라이언트에 메시지를 보내는 방식입니다.&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;2234&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DAxeD/btsObTxBid3/85VSg0TAWl3srynnlU0Tpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DAxeD/btsObTxBid3/85VSg0TAWl3srynnlU0Tpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DAxeD/btsObTxBid3/85VSg0TAWl3srynnlU0Tpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDAxeD%2FbtsObTxBid3%2F85VSg0TAWl3srynnlU0Tpk%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;2234&quot; height=&quot;354&quot; data-origin-width=&quot;2234&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;클라이언트에 데이터 전달 프로토콜 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트리밍 API에서는 웹훅, 롱풀링, SSE, 웹소켓과 같은 통신 패턴이 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSE는 롱풀링의 단점을 극복하고 나온 방식이기에 웹훅, SSE, 웹소켓에 대해 알아보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;웹훅(Webhook)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 데이터가 도착하거나 기타 조건이 충족되면 클라이언트를 HTTP엔드포인트로 호출하는 방식으로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출은 HTTP의 POST를 통해 실행됩니다.&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;최초 클라이언트가 HTTP POST를 사용하여 콜백을 등록하기 위한 요청을 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백이 저장되면 이후 이벤트 조건이 충족되면 콜백을 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제 완료 알림이나, 깃 커밋 푸시시 슬랙이나 CI로 전달하는 깃허브 웹훅과 같은 방식에서 사용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2254&quot; data-origin-height=&quot;1412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCh0ZP/btsOcoRB1BM/B3xUo0l7UGp3X00KaKaH8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCh0ZP/btsOcoRB1BM/B3xUo0l7UGp3X00KaKaH8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCh0ZP/btsOcoRB1BM/B3xUo0l7UGp3X00KaKaH8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCh0ZP%2FbtsOcoRB1BM%2FB3xUo0l7UGp3X00KaKaH8k%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;2254&quot; height=&quot;1412&quot; data-origin-width=&quot;2254&quot; data-origin-height=&quot;1412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SSE (Server Sent Events)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;W3C의 권장 서버 -&amp;gt; 브라우저간 단방향 프로토콜입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 먼저 스트리밍 API 연결 후 맺어진 네트워크를 통해 모든 이벤트가 지속적으로 전송됩니다.&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;1982&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5nDAg/btsOaL2db0p/hlurV3EpFXIICqqOIKkB01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5nDAg/btsOaL2db0p/hlurV3EpFXIICqqOIKkB01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5nDAg/btsOaL2db0p/hlurV3EpFXIICqqOIKkB01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5nDAg%2FbtsOaL2db0p%2FhlurV3EpFXIICqqOIKkB01%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;1982&quot; height=&quot;1062&quot; data-origin-width=&quot;1982&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;비연결 푸시 (Connection push)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSE는 클라이언트와의 지속적인 연결이 필수적입니다.&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;비연결 푸시 방식을 사용하면 SSE의 단점인 지속적인 연결을 해결할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;푸시 프록시 서버를 사용하여 어플리케이션이 꺼져있어도 메시지를 보낼 수 있습니다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2778&quot; data-origin-height=&quot;1570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvNGHw/btsOb6KN38R/80sbqBxHNjcoCKEFBlRDg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvNGHw/btsOb6KN38R/80sbqBxHNjcoCKEFBlRDg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvNGHw/btsOb6KN38R/80sbqBxHNjcoCKEFBlRDg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvNGHw%2FbtsOb6KN38R%2F80sbqBxHNjcoCKEFBlRDg0%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;780&quot; height=&quot;441&quot; data-origin-width=&quot;2778&quot; data-origin-height=&quot;1570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 클라이언트(여기서는 모바일 디바이스)가 스트리밍 API와 연결을 하고 이벤트를 수신합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 클라이언트에서 지정한 시간, 조건이 지나면 디바이스는 절전모드로 전환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 푸시 프록시 서버와 연결 상태를 유지하기 위한 통신(마지막으로 받은 이벤트의ID를 전송)을 시작합니다.&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;푸시 프록시 서버가 이벤트를 수신하면 모바일 푸시 시스템(FCM,APNs)을 사용해서 클라이언트로 이벤트를 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서는 메시지를 처리하기 위해 절전모드를 일시 해제하고 다시 절전모드에 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시점이 되면 푸시 프록시가 스트리밍API와 연결을 끊고 연결이 종료됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;웹소켓&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹소켓은 스트리밍 시스템에서 가장 대중적이고 효율적인 방법중 하나입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;초기 핸드 쉐이크에서는 HTTP를 사용하고 이후 통신은 TCP를 사용합니다.&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;아래 그림에서 &quot;저속 (Slow down)&quot;으로 표시딘 부분은 통신이 양방향으로 이루어지고 클라이언트가 종류와 전송 시점을 결정하여 서버로 언제든지 메시지를 보낼 수 있다는 의미입니다.&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;연결을 올바르게 종료하려면 종료 헨드쉐이크를 사용해서 서로 TCP 통신을 끊습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2720&quot; data-origin-height=&quot;1558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FAq2S/btsObTx2prc/gh0cJ7uTXfcdMKEgp86g2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FAq2S/btsObTx2prc/gh0cJ7uTXfcdMKEgp86g2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FAq2S/btsObTx2prc/gh0cJ7uTXfcdMKEgp86g2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFAq2S%2FbtsObTx2prc%2Fgh0cJ7uTXfcdMKEgp86g2k%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;2720&quot; height=&quot;1558&quot; data-origin-width=&quot;2720&quot; data-origin-height=&quot;1558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 스트리밍 시스템의 구조에 대해서 알아보았는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 실무적인 부분과 직접 연관짓기에는 조금 부족한 글인것 같습니다..&lt;/p&gt;
&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;</description>
      <category>데이터 파이프라인 아키텍쳐</category>
      <category>스트리밍 데이터 아키텍쳐</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/102</guid>
      <comments>https://jaykos96.tistory.com/102#entry102comment</comments>
      <pubDate>Sun, 18 May 2025 21:01:45 +0900</pubDate>
    </item>
    <item>
      <title>스트리밍 데이터 아키텍쳐-1 수집 단계, 큐, 분석 단계</title>
      <link>https://jaykos96.tistory.com/101</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;스트리밍 데이터 아키텍쳐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트리밍 아키텍쳐는 크게 6가지 단계로 나눌 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2938&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zP9Hq/btsNJufIZdD/XEGG7hxrnFb0KFUmGFQn9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zP9Hq/btsNJufIZdD/XEGG7hxrnFb0KFUmGFQn9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zP9Hq/btsNJufIZdD/XEGG7hxrnFb0KFUmGFQn9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzP9Hq%2FbtsNJufIZdD%2FXEGG7hxrnFb0KFUmGFQn9K%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;2938&quot; height=&quot;1004&quot; data-origin-width=&quot;2938&quot; data-origin-height=&quot;1004&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;수집 단계&lt;/h2&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;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;발행/구독 패턴&lt;/li&gt;
&lt;li&gt;단방향 패턴&lt;/li&gt;
&lt;li&gt;요청/확인응답 패턴&lt;/li&gt;
&lt;li&gt;스트림 패턴&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요청/응답 패턴 ((Request/response pattern))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라이언트가 서버로 요청을 보내면 서버가 응답하는 웹 브라우저에서 자주 사용되는 일반적인 패턴입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반비동기(Half-async), 비동기(Full-async) 방식으로 요청/응답 패턴을 구현할 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;요청/응답 확인 패턴 ((Request/acknowledge pattern))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청/응답 패턴과 유사하게 &lt;b&gt;통신을 해야하지만 서버에서 반환하는 응답이 클라이언트에는 필요하지 않는 경우에 사용됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인응답으로 전달되는 데이터는 현재 요청 상태를 확인하는 용도의 데이터나 다음 요청에 사용할 데이터를 내려주기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 고객이 접속한 모든 페이지에 대한 정보와 클릭한 링크에 대한 데이터를 수집해야한다고 한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 요청이 들어올때 서버는 다음요청에서 고객이 사용할 고유의 식별자를 넘겨줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 식별자는 고객이 방문하는 모든 페이지들을 추적하는데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 정보들을 토대로 고객의 구매성향을 분석 할수도 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;발행/구독 패턴 ((Publish/subscribe pattern))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적인 메시지 기반 데이터 시스템에서 자주 사용되는 패턴입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서가 브로커에세 메시지를 전달하는 것으로 시작됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지는 토픽(Topic)으로 전송되며, 전송 이후 토픽을 구독하는 모든 컨슈머(Consumer)에게 메시지가 전송됩니다.&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;844&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qGgcJ/btsNLZZwjho/YlwdchW5mMGZNWra5SR9y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qGgcJ/btsNLZZwjho/YlwdchW5mMGZNWra5SR9y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qGgcJ/btsNLZZwjho/YlwdchW5mMGZNWra5SR9y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqGgcJ%2FbtsNLZZwjho%2FYlwdchW5mMGZNWra5SR9y0%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;765&quot; height=&quot;368&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;단방형 패턴 ((One-way pattern))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단방향 패턴은 요청하는 시스템이 응답이 필요하지 않을 때 사용합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'발사 후 망각' (Fire and forgot) 패턴과 유사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청/응답 확인 패턴과 유사하지만 서버가 응답을 보내지 않아도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 데이터의 유실을 허용하고 통신의 단순화, 리소스 감소, 전송 속도를 보장해야하는 환경에서는 단방향 패턴이 적합합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;스트림 패턴 ((Stream pattern))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림 패턴은 앞서 다루었던 패턴들과 조금 다릅니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보통 클라이언트가 서버로부터 응답을 받거나 받지않으며 클라이언트에서 출발하는 한번 한번의 요청이 주를 이루었다면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스트림 패턴에서는 한번에 데이터를 모두 전달하지 않고 지속적으로 데이터를 응답으로 보내는 특징을 가집니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버는 원천 스트림 데이터와 연결하여 지속적으로 원천 스트림 데이터를 가져갈 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AfQd1/btsNJEv1BZQ/2n6lZmfUKhpCiKOoUF1wk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AfQd1/btsNJEv1BZQ/2n6lZmfUKhpCiKOoUF1wk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AfQd1/btsNJEv1BZQ/2n6lZmfUKhpCiKOoUF1wk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAfQd1%2FbtsNJEv1BZQ%2F2n6lZmfUKhpCiKOoUF1wk1%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;644&quot; height=&quot;236&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;236&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;스트림 패턴((Stream pattern))의 수평확장&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;b&gt;이를 해결하기 위한 방법 중 하나는 버퍼링((Buffering layer))를 사용하는 방법입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼링 계층과 수집서버는 서로 상호 배타적((Mutually execlusice))이지 않으며 스트림 데이터의 유입량이나 크기에 따라 둘 다 적절한 상태로(크기, 갯수) 설계되어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZR5AI/btsNMdwtMri/3o44EFLYikuxweFxrhGIDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZR5AI/btsNMdwtMri/3o44EFLYikuxweFxrhGIDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZR5AI/btsNMdwtMri/3o44EFLYikuxweFxrhGIDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZR5AI%2FbtsNMdwtMri%2F3o44EFLYikuxweFxrhGIDk%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;631&quot; height=&quot;552&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;내결함성 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 소프트웨어는 장애에 대한 설계가 필요합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집 단계에서는 원천 데이터가 수집 서버로 들어오는 시점, 수집 서버에서 큐로 넘어가는 시점 크게 두곳에서 장애가 발생 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내결함성을 달성하기 위해 체크 포인팅((Checkpointing))과 로깅((logging)) 방식이 사용됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYyA9m/btsNMCwj8kz/OvDd2evCqL1APxoiEBYYRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYyA9m/btsNMCwj8kz/OvDd2evCqL1APxoiEBYYRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYyA9m/btsNMCwj8kz/OvDd2evCqL1APxoiEBYYRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYyA9m%2FbtsNMCwj8kz%2FOvDd2evCqL1APxoiEBYYRK%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;770&quot; height=&quot;181&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;체크 포인팅((Checkpointing))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크포인팅의 두가지 키워드는 전역 스냅샷(Global snapshot)과 데이터 유실 가능성(Potential for data loss)입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전역 스냅샷(Global snapshot) : 시스템 전역 상태에 대한 스냅샷을 정기적으로 특정 저장소에 저장합니다.&lt;/li&gt;
&lt;li&gt;데이터 유실 가능성(Potential for data loss) : 가장 최근에 기록된 전역 상태까지 복구할 수 있도록 합니다. 이후 시점에 처리되고 생성된 메시지는 유실됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 복원하는 전역상태는 영속성 저장소(Persistent store)에 저장되어있는 상태를 의미합니다.&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;로깅 ((Logging))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅의 기본적인 아이디어는 &lt;b&gt;'메세지를 제처리 할 수 있다면, 시스템은 전역 스냅샷이 없어도 전 구간에서 일관돤 상태에 도달할 수 있다'&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;로깅방식은 시스템을 구성하는 각 단계가 수신한 모든 메시지를 자체적으로 저장하고 장애가 발생하면 저장된 메시지를 재처리하는 방식입니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅은 RBML((Receiver-Based Message Logging))과 SBML((Sender-Based Message Logging)) 이 두 방식을 합친 HML(Hybrid Message Logging)이 있습니다. ((HML방식은 RBML방식과 SBML방식의 장점만을 합친 방식입니다.))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;RBML은 서버가 데이터를 받을 떄, &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;SBML방식은 데이터를 다음 단계로 보낼 때 데이터의 유실을 방지합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;로깅방식 ((RMBL, SBML, HML))&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RBML((Receiver-Based Message Logging))&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RBML은 전달받은 모든 메시지를 저장소에 저장한 이후에 로직을 처리하는 방식입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 방식은 소프트웨어에 장애가 발생하더라도 이미 저장되어 있던 데이터를 바탕으로 재처리하여 복구 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만 저장소의 성능이 따라주지 않으면 시스템의 성능에 큰 영향이 있을 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;장애가 발생하면 수집서버는 프로듀서로 부터 더 이상 메시지를 받지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RBML로거는 메시지가 저장된 저장소에서 처리되지 않는 메시지를 읽고 기존 순서대로 로직을 처리합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;중지되었던 모든 메시지가 처리되고 나면 수집서버가 복구된 것으로 간주합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;1152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tiJik/btsNLrhYpDY/kqkzWt6wY8zCsKhjyIzS9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tiJik/btsNLrhYpDY/kqkzWt6wY8zCsKhjyIzS9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tiJik/btsNLrhYpDY/kqkzWt6wY8zCsKhjyIzS9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtiJik%2FbtsNLrhYpDY%2FkqkzWt6wY8zCsKhjyIzS9K%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;1974&quot; height=&quot;1152&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;1152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 데이터 프로듀서 (클라이언트 등)가 메시지를 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 데이터 프로듀서가 보낸 메시지는 RBML로거 서버로 전달되고 저장소로 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 메시지가 저장소에 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 비즈니스 로직 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 수집단계의 다음 단계인 큐 단계로 메시지가 전달됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SBML((Sender-Based Message Logging))&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SBML은 서버가 처리한 데이터를 다음 단계로 보내기 전 수집서버에서 외부로 나가는 모든 메시지를 로깅합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RBML과 SBML의 중요한 차이점은 RMBL은 메시지를 처리하기 전에 저장하고 SMBL은 다음단계로 넘어가기 전에 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 RBML은 원천데이터이고 SBML은 로직을 통해 한번 가공된 데이터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애가 발생하면 수집서버는 RBML과 동일하게 프로듀서로 부터 데이터를 받지 않게됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;1154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9rCU1/btsNKclOchc/uIteaMqv9Qmc5H7iUYFeO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9rCU1/btsNKclOchc/uIteaMqv9Qmc5H7iUYFeO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9rCU1/btsNKclOchc/uIteaMqv9Qmc5H7iUYFeO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9rCU1%2FbtsNKclOchc%2FuIteaMqv9Qmc5H7iUYFeO0%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;1976&quot; height=&quot;1154&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;1154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SBML에서 메시지가 다음단계로 잘 넘어갔는지 확인하려면?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SBML에서 넘어간 데이터가 잘 처리되었는지 알 수 있는 방식은 &lt;b&gt;메시지 큐 단계에서 메시지를 정상적으로 받았다고 수집 단계에 확인 응답을 보내주는 것입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 큐에 정상적으로 전달되었으면 수집단계에서 이상 수집 단계에서 데이터를 보관할 필요가 없으니 저장소에서 삭제하는 등 비즈니스에 따라 해당 메시지에 대해 전송완료 표시를 해두면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 메시지 큐에서 전달응답을 보낼 수 없는 경우에는 6,7 단계를 생략하고 수집 단계에 영속 저장소에서 삭제하는 방식을 사용합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;HML((Hybrid Message Logging))&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HML은 RBML과 SBML의 장점을 최대한 활용하도록 설계되었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 3가지 로깅구조에서 하나만 선택해야한다면 HML을 선택하는게 일반적인 정답입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HML에서는 메시지를 저장할 때 저장을 비동기로 처리한다는 특징이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;1154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O9u8h/btsNLpkrIAm/BrAwEPybF43LBN1B4ao7vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O9u8h/btsNLpkrIAm/BrAwEPybF43LBN1B4ao7vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O9u8h/btsNLpkrIAm/BrAwEPybF43LBN1B4ao7vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO9u8h%2FbtsNLpkrIAm%2FBrAwEPybF43LBN1B4ao7vK%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;1974&quot; height=&quot;1154&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;1154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;메시지 큐 단계&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgFjdN/btsNSf9HJA1/ekkHvJSKyEXOXmGAQtG590/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgFjdN/btsNSf9HJA1/ekkHvJSKyEXOXmGAQtG590/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgFjdN/btsNSf9HJA1/ekkHvJSKyEXOXmGAQtG590/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgFjdN%2FbtsNSf9HJA1%2FekkHvJSKyEXOXmGAQtG590%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;854&quot; height=&quot;220&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 설게에서는 서버들간의 디커플링이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버들간의 통신 및 디커플링을 위해&amp;nbsp;'메시지 큐'를 많이 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'메시지 큐'를 통해 수집단계와 분석단계의 커플링을 끊고 통신을 할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;프로듀서, 브로커, 컨슈머&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2jlOH/btsNQYVoUsO/OGOFwOkl9YR33NAe8mEcTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2jlOH/btsNQYVoUsO/OGOFwOkl9YR33NAe8mEcTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2jlOH/btsNQYVoUsO/OGOFwOkl9YR33NAe8mEcTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2jlOH%2FbtsNQYVoUsO%2FOGOFwOkl9YR33NAe8mEcTk%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;1770&quot; height=&quot;664&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 큐는 프로듀서, 브로커, 컨슈머로 구성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브로커는 실제 메시지 큐들을 관리하는 추상화된 논리적 클러스터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 큐 단계에서는 위 3가지 구성요소들끼리 통신합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 큐는 아래와 같은 순서대로 동작합니다.&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;브로커는 큐에 메시지를 넣는다.&lt;/li&gt;
&lt;li&gt;컨슈머는 브로커로부터 메시지를 읽는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;메시지의 지속적저장 ((Durable Messaging))&lt;/h2&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;메시지의 지속적 저장((Durable Messaging))은 컨슈머가 데이터를 천천히 읽어가거나 일시적으로 컨슈머의 연결이 끊기더라도 다시 데이터를 읽어갈 수 있는 방법입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 메시지의 지속적 저장이 가능한 큐를 Durable Queue라고 합니다.&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;하지만 Durable Queue는 메시지를 지속적 저장소((파일시스템. JDBC, 전용 시스템))등에 백업해두기에 메시지의 소실을 예방 할 수 있습니다.&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;RabbitMQ는 Queue 생성 시 durable=true, 메시지 deliveryMode=2 설정해야 디스크 저장이 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka는 기본적으로 모든 메시지 로그는 디스크 저장됩니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;메시지의 전달 시맨틱 ((Messaging Delivery Semantics))&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지가 컨슈머에게 어떻게 전달되고, 몇 번이나, 어떤 조건에서 도착해야 하는지&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;규칙입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 최대 한 번 ((At-most-once))&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 메시지가 유실될 수 잇습니다. 유실된 메시지는 컨슈머에 도달하지 못합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 적어도 한 번 ((At-least-once))&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지가 절대 유실되지 않습니다. 그러나 컨슈머에서 동일 메시지를 두번 이상 처리할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 정확히 한 번 ((Exactly-once))&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 메시지는 절대로 유실되지 않으며, 컨슈머는 반드시 한 번만 메시지를 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로는 '정확히 한 번'이 당연히 가장 이상적이고 완벽한 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 '정확히 한 번'을 구현하는건 어려운 일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 사실상 각 큐의 요소간 모든 통신은 유실위험이 존재하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'정확히 한 번'을 구현하려면 큐의 각 요소에서 메시지 소실 및 재전송이 발생 할 수 있는 상황들을 알고 고려해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(('정확히 한 번'은 매번 정답이 아니며 비즈니스에 따라 적절한 시맨틱을 선택해야합니다. 실제로 카프카도 0.11.0.0 버전 이전까지는 정확히 한 번 기능을 제공하지 않았습니다.&lt;/b&gt;&lt;b&gt;))&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqf3gP/btsNRg2K8A3/CFgldK9kKKLPuGOlI3cWLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqf3gP/btsNRg2K8A3/CFgldK9kKKLPuGOlI3cWLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqf3gP/btsNRg2K8A3/CFgldK9kKKLPuGOlI3cWLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdqf3gP%2FbtsNRg2K8A3%2FCFgldK9kKKLPuGOlI3cWLK%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;1800&quot; height=&quot;484&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;'정확히 한 번'을 위해 고려해야하는 부분들&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'정확히 한 번'을 구현하기 위해서는 각 요소간에서 발생할 수 있는 문제들을 미리 알아두어야합니다.&lt;/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;&lt;b&gt;프로듀서에서 위험요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로듀서 내부에서 메시지가 생성된 후 네트워크 통신을 통해 브로커로 보내기 직전 프로듀서 장애 발생 시 메시지 유실가능&lt;/li&gt;
&lt;li&gt;프로듀서가 정상적으로 메시지를 보냈지만 브로커에서 메시지를 전달받았다는 응답을 받지 못하면 동일한 메시지가 브로커로 한번 더 전달 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;프로듀서와 브로커 간 네트워크 통신간 위험요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로듀서와 브로커 간 네트워크 사이 통신에 이슈가 발생하면 프로듀서는 브로커로 메시지를 보내지 못함&lt;/li&gt;
&lt;li&gt;브로커가 메시지를 저장했지만 저장 완료되었다는 응답을 프로듀서에 보내지 못하면 동일한 메시지가 두 번 전달될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;브로커에서의 위험요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브로커에 장애가 발생하면 저장소에 저장하기 직전에 메모리가 가지고 있던 메시지가 유실 될 수 있음&lt;/li&gt;
&lt;li&gt;프로듀서로 메시지를 전달받았다고 응답을 보내기 전에 장애가 발생하면 프로듀서는 동일한 메시지를 두 번 보낼 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메시지 큐에서의 위험요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장소에 장애가 발생하면 디스크에 저장된 메시지 중 일부가 유실될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;컨슈머와 브로커 간 네트워크 통신간 위험요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨슈머와 브로커 간 네트워크 이슈 발생 시, 브로커가 컨슈머로 메시지를 보낼 수 없음&lt;/li&gt;
&lt;li&gt;컨슈머가 처리한 마지막 메시지 정보가 브로커에 전달되지 않으면 동일한 메시지가 두 번 이상 컨슈머에서 처리될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;컨슈머에서의 위험요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브로커로부터 메시지를 전달 받은 후, 데이터를 처리하기 전에 장애가 생기거나 커밋정보를 브로커로 전달하지 못하면 동일한 메시지를 두번 이상 처리할수도 있습니다.&lt;/li&gt;
&lt;li&gt;여러 컨슈머가 동일한 메시지를 여러 번 읽는 경우도 발생할 수 있습니다.&lt;/li&gt;
&lt;/ul&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;아파치 카프카, 아파치 Active MQ의 메시징 시스템에서도 이렇게 메타데이터를 이용해 정확히 한 번 처리를 구현합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUg0a5/btsNSt05FjA/UD2vtEFJAAKprJKsEsBUY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUg0a5/btsNSt05FjA/UD2vtEFJAAKprJKsEsBUY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUg0a5/btsNSt05FjA/UD2vtEFJAAKprJKsEsBUY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUg0a5%2FbtsNSt05FjA%2FUD2vtEFJAAKprJKsEsBUY1%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;1998&quot; height=&quot;608&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&lt;b&gt;만약 프로듀서와 브로커 사이 응답이 유실되거나 연결이 끊기면, 프로듀서가 이전에 보낸 데이터를 브로커가 정상적으로 받았는지 브로커로 요청하고 확인응답((Acknowledgement))를 받습니다.&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;이때 프로듀서가 메시지에 고유 ID((메타데이터))을 붙여 보내게 되면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;브로커는 고유 ID를 통해 프로듀서가 이전과 동일한 메시지를 보냈는지, 즉 중복된 메시지를 보냈는지 판단하여 프로듀서가 메시지를 두 번 보내지 않도록 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;마지막으로 처리한 메시지의 메타데이터 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장해야하는 메타데이터의 종류는 메시징 시스템에 따라 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JMS 기반 시스템을 사용할 경우 JMS의 메시지ID를 사용하면 됩니다.&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;결국 중복해서 처리하지 않기 위한 메시지 구분을 위한 메타데이터를 저장하면 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;또한 메타데이터는 장애복구 시 컨슈머가 어디서부터 메시지를 읽을지 기억하는 기준이 되기도 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;메시지 큐 단계에서의 장애처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애처리는 어떤 단계에서든 중요한 부분입니다.&lt;/p&gt;
&lt;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;1826&quot; data-origin-height=&quot;1272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYbzjZ/btsNYn6CuXF/3ip11KBvyWvy7lGZLbvkgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYbzjZ/btsNYn6CuXF/3ip11KBvyWvy7lGZLbvkgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYbzjZ/btsNYn6CuXF/3ip11KBvyWvy7lGZLbvkgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYbzjZ%2FbtsNYn6CuXF%2F3ip11KBvyWvy7lGZLbvkgk%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;1826&quot; height=&quot;1272&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;1272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;브로커에서 장애 위험요소 및 처리방안&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브로커가 내부 저장소에 저장하기 직전에 장애가 발생 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 메모리에 가지고 있던 메시지를 처리하는 방법은 3가지 정도가 있을 것 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로듀서와 브로커간 확인응답(Acknowledgment)를 활용(메시지 전달 시멘틱 방법과 동일!)&lt;/li&gt;
&lt;li&gt;큐가 별도의 브로커로 메시지를 복제하여 백업&lt;/li&gt;
&lt;li&gt;메시지를 저장하는 메모리의 사용률을 줄이고 저장소에 저장하도록 변경 (속도저하 가능성 있음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;브로커 통신간 장애 위험요소 &lt;b&gt;및 처리방안&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐 소프트웨어들은 최소 1개 이상의 브로커에 메시지를 저장하여 안전하게 저장되도록하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브로커간 네트워크의 문제가 발생했을때도 복구 이후 각 브로커에 저장된 메시지를 동기화 하는 기능이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;큐를 직접 구현하는게 아니라면 '브로커 통신간 네트워크 장애 발생 시 또는 복구 시' 큐 소프트웨어가 어떻게 동작하는지 확인한 다음,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비즈니스에 맞는 몇가지 기준을 만들고 그에 적합한 큐 소프트웨어를 선택해야합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;저장소에서 장애 위험요소 &lt;b&gt;및 처리방안&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장소에서 유실된 데이터에 대한 백업을 만들어 두어야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;분석 단계 ((스트림 프로세싱 아키텍쳐))&lt;/h2&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-origin-width=&quot;862&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1qlcd/btsNVDKvMAC/GVMKWYWUaIwtLgjSV7OLL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1qlcd/btsNVDKvMAC/GVMKWYWUaIwtLgjSV7OLL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1qlcd/btsNVDKvMAC/GVMKWYWUaIwtLgjSV7OLL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1qlcd%2FbtsNVDKvMAC%2FGVMKWYWUaIwtLgjSV7OLL1%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;682&quot; height=&quot;350&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전통적인 DBMS와 스트리밍 시스템의 데이터 접근에 대한 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 전통적인 DBMS와 스트리밍 시스템의 데이터 접근에 대한 차이입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스트리밍 시스템에서는 사용자가 쿼리를 한번 등록하면 결과값은 지속적으로 클라이언트에게 푸시됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트리밍 분석은 사용자 행동 추적, 실시간 분석 등 여러 비즈니스 요구사항을 풀 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2792&quot; data-origin-height=&quot;1366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSxL3M/btsNV1xA4wU/kWz8VjoY6LYwYQwkLcpwuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSxL3M/btsNV1xA4wU/kWz8VjoY6LYwYQwkLcpwuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSxL3M/btsNV1xA4wU/kWz8VjoY6LYwYQwkLcpwuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSxL3M%2FbtsNV1xA4wU%2FkWz8VjoY6LYwYQwkLcpwuk%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;2792&quot; height=&quot;1366&quot; data-origin-width=&quot;2792&quot; data-origin-height=&quot;1366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;인플라이트 데이터(In-flight Data)와 연속 쿼리(Continuous quries)&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인플라이트 데이터는 시스템의 파이프라인 속에서 이동 중이거나 처리 중인 데이터를 의미합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;시스템에 따라 인플라이트 데이터는 디스크에 저장되거나 사용 후 소멸되기도 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;연속쿼리는 시스템에서 실시간으로 들어오는 데이터에 대해 즉시 실행되고, 그 실시간 결과를 지속적으로 반환하는 쿼리를 의미합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉 데이터가 들어올 때 마다 자동으로 실행되는 쿼리입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스트리밍 시스템을 사용하는 사용자 입장에서 생각해보면 스트리밍 시스템의 쿼리(연속쿼리)를 실행하면 데이터가 지속적으로 들어오게 됩니다. 이러한 쿼리들을 조합한 일련의 형태 모델을 '연속 쿼리 모델'((Continuous query model))이라 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 쿼리로 추출된 데이터는 다음 단계로 PUSH됩니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;분산 스트림 프로세싱 아키텍쳐&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;분석 단계는 많은 리소스를 필요로 하는 단계이기에 여러 서버를 사용하는 분산 시스템이 동반된 아키텍쳐를 도입해야합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분석 단계에서 많이 사용되는 스트림 프로세싱 소프트웨어는 아파치 스파크, 스톰, 플링크 등이 있습니다.&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;b&gt;인기있는 스트리밍 어플리케이션의 구조를 일반화 해서 그려보면 아래와 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q7i7o/btsNV4nKGG7/a4XWZkkESjOTkIyHkLjKRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q7i7o/btsNV4nKGG7/a4XWZkkESjOTkIyHkLjKRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q7i7o/btsNV4nKGG7/a4XWZkkESjOTkIyHkLjKRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq7i7o%2FbtsNV4nKGG7%2Fa4XWZkkESjOTkIyHkLjKRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;766&quot; height=&quot;418&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;956&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어플리케이션 드라이버&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 작성한 스트리밍 job 코드가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성한 job코드는 스트리밍 매니저와 연동되어 실행되어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;job의 결과도 수집하며 이를 활용하여 통해 job의 생명주기 또한 관리합니다.&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;job을 할당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때떄로 스트림 프로세서가 필요로하는 리소스를 요청하거나 제어 할 수 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;스트림 프로세서&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원천 데이터를 받아서 필터링, 집계, 패턴 감지 등 비즈니스 로직이 실행됩니다. (작성한 job 로직이 실행되는 장소)&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;스트림 프로세싱 프레임워크의 핵심 기능&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;메시지 관리 시맨틱&lt;/h4&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;원천 데이터와 스트림 프로세서간 통신에서도 동일한 문제가 나타 날 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그렇기에 스트림 프레임워크에서도 최대 한 번, 적어도 한 번, 정확히 한 번 등의 시맨틱을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;동일하게 비즈니스에 맞는 시맨틱을 선택하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;상태 관리 ((State management))&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림 시스템에서 계속 흘러가는 데이터들이 외부 데이터와 연동되거나 이전에 처리된 메시지를 참조하는 등 데이터를 다루는 로직이 복잡해지면, 프로세서가 데이터의 상태(State)를 기억하여 값을 안전하게 관리해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 '방문자의 지난 한 시간 동안 페이지 수 뷰'를 추출해야 한다면 시스템은 '해당 사용자가 한 시간 동안 방문한 페이지 수 데이터'의 값, 즉 상태를 유지해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트리밍 프레임워크는 순수 인 메모리 ((In-Memory))를 활용하는 방법에서 복제 기반 쿼리 영구 저장소 ((Replicated Queryable Persistent Storage))를 활용하는 등 몇가지 상태관리 기능을 제공합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;장애 허용 설계  ((Falut tolerance))&lt;/h3&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;1590&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cW1n7g/btsNV7Ly5s0/S1Jb73wyBgjkKx7ZkpSXX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cW1n7g/btsNV7Ly5s0/S1Jb73wyBgjkKx7ZkpSXX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cW1n7g/btsNV7Ly5s0/S1Jb73wyBgjkKx7ZkpSXX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcW1n7g%2FbtsNV7Ly5s0%2FS1Jb73wyBgjkKx7ZkpSXX0%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;1590&quot; height=&quot;826&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스트림 데이터 입수, 네트워크 통신, 저장소, 어플리케이션 드라이버에서 장애가 발생하면?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 4가지 상황에서의 장애는 프레임워크가 통제할 수 있는 상황은 아니지만 장애를 대응 할 수 있는 방법을 만들어 두는것이 좋습니다.&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;b&gt;스트림 프로세싱 프레임워크는 메시지 큐 단계의 데이터를 사용할 수 없거나 접근이 불가능할 경우에도 정상적으로 동작해야합니다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리고 네트워크, 저장소, 어플리케이션 드라이버 장애 또한 동일하게 스트림 프로세스 시스템에 영향을 미칠 수 있기에 적잘한 장애 대응 방법을 고려하는것이 좋습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;스트림 프로세서에서 장애가 발생하면?&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드가 실행되는 부분이기에 작성한 코드에서나 소프트웨어 서버에 장애가 발생 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장애가 발생하면 스트리밍 매니저는 프로세서들을 다시 실행하거나 다른 서버로 프로세스를 옮겨 실행해야합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;헤드리스 실행((&lt;b&gt;Running headless&lt;/b&gt;))이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매니저에 장애가 발생하는 상황을 '헤드리스 실행(Running headless)' 이라고 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스트리밍 매니저의 통제 없이 스트림 프로세서가 계속해서 실행되는 상황을 의미합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 매니저에 장애가 발생하면 프로세서의 통제가 되지 않기에 신규 프로세서 생성이나 장애발생 프로세스 복구가 불가능합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;k-내결함성((k-fault tolerant))&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장애를 다루기 위한 모든 기술은 복제(Replication)과 조정(Coordination)을 기반으로 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림 매니저가 계산 중인 데이터의 상태를 다른 스트림 프로세서로 복제하고, 장애가 발생하면 미리 복제해둔 데이터를 활용하여 복구합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;k-내결함성 시스템에서 k는 장애를 허용하는 노드의 수를 말하며, 분산시스템에서는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;투표를 위해 일반적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;2k+1 의 노드가 있어야합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;상태 머신((State-machine))과 롤백복구((Rollback recovery))&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;자원이 들지만 빠르게 복구가 가능하기에 침입 감지 시스템과 같은 서비스에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 롤백 복구 방식은 스트림 프로세서가 주지거으로 상태를 체크포인트로 다른 스트림 프로세서 노드 또는 디스크와 같은 비휘발성 데이터에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에 저장하기에 상대적으로 지연이 발생 할 수 있기에 내결함성이 중요하고 어느정도 지연이 허용되는 상황에 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 단계((저장, 접근))는 다음 포스팅에서 다루겠습니다!&lt;/p&gt;</description>
      <category>데이터 파이프라인 아키텍쳐</category>
      <category>스트리밍 데이터 아키텍쳐</category>
      <category>스트리밍 아키텍쳐</category>
      <category>실시간 스트리밍 데이터</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/101</guid>
      <comments>https://jaykos96.tistory.com/101#entry101comment</comments>
      <pubDate>Sun, 4 May 2025 00:20:10 +0900</pubDate>
    </item>
    <item>
      <title>핵심 데이터 모델링 후기</title>
      <link>https://jaykos96.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터 읽었던 책들에 대해서 블로그에 간단히 개인적인 후기를 남겨놓으려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 첫 독서 후기는 유동오님의 책 '&lt;b&gt;핵심 데이터 모델링'&lt;/b&gt;&amp;nbsp;후기입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;796&quot; data-origin-height=&quot;1104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mrsAc/btsNfGzbHaB/2Mh5ooGBpk5yopJZkVoJF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mrsAc/btsNfGzbHaB/2Mh5ooGBpk5yopJZkVoJF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mrsAc/btsNfGzbHaB/2Mh5ooGBpk5yopJZkVoJF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmrsAc%2FbtsNfGzbHaB%2F2Mh5ooGBpk5yopJZkVoJF0%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;324&quot; height=&quot;449&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;1104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;하지만 그럴 때마다 저는 그저 &quot;잘 되어 있겠지&quot;, &quot;원래 이렇게 쓰는 거겠지..&quot; 하면서 명확한 기준이 없는 상태로 사내 데이터 모델링들이&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(쓰다보니까&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;생각한거보다 글이 조금 장황해지는 느낌이 있는데..ㅠ 그냥 해보겠습니다.)&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;후기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ER 모델과 기본적인 정규화 이론들 부터 시작해서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 - 논리 - 물리 모델링 방법론들을 적절한 데이터들을 통해 설명해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 DA컨설턴트 입장에서 클라이언트와 잘 일하기 위한 노하우(?)들도 많이 적혀있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적인 이론보다 실제 데이터(금융, 이커머스)들을 예시로 설명해줘서 익숙한 느낌의 설계들도 많이 보였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통코드 설계와 이력관리 테이블 반정규화 방법론 등 도메인 데이터가 아니지만, 필수적인 데이터들에 대한 내용들이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 보고 배워왔던 방법들과 비슷하게 기술되어있는걸 보고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘날의 데이터 모델링은 '다양한 시스템 속에서 많은 사람들이 만들어놓고 안정시켜 놓은 방법이구나'라는 생각이 들었습니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;개인적으로 아쉬운점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리 모델링 설계에 대한 내용들이 RDB 설계에만 국한되어 있고,&lt;br /&gt;NoSQL 기반 DB나 데이터 웨어하우스(DW) 환경에서의 모델링 접근 방식은 다뤄지지 않아 약간 아쉬웠습니다.&lt;br /&gt;최근처럼 다양한 형태의 최근처럼 다양한 형태의 저장소와 데이터 구조가 공존하는 환경에서의 모델링 사례나 설계 전략도 함께 소개되었으면 더 좋았을 것 같습니다.&lt;/p&gt;</description>
      <category>독서 후기</category>
      <category>독서 후기</category>
      <category>핵심 데이터 모델링</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/100</guid>
      <comments>https://jaykos96.tistory.com/100#entry100comment</comments>
      <pubDate>Wed, 9 Apr 2025 21:26:55 +0900</pubDate>
    </item>
    <item>
      <title>RedBlackTree에 대해서 알아보자</title>
      <link>https://jaykos96.tistory.com/99</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;RedBlackTree란?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpNMHK/btsM4spvr2Y/iUhAYxLejbYnwlGEkCRSc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpNMHK/btsM4spvr2Y/iUhAYxLejbYnwlGEkCRSc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpNMHK/btsM4spvr2Y/iUhAYxLejbYnwlGEkCRSc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpNMHK%2FbtsM4spvr2Y%2FiUhAYxLejbYnwlGEkCRSc1%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;628&quot; height=&quot;316&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 탐색 트리를 사용하다 보면, 데이터들이 불균형하게 생성되는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불균형한 상태의 이진 트리는 검색효율을 심각하게 저하시키는 원인이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Red Black Tree는 이진트리에 몇가지 규칙들을 더 사용하여 데이터를 균형있게 쌓음으로서, 트리의 높이가 최소한으로 유지되게끔 하여&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;균형잡힌 구조 덕분에 Red Black Tree는 시간복잡도 O(LogN)을 유지합니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RedBlackTree의 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Red Black Tree의 특징은 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o1Eiq/btsM6p5TlL2/TnbkwEu1thlRD7eTiL0Kj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o1Eiq/btsM6p5TlL2/TnbkwEu1thlRD7eTiL0Kj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o1Eiq/btsM6p5TlL2/TnbkwEu1thlRD7eTiL0Kj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo1Eiq%2FbtsM6p5TlL2%2FTnbkwEu1thlRD7eTiL0Kj0%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;583&quot; height=&quot;408&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;/figure&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;Root노드는 검은색이다&lt;/li&gt;
&lt;li&gt;Leaf노드는 검은색이다.&lt;/li&gt;
&lt;li&gt;빨간색 노드의 자식들은 모두 검은색이다. (검은색 노드는 빨간색, 검은색 노드 모두 자식으로 가질 수 있다.)&lt;/li&gt;
&lt;li&gt;Root노드와 Leaf노드 사이에 있는 검은색 노드의 수는 모두 동일하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리고 그림을 보면 모든 리프노드가 'NIL'로 되어있는데, 이러한 NIL노드들을 센티넬 노드라고도 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아무 데이터도 가지고 있지 않지만 색깔만 검은색인 더미 노드를 의미합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현을 용이하게 만들기 위해 존재하는 노드이며, 규칙 3번을 지키기 위해서 존재하는 노드입니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RedBlackTree의 사용처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입/삭제가 빈번한 환경에서 뛰어난 성능을 보여줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 대표적으로는 &lt;b&gt;리눅스 커널의 CFS (Completetly Fair Scheduler), 즉 공정스케줄러에서 프로세스의 실행시간을 정렬해서 적절하게 cpu에 배분할때도 Red Black Tree를 사용합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;또한 자바의 TreeMap과 TreeSet,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;C++의 STD (std::map, std::set, std::multimap, std::multiset)에서도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;키값들이 자동 정렬되는데 내부적으로 RedBlackTree를 사용합니다.&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RedBlackTree의 높이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;h(x)는 x자신으로 부터 leaf노드까지의 가장 긴 간선(edge)의 거리입니다. (트리의 깊이)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;bh(x)는 x부터 leaf노드까지의 경로상의 '검은노드의 수'입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;1118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qXntF/btsM8s9bNzF/o47ZQQNXOgrrt7NjDXXkCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qXntF/btsM8s9bNzF/o47ZQQNXOgrrt7NjDXXkCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qXntF/btsM8s9bNzF/o47ZQQNXOgrrt7NjDXXkCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqXntF%2FbtsM8s9bNzF%2Fo47ZQQNXOgrrt7NjDXXkCK%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;671&quot; height=&quot;430&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;1118&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;RedBlackTree의 4번 규칙에따라 h(x) &amp;gt;= hx(x)가 성립하게되며, (red노드는 Black노드 사이에는 하나밖에 올 수 없기에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 트리의 최소 노드 수(n)는 n &amp;gt;= 2^bh(x) - 1이라는 식을 만들 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1743680004487&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n &amp;ge; 2^bh(x) - 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래와 같은 부등식도 얻을 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1743680519282&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bh(x) &amp;le; h(x) &amp;le; 2 &amp;times; bh(x)&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;이후 n에 대한 식을 bh(x)에 대한 식으로 변형한다음&lt;/p&gt;
&lt;pre id=&quot;code_1743680158017&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n &amp;ge; 2^bh(x) - 1
n + 1 &amp;ge; 2^bh(x)
log₂(n + 1) &amp;ge; bh(x)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부등식에 대입해주면 높이를 구하는 공식을 얻을 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1743680251416&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;h(x) &amp;le; 2 &amp;times; log₂(n + 1)&lt;/code&gt;&lt;/pre&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;Red Black Tree는 기본적으로 이진트리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 탐색 알고리즘은 이진트리의 알고리즘을 거의 동일하게 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이진트리의 노드의 삽입, 삭제와 같은 로직에서 Red Black Tree의 규칙을 위배하게되는 상황이 발생하기에 그 무너진 규칙들을 바로잡아주는 과정들이 Red Blakc Tree에 필요합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회전 (Rotate)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Red Black Tree의 규칙을 재정비하기 위한 하나의 방법으로&amp;nbsp;'회전(rotate)'이 필요합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMum6L/btsNiznJwdZ/vVDKfmN27uqREEIG8I9pr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMum6L/btsNiznJwdZ/vVDKfmN27uqREEIG8I9pr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMum6L/btsNiznJwdZ/vVDKfmN27uqREEIG8I9pr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMum6L%2FbtsNiznJwdZ%2FvVDKfmN27uqREEIG8I9pr1%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;1692&quot; height=&quot;640&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우회전 : 왼쪽자식을 부모의 위치 이동시키면서 우회전, 이후 왼쪽 자식노드의 오른쪽 자식노드를 원래 부모노드의 왼쪽 자식으로 연결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;좌회전 : 오른쪽 자식을 부모의 위치로 이동시키면서 좌회전, 이후&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;오른쪽 자식노드의 왼쪽 자식노드를 원래 부모노드의 오른쪽 자식으로 연결합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;노드 삽입 (Insert)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레드 블랙 트리에 삽입되는 &lt;b&gt;새 노드는 빨간색이어야하고 NIL노드를 새 노드의 양쪽 자식으로 연결&lt;/b&gt;해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 NIL노드는 모든 레드 블랙 트리의 실제 리프노드에 연결되어있기에, NIL노드에 계속해서 heap공간을 할당하지 않고 하나의 NIL노드를 계속 재활용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;364&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qDUk3/btsNhveq4Fo/ioHcrT1MZNE24I6MOq08Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qDUk3/btsNhveq4Fo/ioHcrT1MZNE24I6MOq08Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qDUk3/btsNhveq4Fo/ioHcrT1MZNE24I6MOq08Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqDUk3%2FbtsNhveq4Fo%2FioHcrT1MZNE24I6MOq08Fk%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;364&quot; height=&quot;378&quot; data-origin-width=&quot;364&quot; data-origin-height=&quot;378&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;새로운 노드를 삽입하는 과정에서 신경써야하는 레드 블랙 트리의 4번 규칙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 빨간색 노드의 자식들은 모두 검은색이다. (검은색 노드는 빨간색, 검은색 노드 모두 자식으로 가질 수 있다.)&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;4번 규칙이 위배되는 상황은 '&lt;b&gt;삽입된 노드의&lt;span&gt;&amp;nbsp;&lt;/span&gt;부모가&lt;/b&gt;&lt;span&gt;&lt;b&gt; 빨간색&lt;/b&gt;'일 떄 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에 트리의 규칙을 유지하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;색변경과 회전(Rotate)&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;삼촌노드도 빨간색인 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;삼촌노드가 검은색이며, 새로 삽입한 노드가 부모노드의 오른쪽 자식인 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;삼촌노드가 검은색이며, 새로 삽입한 노드가 부모노드의 왼쪽 자식인 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입 연산의 모든 예시에서는 부모노드가 조부모노드의 왼쪽 자식이라고 가정합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;부모노드가 오른쪽 자식일때는 아래 설명에서 왼쪽과 오른쪽을 바꾸어서 생각하면 됩니다!&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1. 삼촌노드도 빨간색인 경우&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;빨갛게 칠해진 조부모노드가 다시 4번 규칙을 위배 할 수 있기에 새로 삽입한 노드로 간주하고 다시 트리의 재구성 작업 시행&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rGqa6/btsNhvlPsPO/QxXWhj9Td3lPTr56xtNtrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rGqa6/btsNhvlPsPO/QxXWhj9Td3lPTr56xtNtrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rGqa6/btsNhvlPsPO/QxXWhj9Td3lPTr56xtNtrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrGqa6%2FbtsNhvlPsPO%2FQxXWhj9Td3lPTr56xtNtrK%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;815&quot; height=&quot;364&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. 삼촌노드가 검은색이며 새로 삽입한 노드가 부모노드의 왼쪽 자식인 경우&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 노드를 검은색, 조부모노드를 빨간색으로 칠한 다음 할아버지 노드를 우회전시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfJqqD/btsNlZkQ6hj/W39RVtEi6IxtjqLUL9ZiF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfJqqD/btsNlZkQ6hj/W39RVtEi6IxtjqLUL9ZiF1/img.png&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;554&quot; data-is-animation=&quot;false&quot; style=&quot;width: 63.3844%; margin-right: 10px;&quot; data-widthpercent=&quot;64.13&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfJqqD/btsNlZkQ6hj/W39RVtEi6IxtjqLUL9ZiF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfJqqD%2FbtsNlZkQ6hj%2FW39RVtEi6IxtjqLUL9ZiF1%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;1906&quot; height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzsWJE/btsNlm2btWy/tNfh7GxxqIshYT2pVc2WNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzsWJE/btsNlm2btWy/tNfh7GxxqIshYT2pVc2WNk/img.png&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;608&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.4529%;&quot; data-widthpercent=&quot;35.87&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzsWJE/btsNlm2btWy/tNfh7GxxqIshYT2pVc2WNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzsWJE%2FbtsNlm2btWy%2FtNfh7GxxqIshYT2pVc2WNk%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;1170&quot; height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3. 삼촌노드가 검은색이며 새로 삽입한 노드가 부모노드의 오른쪽 자식인 경우&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모노드를 좌회전시켜 2번과 동일한 상황으로 만듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1wq4d/btsNkVc8AMj/IqUfNFOBennyPRtuXrhDV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1wq4d/btsNkVc8AMj/IqUfNFOBennyPRtuXrhDV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1wq4d/btsNkVc8AMj/IqUfNFOBennyPRtuXrhDV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1wq4d%2FbtsNkVc8AMj%2FIqUfNFOBennyPRtuXrhDV0%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;1544&quot; height=&quot;618&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;노드 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제연산은 삽입에 비해 조금 까다롭습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드가 삭제되면 왼쪽 자식노드는 부모노드보다 작아야하고, 오른쪽 자식노드듣 부모노드보다 커야한다는 기본적인 이진트리 규칙에 더해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레드 블랙 트리의 규칙을 위한 후속조치도 필요하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행인 부분은 빨간색 노드가 삭제되면 트리의 어떤 규칙도 위배되지 않기에 검은색 노드가 삭제되었을때만 고려하면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제된 노드가 검은색일때 삭제 연산이 무너뜨릴 수 있는 첫번째 규칙은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5번 'Root노드와 Leaf노드 사이에 있는 검은색 노드의 수는 모두 동일하다' 입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 삭제된 노드의 부모와 자식이 모두 빨간색이면 &lt;b&gt;4번 '빨간색 노드의 자식들은 모두 검은색이다.'&lt;/b&gt; 규칙도 위배됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcr1HM/btsNpvdj14u/a83tk0uf8bXqcziKOFruB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcr1HM/btsNpvdj14u/a83tk0uf8bXqcziKOFruB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcr1HM/btsNpvdj14u/a83tk0uf8bXqcziKOFruB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcr1HM%2FbtsNpvdj14u%2Fa83tk0uf8bXqcziKOFruB1%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;751&quot; height=&quot;346&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 4번, 5번 규칙을 지키기 위해 트리는 삭제된 노드를 대체하는 노드를 검은색으로 칠함으로 4번, 5번 규칙을 지킬 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4gc8R/btsNpsvglwo/4uXXP96WXeKFkxS4RGY0fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4gc8R/btsNpsvglwo/4uXXP96WXeKFkxS4RGY0fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4gc8R/btsNpsvglwo/4uXXP96WXeKFkxS4RGY0fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4gc8R%2FbtsNpsvglwo%2F4uXXP96WXeKFkxS4RGY0fk%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;749&quot; height=&quot;337&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 자식노드가 검은색이라면, 4번 규칙은 위배되지 않고 5번 규칙만 위배됩니다.&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;검은색이 두번 칠해진 노드를 '이중 흑색 노드'라고 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qXIaR/btsNpZr2M5p/075uMGK5HQiKTMzCjjgTzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qXIaR/btsNpZr2M5p/075uMGK5HQiKTMzCjjgTzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qXIaR/btsNpZr2M5p/075uMGK5HQiKTMzCjjgTzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqXIaR%2FbtsNpZr2M5p%2F075uMGK5HQiKTMzCjjgTzK%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;819&quot; height=&quot;379&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;788&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;이중 흑색 노드가 생기면 1번 규칙 &lt;b&gt;'모든 노드는 빨간색이거나 검은색이다.' &lt;/b&gt;규칙이&amp;nbsp;무너지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 처리하는 방법은 네 가지 경우로 나뉩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;형제가 빨간색인 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형제가 검은색이고 형제의 양쪽 자식이 모두 검은색인 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형제가 검은색이고 자식은 빨간색, 오른쪽 자식은 검은색인 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형제가 검은색이고, 형제의 오른쪽 자식이 빨간색인 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이중 흑색 노드 처리의 모든 예시에서는 흑색노드가 부모노드의 왼쪽 자식인 경우에 한합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오른쪽 자식인 경우에는 아래 설명에서 왼쪽과 오른쪽을 바꾸어서 생각하면 됩니다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;루트노드가 이중 흑색 노드가 된다면 검은색 노드로 처리합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1. 형제가 빨간색인 경우&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;부모를 기준으로 자식노드를 좌회전 시킵니다.&lt;/li&gt;
&lt;li&gt;아직 이중 흑색 노드는 그대로지만 형제 노드는 검은색으로 바뀝니다.&lt;/li&gt;
&lt;li&gt;이중 흑색 노드는 '검은색 형제 노드 처리 방법 (2,3,4)' 유형에 맞게 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I3cir/btsNpUKIeTL/QJoqNbCzoH2RDQ1WofWff0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I3cir/btsNpUKIeTL/QJoqNbCzoH2RDQ1WofWff0/img.png&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;790&quot; data-is-animation=&quot;false&quot; width=&quot;778&quot; height=&quot;331&quot; data-widthpercent=&quot;59.56&quot; style=&quot;width: 58.8667%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I3cir/btsNpUKIeTL/QJoqNbCzoH2RDQ1WofWff0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI3cir%2FbtsNpUKIeTL%2FQJoqNbCzoH2RDQ1WofWff0%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;1858&quot; height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daf3Yh/btsNqfH98Hd/U4fcxazVLJ5rHoKgsmYDRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daf3Yh/btsNqfH98Hd/U4fcxazVLJ5rHoKgsmYDRk/img.png&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;784&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;40.44&quot; width=&quot;720&quot; height=&quot;451&quot; style=&quot;width: 39.9705%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daf3Yh/btsNqfH98Hd/U4fcxazVLJ5rHoKgsmYDRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdaf3Yh%2FbtsNqfH98Hd%2FU4fcxazVLJ5rHoKgsmYDRk%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;1252&quot; height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. 형제가 검은색이고 형제의 양쪽 자식이 모두 검은색인 경우&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eTtkn0/btsNI67bif8/PS8dLaButPtn01Ec7l3Xck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eTtkn0/btsNI67bif8/PS8dLaButPtn01Ec7l3Xck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eTtkn0/btsNI67bif8/PS8dLaButPtn01Ec7l3Xck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeTtkn0%2FbtsNI67bif8%2FPS8dLaButPtn01Ec7l3Xck%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;802&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;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;이중 흑색 노드가 가지고 있던 검은색을 부모노드에게 넘겨줍니다.&lt;/li&gt;
&lt;li&gt;부모가 넘겨받은 검은색은 1,3,4 유형에 맞게 처리합니다. (부모노드가 빨간색이면 검은색으로 변경, 검은색이면 이중흑색으로 변경)&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2574&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wxTBV/btsNJPQOPpg/dJx0N8KbZDyYwnbTi3Cqm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wxTBV/btsNJPQOPpg/dJx0N8KbZDyYwnbTi3Cqm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wxTBV/btsNJPQOPpg/dJx0N8KbZDyYwnbTi3Cqm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwxTBV%2FbtsNJPQOPpg%2FdJx0N8KbZDyYwnbTi3Cqm1%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;2574&quot; height=&quot;1090&quot; data-origin-width=&quot;2574&quot; data-origin-height=&quot;1090&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3. 형제가 검은색이고 형제의 왼쪽 자식은 빨간색, 오른쪽 자식은 검은색인 경우&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1922&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bubQcU/btsNKtT30t8/JxpUkrMSRgyu06RWeJ8UkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bubQcU/btsNKtT30t8/JxpUkrMSRgyu06RWeJ8UkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bubQcU/btsNKtT30t8/JxpUkrMSRgyu06RWeJ8UkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbubQcU%2FbtsNKtT30t8%2FJxpUkrMSRgyu06RWeJ8UkK%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;1922&quot; height=&quot;780&quot; data-origin-width=&quot;1922&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;형제 노드를 빨간색으로 칠합니다.&lt;/li&gt;
&lt;li&gt;왼쪽 자식을 검은색으로 칠한다음, 형제 노드를 기준으로 왼쪽 자식을 우회전합니다.&lt;/li&gt;
&lt;li&gt;이중 흑색 노드는 그대로라면 1,2,4 유형에 맞게 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2702&quot; data-origin-height=&quot;1308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HlRKT/btsNJkYoBXb/e4BXJHH0kB5vgIReukyIik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HlRKT/btsNJkYoBXb/e4BXJHH0kB5vgIReukyIik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HlRKT/btsNJkYoBXb/e4BXJHH0kB5vgIReukyIik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHlRKT%2FbtsNJkYoBXb%2Fe4BXJHH0kB5vgIReukyIik%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;2702&quot; height=&quot;1308&quot; data-origin-width=&quot;2702&quot; data-origin-height=&quot;1308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;4. 형제가 검은색이고, 형제의 오른쪽 자식이 빨간색인 경우&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;부모 노드와 형제 노드의 오른쪽 자식노드를 검은색으로 칠합니다.&lt;/li&gt;
&lt;li&gt;부모노드를 기준으로 좌회전합니다.&lt;/li&gt;
&lt;li&gt;이중 흑색 노드의 흑색 하나를 루트노드에 넘겨 이중흑색을 처리합니다. (루트노드는 이중 흑색 노드가 되지 않습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2590&quot; data-origin-height=&quot;1290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHzGXS/btsNJtngnCW/ovobGdSUeKcKBL1EHhJYs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHzGXS/btsNJtngnCW/ovobGdSUeKcKBL1EHhJYs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHzGXS/btsNJtngnCW/ovobGdSUeKcKBL1EHhJYs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHzGXS%2FbtsNJtngnCW%2FovobGdSUeKcKBL1EHhJYs1%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;2590&quot; height=&quot;1290&quot; data-origin-width=&quot;2590&quot; data-origin-height=&quot;1290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2656&quot; data-origin-height=&quot;1370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lbPVJ/btsNLbFmNAp/gPxGqCrfqKf0ChkkpAGZl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lbPVJ/btsNLbFmNAp/gPxGqCrfqKf0ChkkpAGZl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lbPVJ/btsNLbFmNAp/gPxGqCrfqKf0ChkkpAGZl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlbPVJ%2FbtsNLbFmNAp%2FgPxGqCrfqKf0ChkkpAGZl0%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;2656&quot; height=&quot;1370&quot; data-origin-width=&quot;2656&quot; data-origin-height=&quot;1370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RedBlackTree 구현 코드 (C)&lt;/h2&gt;
&lt;pre id=&quot;code_1746201234583&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt; 

typedef int ElementType;

/*
 RedBlackTree 규칙

 1.모든 노드는 빨간색이거나 검은색이다.
 2.루트노드는 검은색이다
 3.리프노드는 검은색이다
 4.빨간색 노드의 자식은 모두 검은색이다.
 5.리프노드와 루트노드 사이에 있는 검은색 노드의 수는 모두 동일하다
*/
typedef struct tagRBTNode {
    struct tagRBTNode* Parent;
    struct tagRBTNode* Left;
    struct tagRBTNode* Right;
    enum {RED, BLACK} Color;
    ElementType Data;
} RBTNode;

RBTNode* Nil;

/* 노드 생성 */
RBTNode* RBT_CreateNode(ElementType NewData){
    RBTNode* NewNode = (RBTNode*)malloc(sizeof(RBTNode));
    NewNode-&amp;gt;Parent = NULL;
    NewNode-&amp;gt;Left = NULL;
    NewNode-&amp;gt;Right = NULL;
    NewNode-&amp;gt;Data = NewData;
    NewNode-&amp;gt;Color = BLACK;
    return NewNode;
}

/* 노드 삭제 */
void RBT_DestroyNode(RBTNode* Node) {
    free(Node);
}

/* 트리 삭제 */
void RBT_DestroyTree(RBTNode* Tree) {
    if (Tree-&amp;gt;Right != Nil){
        RBT_DestroyTree(Tree-&amp;gt;Right);
    }
    if (Tree-&amp;gt;Left != Nil){
        RBT_DestroyTree(Tree-&amp;gt;Left);
    }
    Tree-&amp;gt;Left = Nil;
    Tree-&amp;gt;Right = Nil;
    RBT_DestroyNode(Tree);
}

/* 노드 찾기 */
RBTNode* RBT_SearchNode(RBTNode* Tree, ElementType Target){
    if (Tree == Nil) return NULL;
    if (Tree-&amp;gt;Data &amp;gt; Target) {
        return RBT_SearchNode(Tree-&amp;gt;Left, Target);
    }
    else if (Tree-&amp;gt;Data &amp;lt; Target){
        return RBT_SearchNode(Tree-&amp;gt;Right, Target);
    }else {
        return Tree;
    }
}

/* 작은노드 찾기 */
RBTNode* RBT_SearchMinNode(RBTNode* Tree){
    if (Tree == Nil) {
        return NULL;
    }
    if (Tree-&amp;gt;Left == Nil) return Tree;
    else {
        return RBT_SearchMinNode(Tree-&amp;gt;Left);
    }
}

/* 우회전 */
void RBT_RotateRight(RBTNode** Root, RBTNode* Parent) {

    RBTNode* LeftChild = Parent-&amp;gt;Left;
    Parent-&amp;gt;Left = LeftChild-&amp;gt;Right; // 왼쪽 자식노드 자리에 왼쪽 자식노드의 오른쪽 자식노드 세팅

    // 왼쪽 자식노드의 오른쪽 자식노드의 부모노드를, 왼쪽 자식노드의 부모노드로 세팅
    if (LeftChild-&amp;gt;Right != Nil){
        LeftChild-&amp;gt;Right-&amp;gt;Parent = Parent;
    }

    // 왼쪽 자식노드의 부모노드를 부모노드의 부모노드로 세팅 (추후 왼쪽 자식노드가 부모노드가 되어야하기에, 할아버지 노드가 없으면 Root로)
    LeftChild-&amp;gt;Parent = Parent-&amp;gt;Parent;
    if (Parent-&amp;gt;Parent == NULL) {
        (*Root) = LeftChild;
    }else { // 부모노드가 할아버지 노드의 왼쪽에 있으면
        if (Parent == Parent-&amp;gt;Parent-&amp;gt;Left){
            Parent-&amp;gt;Parent-&amp;gt;Left = LeftChild; // 왼쪽 자식노드를 부모노드 자리에 위치 (rotate)
        }else { // 부모노드가 할아버지 노드의 오른쪽에 있으면
            Parent-&amp;gt;Parent-&amp;gt;Right = LeftChild; // 왼쪽 자식노드를 부모노드 자리에 위치 (rotate)
        }
    }
    LeftChild-&amp;gt;Right = Parent; // 왼쪽 자식노드(현재 부모노드 위치)의 오른쪽 자식노드를 오른쪽으로 옮김
    Parent-&amp;gt;Parent = LeftChild; // 옮겨진 노드의 부모노드를 왼쪽 자식노드(현재 부모노드 위치)로 변경 
}

/* 좌회전 */
void RBT_RotateLeft(RBTNode** Root, RBTNode* Parent) {
    RBTNode* RightChild = Parent-&amp;gt;Right;
    Parent-&amp;gt;Right = RightChild-&amp;gt;Left; // 오른쪽 자식노드(R)의 왼쪽 자식노드(A)를 부모노드(P)의 오른쪽 자식으로 변경

    if (RightChild-&amp;gt;Left != Nil) {
        RightChild-&amp;gt;Left-&amp;gt;Parent = Parent; // 오른쪽 자식노드(R)의 왼쪽 자식노드(A)의 부모를 P로 설정
        RightChild-&amp;gt;Parent = Parent-&amp;gt;Parent; // P의 부모(G)를 R의 부모로 지정
    }

    // G의 자식을 R로 설정 
    if (Parent == Parent-&amp;gt;Parent-&amp;gt;Left) {
        Parent-&amp;gt;Parent-&amp;gt;Left = RightChild;
    }else {
        Parent-&amp;gt;Parent-&amp;gt;Right = RightChild;
    }

    RightChild-&amp;gt;Left = Parent; // P를 R의 왼쪽 자식으로 설정
    Parent-&amp;gt;Parent = RightChild; // P의 부모를 R로 설정
}

/*이진 탐색의 InsertNode역할을 수행 */
void RBT_InsertNodeHelper(RBTNode** Tree, RBTNode* NewNode) {
    if ((*Tree) == NULL) {
        (*Tree) = NewNode;
    }
    if ((*Tree)-&amp;gt;Data &amp;lt; NewNode-&amp;gt;Data){
        if ((*Tree)-&amp;gt;Right == Nil) {
            (*Tree)-&amp;gt;Right = NewNode;
            NewNode-&amp;gt;Parent = (*Tree);
        }else {
            RBT_InsertNodeHelper(&amp;amp;(*Tree)-&amp;gt;Right, NewNode);
        }
    }
    else if ((*Tree)-&amp;gt;Data &amp;gt; NewNode-&amp;gt;Data) {
        if ((*Tree)-&amp;gt;Left == Nil) {
            (*Tree)-&amp;gt;Left = NewNode;
            NewNode-&amp;gt;Parent = (*Tree);
        }else {
            RBT_InsertNodeHelper(&amp;amp;(*Tree)-&amp;gt;Left, NewNode);
        }
    }
}

/* 노드 삽입 후 규칙 복구 */
void RBT_RebuildAfterInsert(RBTNode** Root, RBTNode* X) {

    // 빨간색노드의 자식은 반드시 검은색이어야 한다는 규칙 위배
    while (X != (*Root) &amp;amp;&amp;amp; X-&amp;gt;Parent-&amp;gt;Color == RED) {
        // 부모 노드가 할아버지 노드의 왼쪽 자식인 경우
        if (X-&amp;gt;Parent == X-&amp;gt;Parent-&amp;gt;Parent-&amp;gt;Left){
            RBTNode* Uncle = X-&amp;gt;Parent-&amp;gt;Parent-&amp;gt;Right;

            // 1. 삼촌노드가 빨간색인 경우
            // 부모노드와 삼촌노드를 검은색으로 칠하고, 할아버지노드를 빨간색으로 변경합니다.
            // 할아버지 노드가 변경되었으니 할어버지 노드를 자식노드로 취급하여 재점검 시행
            if (Uncle-&amp;gt;Color == RED) {
                X-&amp;gt;Parent-&amp;gt;Color = BLACK;
                Uncle-&amp;gt;Color = BLACK;
                X-&amp;gt;Parent-&amp;gt;Parent-&amp;gt;Color = RED;

                X = X-&amp;gt;Parent-&amp;gt;Parent;
            }
            // 삼촌노드가 검은색인 경우
            else 
            {
                // 2. 삼촌노드가 검은색이며 새로 삽입한 노드가 부모노드의 오른쪽 자식인 경우
                // 부모노드를 좌회전시켜 상황을 3의 경우와 같게 만듭니다.
                if (X == X-&amp;gt;Parent-&amp;gt;Right) {
                    X = X-&amp;gt;Parent;
                    RBT_RotateLeft(Root, X);
                }
                // 3. 삼촌노드가 검은색이며 새로 삽입한 노드가 부모노드의 왼쪽 자식인 경우
                // 부모노드를 검은색, 조부모 노드를 빨간색으로 칠한 뒤 조부모노드를 우회전 시킵니다.
                X-&amp;gt;Parent-&amp;gt;Color = BLACK;
                X-&amp;gt;Parent-&amp;gt;Parent-&amp;gt;Color = RED;

                RBT_RotateRight(Root, X-&amp;gt;Parent-&amp;gt;Parent);
            }
        }else { // 부모 노드가 할아버지 노드의 오른쪽 자식인 경우
            RBTNode* Uncle = X-&amp;gt;Parent-&amp;gt;Parent-&amp;gt;Left;
            if (Uncle-&amp;gt;Color == RED){
                X-&amp;gt;Parent-&amp;gt;Color = BLACK;
                Uncle-&amp;gt;Color = BLACK;
                X-&amp;gt;Parent-&amp;gt;Parent-&amp;gt;Color = RED;

                X = X-&amp;gt;Parent-&amp;gt;Parent;
            }
            else {
                if (X==X-&amp;gt;Parent-&amp;gt;Left){
                    X = X-&amp;gt;Parent;
                    RBT_RotateRight(Root, X);
                }
                X-&amp;gt;Parent-&amp;gt;Color = BLACK;
                X-&amp;gt;Parent-&amp;gt;Parent-&amp;gt;Color = RED;
                RBT_RotateLeft(Root, X-&amp;gt;Parent-&amp;gt;Parent);
            }
        }   
    }
    (*Root)-&amp;gt;Color = BLACK; // 루트노드는 항상 검은색
}

/* 노드 삽입 */
void RBT_InsertNode(RBTNode** Tree, RBTNode* NewNode) {
    RBT_InsertNodeHelper(Tree, NewNode); // 이진탐색트리의 노드 삽입
    NewNode-&amp;gt;Color = RED; // 새로운 노드는 빨간색으로 칠하고 Nil을 양쪽 지식으로 연결합니다.
    NewNode-&amp;gt;Left = Nil;
    NewNode-&amp;gt;Right = Nil;
    RBT_RebuildAfterInsert(Tree, NewNode); // 무너진 Red-Black Tree 규칙 고치기 (빨간삭 노드의 자식들은 모두 검은색이다.)
}

/* 노드 삭제 후 규칙 복구 */
void RBT_RebuildAfterReMove(RBTNode** Root, RBTNode* Successor){
    RBTNode* Sibling = NULL;
    while (Successor-&amp;gt;Parent != NULL &amp;amp;&amp;amp; Successor-&amp;gt;Color == BLACK)
    {
        if (Successor == Successor-&amp;gt;Parent-&amp;gt;Left) {
            Sibling = Successor-&amp;gt;Parent-&amp;gt;Right;
            if (Sibling-&amp;gt;Color == RED) {
                Sibling-&amp;gt;Color = BLACK;
                Successor-&amp;gt;Parent-&amp;gt;Color = RED;
                RBT_RotateLeft(Root, Successor-&amp;gt;Parent);
            }else {
                if (Sibling-&amp;gt;Left-&amp;gt;Color == BLACK &amp;amp;&amp;amp; Sibling-&amp;gt;Right-&amp;gt;Color == BLACK){
                    Sibling-&amp;gt;Color = RED;
                    Successor = Successor-&amp;gt;Parent;
                }else {
                    if (Sibling-&amp;gt;Left-&amp;gt;Color == RED){
                        Sibling-&amp;gt;Left-&amp;gt;Color = BLACK;
                        Sibling-&amp;gt;Color = RED;

                        RBT_RotateRight(Root,Sibling);
                        Sibling = Successor-&amp;gt;Parent-&amp;gt;Right;
                    }
                    Sibling-&amp;gt;Color = Successor-&amp;gt;Parent-&amp;gt;Color;
                    Successor-&amp;gt;Parent-&amp;gt;Color = BLACK;
                    Sibling-&amp;gt;Right-&amp;gt;Color = BLACK;
                    RBT_RotateLeft(Root, Successor-&amp;gt;Parent);
                    Successor = (*Root);
                }
            }
        }else {
            Sibling = Successor-&amp;gt;Parent-&amp;gt;Left;

            if (Sibling-&amp;gt;Color == RED) {
                Sibling-&amp;gt;Color = BLACK;
                Successor-&amp;gt;Parent-&amp;gt;Color = RED;
                RBT_RotateRight(Root, Successor-&amp;gt;Parent);
            }else {
                if (Sibling-&amp;gt;Right-&amp;gt;Color == BLACK &amp;amp;&amp;amp; Sibling-&amp;gt;Left-&amp;gt;Color == BLACK){
                    Sibling-&amp;gt;Color = RED;
                    Successor = Successor-&amp;gt;Parent;
                }else {
                    if (Sibling-&amp;gt;Right-&amp;gt;Color == RED){
                        Sibling-&amp;gt;Right-&amp;gt;Color = BLACK;
                        Sibling-&amp;gt;Color = RED;

                        RBT_RotateLeft(Root, Sibling);
                        Sibling = Successor-&amp;gt;Parent-&amp;gt;Left;
                    }

                    Sibling-&amp;gt;Color = Successor-&amp;gt;Parent-&amp;gt;Color;
                    Successor-&amp;gt;Parent-&amp;gt;Color = BLACK;
                    Sibling-&amp;gt;Left-&amp;gt;Color = BLACK;
                    RBT_RotateRight(Root, Successor-&amp;gt;Parent);
                    Successor = (*Root);
                }
            }
        }
    }
    Successor-&amp;gt;Color = BLACK;
}

/* 노드 삭제 */
RBTNode* RBT_RemoveNode( RBTNode** Root, ElementType Data) {
    RBTNode* ReMoved = NULL;
    RBTNode* Successor = NULL; // Successor는 ReMoved의 자식 노드 중에서 부모와 연결할 대체 노드를
    RBTNode* Target = RBT_SearchNode((*Root), Data);

    if (Target == NULL) return NULL;

    if (Target-&amp;gt;Left == Nil || Target-&amp;gt;Right == Nil) {
        ReMoved = Target;
    }
    else {
        ReMoved = RBT_SearchMinNode(Target-&amp;gt;Right); // MinNode를 ReMoved로 지정 (기본 트리구조 유지를 위해)
        Target-&amp;gt;Data = ReMoved-&amp;gt;Data; // MinNode의 데이터로 Target의 데이터를 변경
    }

    // 삭제될 노드의 후임자(Successor) 찾기
    if (ReMoved-&amp;gt;Left != Nil) {
        Successor = ReMoved-&amp;gt;Left;
    }else {
        Successor = ReMoved-&amp;gt;Right;
    }

    if (Successor != Nil) {
        Successor-&amp;gt;Parent = ReMoved-&amp;gt;Parent;
    }

    if (ReMoved-&amp;gt;Parent == NULL){
        (*Root) = Successor;
    }else {
        if (ReMoved == ReMoved-&amp;gt;Parent-&amp;gt;Left){
            ReMoved-&amp;gt;Parent-&amp;gt;Left = Successor;
        }else {
            ReMoved-&amp;gt;Parent-&amp;gt;Right = Successor;
        }
    }
    if (ReMoved-&amp;gt;Color == BLACK){
        RBT_RebuildAfterReMove(Root, Successor);
    }

    return ReMoved;
}

void RBT_PrintTree(RBTNode* Node, int Depth, int BlackCount) {
    int i = 0;
    char c = 'X';
    int v = -1;
    char cnt[100];

    if (Node == NULL || Node == Nil) {
        return;
    }
    if (Node-&amp;gt;Color == BLACK) BlackCount++;

    if (Node-&amp;gt;Parent != NULL) {
        v= Node-&amp;gt;Parent-&amp;gt;Data;
        if (Node-&amp;gt;Parent-&amp;gt;Left == Node) c = 'L';
        else c='R';
    }

    if (Node-&amp;gt;Left == Nil &amp;amp;&amp;amp; Node-&amp;gt;Right == Nil) {
        sprintf(cnt, &quot;========= %d&quot;, BlackCount);
    }else {
        strncpy(cnt, &quot;&quot;, sizeof(cnt));
    }

    for (i = 0; i&amp;lt;Depth; i++) printf(&quot;   &quot;);

    printf(&quot;%d %s [%c, %d] %s \n&quot;, Node-&amp;gt;Data, (Node-&amp;gt;Color == RED)?&quot;RED&quot;:&quot;BLACK&quot;, c, v, cnt);

    RBT_PrintTree(Node-&amp;gt;Left, Depth+1, BlackCount);
    RBT_PrintTree(Node-&amp;gt;Right, Depth+1, BlackCount);
}

int main(void) {
    RBTNode* Tree = NULL;
    RBTNode* Node = NULL;

    Nil = RBT_CreateNode(0);
    Nil-&amp;gt;Color = BLACK;

    while (1)
    {
        int cmd = 0;
        int param = 0;
        char buffer[10];

        printf(&quot;Enter command number : \n&quot;);
        printf(&quot;(1) Create a node, (2) ReMove a node, (3) Search a Node \n&quot;);
        printf(&quot;(4) Display Tree (5) quit \n&quot;);
        printf(&quot;command number : &quot;);

        fgets(buffer, sizeof(buffer)-1, stdin);
        sscanf(buffer, &quot;%d&quot;, &amp;amp;cmd);

        if (cmd &amp;lt; 1 || cmd &amp;gt; 5){
            printf(&quot;Invalid command Number: \n&quot;);
            continue;
        }
        else if (cmd == 4){
            RBT_PrintTree(Tree,0,0);
            printf(&quot;\n&quot;);
            continue;
        }
        else if (cmd == 5){
            break;
        }

        printf(&quot;Enter the parameter (1~200) :\n&quot;);
        fgets(buffer, sizeof(buffer)-1, stdin);
        sscanf(buffer, &quot;%d&quot;, &amp;amp;param);

        if (param &amp;lt; 1 || param &amp;gt; 200) {
            printf(&quot;Invalid parameter : %d \n&quot;, param);
            continue;
        }

        switch(cmd) {
            case 1:
                RBT_InsertNode(&amp;amp;Tree, RBT_CreateNode(param));
            break;
            case 2:
                Node = RBT_RemoveNode(&amp;amp;Tree, param);
                if (Node == NULL){
                    printf(&quot;not found node to delete : %d&quot;, param);
                }else {
                    RBT_DestroyNode(Node);
                }
                break;
            case 3:
            Node = RBT_SearchNode(Tree, param);
            if (Node == NULL) {
                printf(&quot;Not found Node :%d \n&quot;, param);
            }else {
                printf(&quot;Found Node : %d(Color:%s) \n&quot;, Node-&amp;gt;Data, (Node-&amp;gt;Color == RED)?&quot;RED&quot;:&quot;BLACK&quot;);
            }
            break;
        }
        printf(&quot;\n&quot;);
    }
    RBT_DestroyTree(Tree);
    return 0;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <category>RedBlackTree</category>
      <category>알고리즘</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/99</guid>
      <comments>https://jaykos96.tistory.com/99#entry99comment</comments>
      <pubDate>Wed, 2 Apr 2025 20:40:46 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris : 운영체제-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 책의 마지막 장인 운영체제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS는 컴퓨터에서 실행되는 모든 프로그램을 지원해야하기에 효율이 매우 좋아야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OS의 시간 및 공간 효율성이 높을수록 응용프로그램의 성능 또한 올라갑니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 보통 고수준언어로 작성되어 바이너리 형식으로 컴파일됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1742125041833&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let n = Keyboard.readInt(&quot;Enter a number : &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;위와 같은 명령어가 실행되면, 프롬프트에 Enter a number: 라는 문자열이 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러려면 String 객체가 생성되고 각 글자를 char 값 배열로 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 문자열을 스크린에 한 번에 하나씩 렌더링하며, 다음 문자가 물리적으로 표시되어야하는 커서 위치를 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 사용자가 키보드의 특정키를 누를때까지 기다리는 루프 또한 준비되어야하고 이를 위해서는 키 입력 감지, 문자 입력, 문자열에 해당 문자 추가, 문자열을 정수로 반환하는 등 여러 작업이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉 let n = Keyboard.readInt(&quot;Enter a number : &quot;)&lt;span&gt; 과 같은 명령이 실행되면 객체 사용을 위한 메모리 할당, 입력 작업, 출력 작업, 문자열 처리와 같은 다양한 os함수들이 호출됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 처리뿐만 아니라 아래 그림 처럼 수학연산, I/O 드라이버, 네트워크 등 운영체제가 하는일들은 많고 다양합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;1460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B4Swd/btsMNgOtJKt/9Wm8rTjmwOieFcApQgKdI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B4Swd/btsMNgOtJKt/9Wm8rTjmwOieFcApQgKdI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B4Swd/btsMNgOtJKt/9Wm8rTjmwOieFcApQgKdI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB4Swd%2FbtsMNgOtJKt%2F9Wm8rTjmwOieFcApQgKdI1%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;578&quot; height=&quot;457&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;1460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS에서 지원하는 서비스들입니다.&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;Math : 수학관련 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Memory : Memory 클래스 (peek, alloc 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Screen : 화면에 도형을 그릴 수 있는 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Output : 화면에 text를 출력하는 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keyboard : 사용자의 입력을 받는 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String : 문자열 관련 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Array : Jack에서 사용되는 배열 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sys : 몇가지 유용한 함수가 들어있는 클래스 입니다. (halt, error, wait)&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS : Math&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;곱셈&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca2jcX/btsMNBx6abx/kD6nOD7A5Uil1EoLTkgfa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca2jcX/btsMNBx6abx/kD6nOD7A5Uil1EoLTkgfa0/img.png&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;558&quot; data-is-animation=&quot;false&quot; width=&quot;612&quot; height=&quot;350&quot; style=&quot;width: 63.7961%; margin-right: 10px;&quot; data-widthpercent=&quot;64.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca2jcX/btsMNBx6abx/kD6nOD7A5Uil1EoLTkgfa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca2jcX%2FbtsMNBx6abx%2FkD6nOD7A5Uil1EoLTkgfa0%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;976&quot; height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UWRt8/btsMLv0NGnl/BNki4L0o1EDWUxt3WIikb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UWRt8/btsMLv0NGnl/BNki4L0o1EDWUxt3WIikb0/img.png&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;662&quot; data-is-animation=&quot;false&quot; width=&quot;443&quot; height=&quot;461&quot; style=&quot;width: 35.0411%;&quot; data-widthpercent=&quot;35.45&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UWRt8/btsMLv0NGnl/BNki4L0o1EDWUxt3WIikb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUWRt8%2FbtsMLv0NGnl%2FBNki4L0o1EDWUxt3WIikb0%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;636&quot; height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 jack의 word size는 16bit입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x는 27, y는 9이고 두 숫자를 곱하는 연산을 한다고 하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 이진수로 27과 9을 만들고 남는 크기는 모두 0으로 채웁니다.&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;pre id=&quot;code_1742126980577&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;356 * 73 = (356 * 70) + (356 * 3)&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;먼저 x를 왼쪽으로 옮기고 (shift) 계속해서 한번씩 왼쪽으로 옮깁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 예시에서는 y의 bit(4)만큼 x에 대해 shift가 실행되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shift가 완료되었다면 y의 값을 세로로 적은다음, y값의 bit가 1인 행들끼리만 더합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서는 첫줄과 마지막줄을 더하면 됩니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;나눗셈&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;나누기는 우리가 어릴때 배웠던 나눗셈 방법 '정제법'을 사용합니다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxeZCB/btsMMKoOtZP/UbgsWO9itz5Bo5NnH7BVl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxeZCB/btsMMKoOtZP/UbgsWO9itz5Bo5NnH7BVl0/img.png&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;504&quot; data-is-animation=&quot;false&quot; width=&quot;369&quot; height=&quot;341&quot; style=&quot;width: 52.6432%; margin-right: 10px;&quot; data-widthpercent=&quot;53.26&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxeZCB/btsMMKoOtZP/UbgsWO9itz5Bo5NnH7BVl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxeZCB%2FbtsMMKoOtZP%2FUbgsWO9itz5Bo5NnH7BVl0%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;546&quot; height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o8PZg/btsMLsJTX2i/SQW4NWREkgXqRALsbqoeKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o8PZg/btsMLsJTX2i/SQW4NWREkgXqRALsbqoeKk/img.png&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;486&quot; data-is-animation=&quot;false&quot; width=&quot;344&quot; height=&quot;362&quot; data-widthpercent=&quot;46.74&quot; style=&quot;width: 46.194%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o8PZg/btsMLsJTX2i/SQW4NWREkgXqRALsbqoeKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo8PZg%2FbtsMLsJTX2i%2FSQW4NWREkgXqRALsbqoeKk%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;462&quot; height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 다른점은 2진수를 사용한다는건데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 10(1010)를 3(0011)으로 나눈다면 먼저 몫을 담을 비트와 나머지를 담을 비트를 0으로 초기화 합니다.&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;나눠지는수 '1010'에서 각 bit를 하나씩 왼쪽으로 shift하여 나머지 bit에 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 나머지에서 나누는 수(0011)을 뺍니다. 그 결과가 0 이상이면 몫비트에 1을 넣고 음수라면 몫 비트에 0을 널습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 비트수만큼 반복합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;제곱근&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;제곱근은 수학문제 풀듯이 풀어서 찾을 수 있습니다.&lt;/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;1376&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ldJiN/btsMNBMAltn/MMwFddyOfTcXPKFM2IVVbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ldJiN/btsMNBMAltn/MMwFddyOfTcXPKFM2IVVbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ldJiN/btsMNBMAltn/MMwFddyOfTcXPKFM2IVVbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FldJiN%2FbtsMNBMAltn%2FMMwFddyOfTcXPKFM2IVVbK%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;672&quot; height=&quot;355&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제곱근(&amp;radic;)을 구하는 방법은&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt; 이진 탐색(Binary Search), 제곱근의 역함수 y = x&amp;sup2;,&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;제곱근 함수와 역함수는 모두 단조 증가함수기에 감소하지 않고 계속 커진다&lt;/b&gt;는 특성 이 3가지를 이용해 구할 수 있습니다.&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;y = &amp;radic;x을 구하는 것은 y&amp;sup2; &amp;le; x &amp;lt; (y + 1)&amp;sup2;인 값을 찾는 것과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 제곱근을 찾는 문제는 결국 역함수의 값을 찾는 문제가 됩니다.&lt;br /&gt;그리고 함수다 양의 방향으로만 증가하는 함수기에, 중간값(mid)을 기준으로 범위를 절반씩 줄여갈 수 있습니다. (이진 탐색)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS : String&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;String은 Jack OS 표준 라이브러리입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;컴퓨터는 숫자를 내부적으로 2진 코드로 표현하지만&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 사람이 숫자를 읽거나 입력해야할 때는 10진수로 변환(cast)되어야합니다.&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;944&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rPxj2/btsMOhBFB4E/g9XkeFxJaNgfiyxsKkdVR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rPxj2/btsMOhBFB4E/g9XkeFxJaNgfiyxsKkdVR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rPxj2/btsMOhBFB4E/g9XkeFxJaNgfiyxsKkdVR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrPxj2%2FbtsMOhBFB4E%2Fg9XkeFxJaNgfiyxsKkdVR1%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;735&quot; height=&quot;430&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;String -&amp;gt; int 형변환&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;int2String()은 숫자를 재귀적으로 문자열로 변환하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;string2Int()는 각 문자를 숫자로 변환하며 왼쪽에서 오른쪽으로 조합하는 방식으로 동작합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS76kQ/btsMP5fgMQq/QMnA64nC0iPJdBY5KKPB31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS76kQ/btsMP5fgMQq/QMnA64nC0iPJdBY5KKPB31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS76kQ/btsMP5fgMQq/QMnA64nC0iPJdBY5KKPB31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS76kQ%2FbtsMP5fgMQq%2FQMnA64nC0iPJdBY5KKPB31%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;751&quot; height=&quot;336&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS : Memory&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;os에서 메모리를 관련 클래스의 메소드는 peek, poke, alloc, deAlloc이라는 함수가 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이 메서드들은 컴파일러에서 생성자 및 소멸자를 처리할 때 저수준 코드에 포함되거나, 아니면 고수준언어에서 직접 호출되기도 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;heap의 자원 관리는 os에서 하며 os가 실행되면 heapBase라는 포인터를 RAM에서 heap의 시작주소로 초기화 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;jack에서는 스택에 끝에 위치하며 heapBase는 2048입니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;peek &amp;amp; poke&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;프로그램은 계속해서 RAM에 접근합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;운영체제는 고수준 프로그램와 RAM의 인터페이스입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;peek &amp;amp; poke는 RAM의 주소에 대한 함수입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Memory.peek(addr)은 RAM의 주소 addr을 반환하고 Memory.poke(addr, value)는 RAM주소와 value를 받아서 해당 주소의 RAM을 value로 설정합니다.&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;1250&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWnWwE/btsMMLhUxYJ/vaqSrhzmE2xid0mqbkeK81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWnWwE/btsMMLhUxYJ/vaqSrhzmE2xid0mqbkeK81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWnWwE/btsMMLhUxYJ/vaqSrhzmE2xid0mqbkeK81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWnWwE%2FbtsMMLhUxYJ%2FvaqSrhzmE2xid0mqbkeK81%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;690&quot; height=&quot;491&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;peek &amp;amp; poke를 구현하기 위해서는 RAM에 주소에 직접 접근해야합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;메모리 클래스에서 먼저 init 함수를 이용해서 array ram을 0으로 초기화합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;변수를 0으로 초기화 함으로서 인덱스를 이용해 ram의 주소 어디든 접근할 수 있게됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;그 뒤에 peek &amp;amp; poke를 사용해서 메모의 값을 조회하거나 변경 할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;alloc &amp;amp; dealloc&lt;/span&gt;&lt;/span&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;생성된 객체와 배열들은 heap에 저장되고 stack은 데이터가 저장된 heap주소를 가지고있습니다.&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;alloc함수는 argument에 지정한 사이즈만큼의 메모리를 사용할 수 있는 메모리 주소를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dealloc은 argument지정한 객체나 배열의 주소를 가져와서 재활용 할 수 있도록 해제합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r2NDj/btsMOE3hN0F/KHUkyL7IsYeoR5gPX5v8zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r2NDj/btsMOE3hN0F/KHUkyL7IsYeoR5gPX5v8zK/img.png&quot; data-alt=&quot;록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r2NDj/btsMOE3hN0F/KHUkyL7IsYeoR5gPX5v8zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr2NDj%2FbtsMOE3hN0F%2FKHUkyL7IsYeoR5gPX5v8zK%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;754&quot; height=&quot;334&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스의 생성자가 실행되면 alloc(argument 수)명령어가 실행되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;argument의 수 만큼 heap에서 공간을 할당하고 stack에 그 시작 주소를 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BmXZF/btsMNjzaz1x/hwDKxNaZsrJDulbCNIlm8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BmXZF/btsMNjzaz1x/hwDKxNaZsrJDulbCNIlm8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BmXZF/btsMNjzaz1x/hwDKxNaZsrJDulbCNIlm8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBmXZF%2FbtsMNjzaz1x%2FhwDKxNaZsrJDulbCNIlm8K%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;812&quot; height=&quot;393&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 필요없을때는 deAlloc 메서드를 사용해 그 객체를 제거할 수 있습니다.&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;자바나 c++에서 gc는 생성되는 모든 객체에 대한 모든 참조를 추적합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램이 동작하면서 그 객체에 대한 온갖 종류의 역참조가 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 동작하면서 특정 포인터를 가리키는 참조 수가 감소되어 결국 0이되면 gc가 동작해서 deAlloc 함수를 실행합니다.&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;하지만 jack은 gc를 사용하지 않고 명시적으로 제거하는 방법을 선택했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wHT9D/btsMPmHQOOJ/2rFQ6absRYj0AFHjImiGDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wHT9D/btsMPmHQOOJ/2rFQ6absRYj0AFHjImiGDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wHT9D/btsMPmHQOOJ/2rFQ6absRYj0AFHjImiGDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwHT9D%2FbtsMPmHQOOJ%2F2rFQ6absRYj0AFHjImiGDk%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;654&quot; height=&quot;495&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;heap management&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;os가 heap을 관리하는 방법에 대해 알아보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;init()함수에서 heapBase의 주소를 freeList라는 LinkedList에 저장합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LinkedList에는 크기(size)와 다음 빈 블록을 가리키는 포인터(next)가 저장됩니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;alloc heap management&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alloc함수가 실행되면 적절한 block을 찾은 뒤 FreeList에서 제거하고 호출자에게 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alloc(3)함수가 실행되었을 때 세그먼트의 크기가 size+2보다 크면 해당 세그먼트를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;size+2인 이유는 2만큼의 필수 필드값(size, next)을 넣어야하기 때문입니다.&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;적합한 세그먼트를 찾는 방법은 best-fit, first-fit 두가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;first-fit은 freeList를 탐색하면서 최초로 찾은 적합한 세그먼트를 사용하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;best-fit은 가능한 작은 세그먼트를 찾는 방법입니다.&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;추가로 최초의 freeList의 크기는 heap 전체입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/denI0F/btsMN8w7AWF/VFpKXTbWtqbpsWyYAfpf0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/denI0F/btsMN8w7AWF/VFpKXTbWtqbpsWyYAfpf0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/denI0F/btsMN8w7AWF/VFpKXTbWtqbpsWyYAfpf0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdenI0F%2FbtsMN8w7AWF%2FVFpKXTbWtqbpsWyYAfpf0k%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;691&quot; height=&quot;306&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;deAlloc heap management&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;dealloc이 하는일은 객체로 사용되고있는 block을 다시 freeList의 뒤에 붙이는 작업입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;그렇기에 dealloc이 많이 실행될수록 freeList는 더 많이 파편화됩니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&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;1852&quot; data-origin-height=&quot;1362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ8kwC/btsMNhnUF3s/mpflStSxJZVSrP8U9l8jJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ8kwC/btsMNhnUF3s/mpflStSxJZVSrP8U9l8jJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ8kwC/btsMNhnUF3s/mpflStSxJZVSrP8U9l8jJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ8kwC%2FbtsMNhnUF3s%2FmpflStSxJZVSrP8U9l8jJ0%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;628&quot; height=&quot;462&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;1362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 freeList가 많이 파편화되면 alloc이 적합한 size의 블록을 찾기 점점 어려워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 defrag라는 알고리즘을 통해 파편화된 freeList들을 합치는 작업이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(실제로 모든 os에서 defrag가 가끔식 실행됩니다.)&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS : Array&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Array 클래스의 new는 생성자가 아니라 함수입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;os함수인 Memory.alloc을 호출하여 배열을 위한 메모리를 할당합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;그리고 dispose()를 통해 Memory.deAlloc을 호출하여 해제합니다.&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;1070&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L5AQg/btsMP7jR1qU/JocYb1UJLdqCRp0KDuLA7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L5AQg/btsMP7jR1qU/JocYb1UJLdqCRp0KDuLA7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L5AQg/btsMP7jR1qU/JocYb1UJLdqCRp0KDuLA7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL5AQg%2FbtsMP7jR1qU%2FJocYb1UJLdqCRp0KDuLA7k%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;404&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;636&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS : Graphics&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;jack의 Screen클래스는 기본적인 그래픽 그리기 알고리즘만을 제공합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;우리는 맨 왼쪽 상단의 좌표가 (0,0)인 흑백스크린에 연결되어있다고 가정합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;스크린은 전용 RAM의 메모리맵 영역과 연결됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;컴퓨터 외부의 프로게스가 1초당 한번씩 메모리 맵에 따라 스크린을 새로고침합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;vector graphics, bitmap graphics&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;1340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvjx9M/btsMPlhYtUL/Kk9oMs7OukSbrAo78WT6b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvjx9M/btsMPlhYtUL/Kk9oMs7OukSbrAo78WT6b1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvjx9M/btsMPlhYtUL/Kk9oMs7OukSbrAo78WT6b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvjx9M%2FbtsMPlhYtUL%2FKk9oMs7OukSbrAo78WT6b1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;588&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;1340&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;jack에서는 vector, bitmap 두가지 방식으로 그래픽 구현이 가능합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 그래픽은&lt;span&gt;&amp;nbsp;&lt;/span&gt;수학적 도형(선, 사각형, 원 등)의 명령어를 기반으로 그래픽을 표현하는 방식이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비트맵 그래픽은&lt;span&gt;&amp;nbsp;&lt;/span&gt;픽셀 하나하나를 직접 지정하여 저장하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면을&lt;span&gt;&amp;nbsp;&lt;/span&gt;2D 배열의 형태(0과 1)로 표현하며, 각 픽셀이&lt;span&gt;&amp;nbsp;&lt;/span&gt;켜짐(1) 또는 꺼짐(0)&lt;span&gt;&amp;nbsp;&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;vector 그래픽은 이미지를&lt;span&gt;&amp;nbsp;&lt;/span&gt;픽셀이 아닌 수학적 개념으로 저장하며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;크기를 확대해도 깨지지 않으나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bitmap은 픽셀 단위로 저장되므로, 확대하면 깨지는 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;픽셀 그리기 (bitmap)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hadyn/btsMMpGlGZe/WGjJbOTd2PR78tXYljLLvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hadyn/btsMMpGlGZe/WGjJbOTd2PR78tXYljLLvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hadyn/btsMMpGlGZe/WGjJbOTd2PR78tXYljLLvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHadyn%2FbtsMMpGlGZe%2FWGjJbOTd2PR78tXYljLLvK%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;946&quot; height=&quot;264&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;drawPixcel 함수를 통해 메모리맵을 조작하며 픽셀을 그리게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 개의 RAM 주소는 16개의 픽셀을 저장하기에&amp;nbsp;픽셀 좌표 (x, y)를 RAM 주소로 변환해서 위치를 찾습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1752&quot; data-origin-height=&quot;1380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6qEFt/btsMNi1uMxl/s4hEuql7kXjyPEtZ9K9b81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6qEFt/btsMNi1uMxl/s4hEuql7kXjyPEtZ9K9b81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6qEFt/btsMNi1uMxl/s4hEuql7kXjyPEtZ9K9b81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6qEFt%2FbtsMNi1uMxl%2Fs4hEuql7kXjyPEtZ9K9b81%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;664&quot; height=&quot;523&quot; data-origin-width=&quot;1752&quot; data-origin-height=&quot;1380&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;y 좌표 찾기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면은 512(16*32) 픽셀의 가로 해상도를 가지고 있습니다. 즉 한 줄에 32개의 word size(16bit)가 존재합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 y 번째 줄의 시작 RAM 주소는 16384 + (y * 32)로 계산합니다.&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;x 좌표 찾기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀 x 좌표는 16픽셀 단위로 저장되므로, x가 0~15이면 첫 번째 RAM 단어(16384), x가 16~31이면 두 번째 RAM 단어(16385)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이기에 x / 16계산을 통해 좌표를 찾습니다.&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;즉 메모리맵의 주소는 address = 16384 + (y * 32) + (x / 16);이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;bitMask (픽셀 on/off)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 bitMask 연산(&quot;1 &amp;lt;&amp;lt; (15 - (x % 16))&quot;)을 통해 픽셀의 on/off를 설정할수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;and연산으로 픽셀을 끄고 or 연산으로 픽셀을 킵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1742285080597&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 픽셀 키기
Memory.poke(address, Memory.peek(address) | bitMask);
// 픽셀 끄기
Memory.poke(address, Memory.peek(address) &amp;amp; (~bitMask));&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;선 그리기 (vector)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선은 픽셀들을 지그제그로 그려나가는 모양입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림처럼 선을 그리는 함수 drawLine은 결국 drawPixel의 연속입니다.&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;x1,y1에서 x2,y2까지 선을 그리는 함수 drawLine(x1,y1,x2,y2) 가 있을때 drawLine함수에는 좌표값(x1, y1)의 이동을 위한 a,b 지역변수가 존재합니다. (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;a는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;오른쪽으로 몇 픽셀이동했는지 알려주고 b는 위로 몇 픽셀 이동했는지 알려줍니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYUe70/btsMOv6GG5V/1THWhjJtU0j15x5jczGFfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYUe70/btsMOv6GG5V/1THWhjJtU0j15x5jczGFfk/img.png&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;682&quot; data-is-animation=&quot;false&quot; width=&quot;751&quot; height=&quot;289&quot; style=&quot;width: 65.6739%; margin-right: 10px;&quot; data-widthpercent=&quot;66.45&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYUe70/btsMOv6GG5V/1THWhjJtU0j15x5jczGFfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYUe70%2FbtsMOv6GG5V%2F1THWhjJtU0j15x5jczGFfk%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;1774&quot; height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1Unkf/btsMNzvj33L/U3oyF46YToFAbjY9aKGiXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1Unkf/btsMNzvj33L/U3oyF46YToFAbjY9aKGiXK/img.png&quot; width=&quot;584&quot; height=&quot;445&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;740&quot; data-origin-width=&quot;972&quot; style=&quot;width: 33.1633%;&quot; data-widthpercent=&quot;33.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1Unkf/btsMNzvj33L/U3oyF46YToFAbjY9aKGiXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1Unkf%2FbtsMNzvj33L%2FU3oyF46YToFAbjY9aKGiXK%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;972&quot; height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/div&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;drawLine함수가 실행되면 먼저 x1,y2를 그립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 픽셀이 오른쪽으로 이동할지 위쪽으로 이동할지 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b/a가 dy/dx보다 크다면 오른쪽으로 아니라면 위쪽으로 이동합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u9DTS/btsMPqjk5VC/Hi2SM3ACh8MwRKrznlKg5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u9DTS/btsMPqjk5VC/Hi2SM3ACh8MwRKrznlKg5K/img.png&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; width=&quot;513&quot; height=&quot;387&quot; style=&quot;width: 49.8255%; margin-right: 10px;&quot; data-widthpercent=&quot;50.41&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u9DTS/btsMPqjk5VC/Hi2SM3ACh8MwRKrznlKg5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu9DTS%2FbtsMPqjk5VC%2FHi2SM3ACh8MwRKrznlKg5K%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;990&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uEofq/btsMNPklWKG/5LQKbsrAB2Nk3nId44WlcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uEofq/btsMNPklWKG/5LQKbsrAB2Nk3nId44WlcK/img.png&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;740&quot; data-is-animation=&quot;false&quot; width=&quot;434&quot; height=&quot;332&quot; style=&quot;width: 49.0118%;&quot; data-widthpercent=&quot;49.59&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uEofq/btsMNPklWKG/5LQKbsrAB2Nk3nId44WlcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuEofq%2FbtsMNPklWKG%2F5LQKbsrAB2Nk3nId44WlcK%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;966&quot; height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 값을 비교를 위해 diff라는 변수를 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4A0M5/btsMOFg35qH/pmmukgoYARrDejPkD6XCnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4A0M5/btsMOFg35qH/pmmukgoYARrDejPkD6XCnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4A0M5/btsMOFg35qH/pmmukgoYARrDejPkD6XCnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4A0M5%2FbtsMOFg35qH%2FpmmukgoYARrDejPkD6XCnk%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;722&quot; height=&quot;189&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디로 이동할지 정해졌다면 이제 이동을 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽으로 가야한다면 a를 증가시킵니다. 그 후 while로 다시 돌아와서 변경된 좌표(x+1, y)에 픽셀을 그립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위로 가야한다면 위로 한칸(x+1, y+1) 이동해서 픽셀을 그립니다. (대각선으로 이동하는 경우는 없습니다)&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;원 그리기 (vector)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원을 그리는 메서드 drawCircle또한 원의 근사치를 만드는 방향으로 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중심의 좌표 x,y와 원의 반지름 r을 argument로 받고, y+dy, x를 지나는 선들을 이용해 원을 그립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x좌표는 반지름과 dy를 이용한 피타고라스의 정리를 통해 구할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYedfI/btsMPvSTkgO/PBMSdjgegIOvnNjSOwxziK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYedfI/btsMPvSTkgO/PBMSdjgegIOvnNjSOwxziK/img.png&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;1056&quot; data-is-animation=&quot;false&quot; width=&quot;689&quot; height=&quot;556&quot; data-widthpercent=&quot;53.74&quot; style=&quot;width: 53.1144%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYedfI/btsMPvSTkgO/PBMSdjgegIOvnNjSOwxziK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYedfI%2FbtsMPvSTkgO%2FPBMSdjgegIOvnNjSOwxziK%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;1308&quot; height=&quot;1056&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqogXl/btsMNOM0bUA/4FlglmKdoeuGNxwAzWSyCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqogXl/btsMNOM0bUA/4FlglmKdoeuGNxwAzWSyCK/img.png&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;1328&quot; data-is-animation=&quot;false&quot; width=&quot;708&quot; height=&quot;664&quot; style=&quot;width: 45.7229%;&quot; data-widthpercent=&quot;46.26&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqogXl/btsMNOM0bUA/4FlglmKdoeuGNxwAzWSyCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqogXl%2FbtsMNOM0bUA%2F4FlglmKdoeuGNxwAzWSyCK%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;1416&quot; height=&quot;1328&quot;/&gt;&lt;/span&gt;&lt;/div&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;그렇게 drawCircle에 대한 메소드를 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n72PO/btsMOu1Hrgx/fu4dqRySVy6Lfvm3jhIkr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n72PO/btsMOu1Hrgx/fu4dqRySVy6Lfvm3jhIkr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n72PO/btsMOu1Hrgx/fu4dqRySVy6Lfvm3jhIkr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn72PO%2FbtsMOu1Hrgx%2Ffu4dqRySVy6Lfvm3jhIkr1%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;807&quot; height=&quot;197&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS : Output&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;문자집합은 출력 가능한것과 불가능한것으로 나뉩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;출력 가능한 문자들은 모두 화면에 표시되는 bitmap이미지가 있어야하고 그 이미지들을 font라고 합니다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;(jack에서는 11*8 픽셀을 사용합니다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;jack os의 Output 클래스에서는 initMap이라는 init시 발생하는 함수를 통해 font에 대한 bitmap들을 저장합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;font를 만드는 작업은 사용할때마다 처리하지 않고 최초 Init시에만 설정합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH2OkY/btsMPkYs4lC/A9YlfuqPKFCLbsymIwZNUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH2OkY/btsMPkYs4lC/A9YlfuqPKFCLbsymIwZNUK/img.png&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1600&quot; data-is-animation=&quot;false&quot; width=&quot;594&quot; height=&quot;497&quot; style=&quot;width: 48.2132%; margin-right: 10px;&quot; data-widthpercent=&quot;48.78&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH2OkY/btsMPkYs4lC/A9YlfuqPKFCLbsymIwZNUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH2OkY%2FbtsMPkYs4lC%2FA9YlfuqPKFCLbsymIwZNUK%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;1912&quot; height=&quot;1600&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BYsk4/btsMOhIpgAX/DxjhrLeeKtWCp2VMldWdT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BYsk4/btsMOhIpgAX/DxjhrLeeKtWCp2VMldWdT1/img.png&quot; data-origin-width=&quot;1980&quot; data-origin-height=&quot;1578&quot; data-is-animation=&quot;false&quot; width=&quot;674&quot; height=&quot;537&quot; style=&quot;width: 50.624%;&quot; data-widthpercent=&quot;51.22&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BYsk4/btsMOhIpgAX/DxjhrLeeKtWCp2VMldWdT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBYsk4%2FbtsMOhIpgAX%2FDxjhrLeeKtWCp2VMldWdT1%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;1980&quot; height=&quot;1578&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS : Keyboard&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;jack에서는 keyboard를 위해 단일 16bit 레지스터를 할당합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;키보드에 어떤 키도 누르지 않으면 레지스터의 값은 0입니다.&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;1988&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba1Oxx/btsMQhzJf43/y9Ey3AyKCSuw9t3qJQchCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba1Oxx/btsMQhzJf43/y9Ey3AyKCSuw9t3qJQchCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba1Oxx/btsMQhzJf43/y9Ey3AyKCSuw9t3qJQchCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba1Oxx%2FbtsMQhzJf43%2Fy9Ey3AyKCSuw9t3qJQchCK%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;813&quot; height=&quot;295&quot; data-origin-width=&quot;1988&quot; data-origin-height=&quot;722&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;키보드 클래스는 4가지 메소드로 만들어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keyPressed() : 키보드에서 눌린 내용이 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;readChar() : 사용자가 마지막으로 누른키를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;readLine() : 문자를 입력하면 enter, return 키를 눌렀을때 입력이 완료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;readInt() : readLine과 유사하지만 숫자만 허용&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTpMp/btsMPrXyvzH/LA4iKdtIlu4C7FcRs9KR9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTpMp/btsMPrXyvzH/LA4iKdtIlu4C7FcRs9KR9K/img.png&quot; width=&quot;777&quot; height=&quot;430&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;926&quot; data-origin-width=&quot;1672&quot; data-widthpercent=&quot;51.45&quot; style=&quot;width: 50.8537%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTpMp/btsMPrXyvzH/LA4iKdtIlu4C7FcRs9KR9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTpMp%2FbtsMPrXyvzH%2FLA4iKdtIlu4C7FcRs9KR9K%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;1672&quot; height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eDxLju/btsMOu0fiz5/zfKhhW5Kpq9FDpiyr9V6Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eDxLju/btsMOu0fiz5/zfKhhW5Kpq9FDpiyr9V6Rk/img.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;810&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.9835%;&quot; data-widthpercent=&quot;48.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eDxLju/btsMOu0fiz5/zfKhhW5Kpq9FDpiyr9V6Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeDxLju%2FbtsMOu0fiz5%2FzfKhhW5Kpq9FDpiyr9V6Rk%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;1380&quot; height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 표는 keyboard에서 사용하는 아스키코드값입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pXj63/btsMQggwqOf/AsHnDicOrIzGsblXrQJR7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pXj63/btsMQggwqOf/AsHnDicOrIzGsblXrQJR7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pXj63/btsMQggwqOf/AsHnDicOrIzGsblXrQJR7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpXj63%2FbtsMQggwqOf%2FAsHnDicOrIzGsblXrQJR7K%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;722&quot; height=&quot;507&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Jack OS : Sys&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;부트스트래핑은 전원을 키거나 전반적인 재설정이 일어날때 컴퓨터의 메모리에 기본적인 소프트웨어를 로드하는 과정입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;특히 os가 필요에 따라 소프트웨어를 로드하게됩니다.&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;1720&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y9TnL/btsMOQKspc1/W4COuGQKeVLYSGkSkHVRf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y9TnL/btsMOQKspc1/W4COuGQKeVLYSGkSkHVRf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y9TnL/btsMOQKspc1/W4COuGQKeVLYSGkSkHVRf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy9TnL%2FbtsMOQKspc1%2FW4COuGQKeVLYSGkSkHVRf0%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;228&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;228&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;부트스트래핑이 일어나기 위해 jack 플랫폼에서는 몇가지 약속이 필요합니다.&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;1694&quot; data-origin-height=&quot;920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE7VW8/btsMPOESnNR/Z1hpmO4Z7IriS3dgUfLGP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE7VW8/btsMPOESnNR/Z1hpmO4Z7IriS3dgUfLGP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE7VW8/btsMPOESnNR/Z1hpmO4Z7IriS3dgUfLGP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE7VW8%2FbtsMPOESnNR%2FZ1hpmO4Z7IriS3dgUfLGP1%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;750&quot; height=&quot;407&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;920&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;1. 컴퓨터가 실행되거나 재설정될 때 ROM[0]에 위치한 명령어가 실행되어야합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. vm번역기에서 프로그램을 변환할떄 항상 ROM[0]에 sp=256, call Sys.init&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 실행되는 프로그램은 반드시 Main이라는 클래스가 있어야하고 클래스 내에 main이라는 함수가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Sys init은 os를 초기화하고 반드시 Main.main()을 호출합니다.&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;Sys클래스의 init함수는 필요한 각 클래스들을 초기화 하고 Main.main을 호출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b28WZK/btsMP92YktC/YlpUAzuKeokUBMJye9Uo11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b28WZK/btsMP92YktC/YlpUAzuKeokUBMJye9Uo11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b28WZK/btsMP92YktC/YlpUAzuKeokUBMJye9Uo11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb28WZK%2FbtsMP92YktC%2FYlpUAzuKeokUBMJye9Uo11%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;739&quot; height=&quot;380&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일하게 Sys에 있는 halt, wait함수도 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wait함수는 주어진 밀리초 동안 기다렸다가 반환하는 함수이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;halt는 프로그램 실행을 중단합니다. (while루프로 구현)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wnaCp/btsMQfBZE2p/kspXpGbcYqMNJAMH5uz7c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wnaCp/btsMQfBZE2p/kspXpGbcYqMNJAMH5uz7c1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wnaCp/btsMQfBZE2p/kspXpGbcYqMNJAMH5uz7c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwnaCp%2FbtsMQfBZE2p%2FkspXpGbcYqMNJAMH5uz7c1%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;664&quot; height=&quot;166&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NandToTetris 후기&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;나중에(2026년쯤) 다시 처음부터 개선부분까지 과제를 진행하며 git에 올려봐야겠다는 생각이 강하게 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 내 기록용 포스팅들이 되어버렸지만 다시 리마스터해서는 조금 더 좋은 내용들을 담아서 리마스터 하고싶다.&lt;/p&gt;</description>
      <category>NandToTetris</category>
      <category>nandtotetris</category>
      <category>os</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/97</guid>
      <comments>https://jaykos96.tistory.com/97#entry97comment</comments>
      <pubDate>Sun, 16 Mar 2025 21:13:39 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris : 컴파일러 2: 코드 생성-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/96</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴파일러&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수준 프로그램을 vm코드로 변경해주는 일종의 번역기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수준 언어는 객체와 배열등 다양한 추상적 개념들이 있지만 컴파일러의 vm코드에서는 스택과 가상 세그먼트만 사용 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잭 컴파일러는 jack 코드를 vm 코드로 변환합니다.&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;각각의 파일들은 각각의 vm코드로 변환되며 (filename.jack -&amp;gt; fileName.vm)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브루틴(생성자, 메서드 등)들은 fileName.subName으로 변환됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;1354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lYj1V/btsML9WUm2G/ZpWkZwplCvo9QVA14Gsvqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lYj1V/btsML9WUm2G/ZpWkZwplCvo9QVA14Gsvqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lYj1V/btsML9WUm2G/ZpWkZwplCvo9QVA14Gsvqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlYj1V%2FbtsML9WUm2G%2FZpWkZwplCvo9QVA14Gsvqk%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;674&quot; height=&quot;488&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;1354&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;앞장에서 다뤘던 요소 Tokenizer를 포함하여 총 5개의 모듈이 만들어져야 합니다.&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. JackCompiler&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 jack 파일을 vm파일로 변환하는 핵심 모듈 , JackToknizer를 호출해여 입력을 전달하고 vm파일을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 SymbolTable, VMWriter를 이용해 최종 vm파일을 만들고 반환합니다.&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. SymbolTable&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jack파일에는 여러 종류의 변수들과 scope가 존재하는데 클래스가 컴파일될때 클래스레벨 변수와 서브루틴 레벨 변수를 나누어 테이블을 생성하고 각 변수에 실행 인덱스를 제공합니다.&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. VMWriter&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vm코드를 생성하고 출력 vm파일에 보냅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. ComplitaionEngine&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tokenizer로 부터 부터 입력을 받고 VMWriter를 통해 출력을 작성합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;code generator&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 구문 분석기를 알아보았고 이제는 실행 가능한 vm코드를 만드는 코드 생성기 code generator입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;code generator를 구현하기 위해서는 파서 parsor 의 기능이 일부 필요합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5qy9i/btsMG9vaf6f/tukZesLfTb3TFsHMT9T9bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5qy9i/btsMG9vaf6f/tukZesLfTb3TFsHMT9T9bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5qy9i/btsMG9vaf6f/tukZesLfTb3TFsHMT9T9bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5qy9i%2FbtsMG9vaf6f%2FtukZesLfTb3TFsHMT9T9bK%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;755&quot; height=&quot;344&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jack프로그램은 여러 클래스로 이루어져있고 각 클래스 파일들은 개별적으로 컴파일됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 클래스에서는 class-level code와 subroutine-level code 2가지로 나누어서 컴파일됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 subroutine들을 하나씩 컴파일합니다.&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;서브 루틴을 컴파일할때는 아래 그림처럼 서브 루틴 내에 존재하는 변수, 표현식, if, while, 객체, 배열들이 모두 컴파일되어야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FkPFw/btsMGgVQWC0/FIANS6KrhImzMeOiSgkMjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FkPFw/btsMGgVQWC0/FIANS6KrhImzMeOiSgkMjk/img.png&quot; data-origin-width=&quot;1932&quot; data-origin-height=&quot;1426&quot; data-is-animation=&quot;false&quot; width=&quot;738&quot; height=&quot;545&quot; style=&quot;width: 54.5688%; margin-right: 10px;&quot; data-widthpercent=&quot;55.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FkPFw/btsMGgVQWC0/FIANS6KrhImzMeOiSgkMjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFkPFw%2FbtsMGgVQWC0%2FFIANS6KrhImzMeOiSgkMjk%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;1932&quot; height=&quot;1426&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsiZu4/btsMFsbyM6i/MWZ9NktL6F8mEPQVLeu4H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsiZu4/btsMFsbyM6i/MWZ9NktL6F8mEPQVLeu4H1/img.png&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;666&quot; data-is-animation=&quot;false&quot; width=&quot;433&quot; height=&quot;394&quot; style=&quot;width: 44.2684%;&quot; data-widthpercent=&quot;44.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsiZu4/btsMFsbyM6i/MWZ9NktL6F8mEPQVLeu4H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsiZu4%2FbtsMFsbyM6i%2FMWZ9NktL6F8mEPQVLeu4H1%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;732&quot; height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변수 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 언급한 것 처럼, 변수 또한 class-level과 subroutine-level 두가지 관점에서 존재하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 변수들은 식별자와 타입 (int, char 등), 종류 (locak, static), 범위 (scope)를 가집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(핵 플랫폼에서는 class scope와 subroutine scope 2개만 존재!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPADA4/btsMFhORPUE/yM0SvcfjKuJbeqSnJTD3l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPADA4/btsMFhORPUE/yM0SvcfjKuJbeqSnJTD3l0/img.png&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;792&quot; data-is-animation=&quot;false&quot; width=&quot;707&quot; height=&quot;374&quot; style=&quot;width: 43.8322%; margin-right: 10px;&quot; data-widthpercent=&quot;44.35&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPADA4/btsMFhORPUE/yM0SvcfjKuJbeqSnJTD3l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPADA4%2FbtsMFhORPUE%2FyM0SvcfjKuJbeqSnJTD3l0%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;1496&quot; height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mzHm6/btsMFH0DhIg/KnxdLd8TM6w9KnEWSLl6qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mzHm6/btsMFH0DhIg/KnxdLd8TM6w9KnEWSLl6qk/img.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;648&quot; data-is-animation=&quot;false&quot; style=&quot;width: 55.0051%;&quot; data-widthpercent=&quot;55.65&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mzHm6/btsMFH0DhIg/KnxdLd8TM6w9KnEWSLl6qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmzHm6%2FbtsMFH0DhIg%2FKnxdLd8TM6w9KnEWSLl6qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;Symbol Table&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2hOqf/btsMHdLbSif/oRirp2Wtlq1akmXEPLIkxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2hOqf/btsMHdLbSif/oRirp2Wtlq1akmXEPLIkxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2hOqf/btsMHdLbSif/oRirp2Wtlq1akmXEPLIkxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2hOqf%2FbtsMHdLbSif%2FoRirp2Wtlq1akmXEPLIkxK%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;614&quot; height=&quot;245&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수, 상수, 서브루틴, 클래스 등 프로그램에서 사용되는 식별자(identifier)에 대한 정보를 관리하는 자료구조이며 해시 테이블 추상화로 구성됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 마다 class-level, subroutine-level의 식별자들이 각각 다른 symbol table에 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스는 컴파일 될 때 마다 새롭게 symbol 테이블을 가지게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subroutine symbol 테이블의 첫번째 로우(this)의 type은 항상 해당 클래스를 가지며 argument 0으로 정의됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oUSkc/btsMJcMBEd9/bZsaVCx9SjuBiTtbCK7yQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oUSkc/btsMJcMBEd9/bZsaVCx9SjuBiTtbCK7yQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oUSkc/btsMJcMBEd9/bZsaVCx9SjuBiTtbCK7yQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoUSkc%2FbtsMJcMBEd9%2FbZsaVCx9SjuBiTtbCK7yQK%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;1926&quot; height=&quot;258&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 x + 17이라는 명령이 있다면 먼저 x가 subroutine수준의 변수인지 확인하고, 이 작업이 실패하면(subroutine 수준이 아니면)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 x가 정적변수나 필드변수인지 확인합니다.&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;컴파일러는 symbol table들을 LinkedList로 연결하여 사용하고있기에 현재 범위와 관련된 테이블에서 값을 찾고 관련 테이블에서 그 값을 찾지 못하면 리스트의 다음 테이블에서 찾는 방식으로 찾아나갑니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 어느 테이블에도 없으면 undefined 에러를 발생시킵니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Jack 문자열 컴파일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적을 객체지향 언어들은 Stringd이라는 클래스 인스턴스로 문자열을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 상수가 나올 때 마다 컴파일러는 String 생성자를 호출해서 새로운 String 객체를 생성하고 반환하는 코드를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 초기화는 문자열 상수마다 String메서드의 appendChar를 연달아서 호출하는 방식으로 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대의 언어 (Java, Python)은 gc를 제공하여 사용하지 않는 클래스들을 처리하고, 객체의 효율적 활용을 위해 다양한 최적화와 특화된 문자열 클래스를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;잭 OS는 하나의 String 클래스만 지원하며, 최적화는 지원하지 않는다고 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Jack 표현식 컴파일&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;908&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vH5XV/btsMHoA0BXN/bgfUyMOlm432SPnTFmF10K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vH5XV/btsMHoA0BXN/bgfUyMOlm432SPnTFmF10K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vH5XV/btsMHoA0BXN/bgfUyMOlm432SPnTFmF10K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvH5XV%2FbtsMHoA0BXN%2FbgfUyMOlm432SPnTFmF10K%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;662&quot; height=&quot;455&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;908&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잭은 중위표기법을 사용해서 표현식을 작성합니다.&amp;nbsp; {a * (b + c)}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 우리가 컴파일할 목적 언어는 후위표기법 입니다. {a b c + *}&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;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;vm머신은 스택기반이기에 중위표현식을 직접 처리 할 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그래서 후위표현식으로 변환 후 스택 연산을 이용해 vm코드로 변경하는 작업이 필요합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;위에 그림에서 나와있듯이 parse tree를 통해 표현식들을 변환합니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현식은 컴파일은 compileExpression 루틴에서 처리됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;codewrite 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;codewrite알고리즘은 전달받은 표현식을 vm명령으로 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어온 값이 상수라면 push c 상수를 push하는 명령어를 만듭니다.&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;아래 예시와 같이 x + g(2,y -5, -z)같은 표현식이 들어오면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x에 대한 코드를 먼저 생성하고 g에 대한 코드를 모두 생성하고 op를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBJNXn/btsMJxCNTj7/rXkLhoXebfAFJF6a3zm8kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBJNXn/btsMJxCNTj7/rXkLhoXebfAFJF6a3zm8kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBJNXn/btsMJxCNTj7/rXkLhoXebfAFJF6a3zm8kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBJNXn%2FbtsMJxCNTj7%2FrXkLhoXebfAFJF6a3zm8kk%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;631&quot; height=&quot;400&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;전체 Jack 표현식&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Jack언어의 표현식 문법입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sDx6e/btsMIwSpO6S/0cWDCR9fTT8K0XI0hg9NT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sDx6e/btsMIwSpO6S/0cWDCR9fTT8K0XI0hg9NT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sDx6e/btsMIwSpO6S/0cWDCR9fTT8K0XI0hg9NT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsDx6e%2FbtsMIwSpO6S%2F0cWDCR9fTT8K0XI0hg9NT0%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;594&quot; height=&quot;450&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Jack 명령문 컴파일 (let, do, return)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return 명령문은 compileExpression 루틴을 호출해서, 표현식의 값을 계산하여 스택에 넣는 vm코드를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 return vm명령을 생성합니다.&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;let 명령문 (let varName = expression)은 varName을 기억하는것 부터 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 compileExpression을 호출하여 표현식의 값을 스택에 넣고 마지막으로 pop varName vm명령을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 varName은 실제로 symbol 테이블에서 varName에 매핑됩니다. (local 3, static 1 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;do 명령문 (do className functionName(exp1, exp2, ..., expn))은 서브루틴의 반환값은 무시하고 서브루틴의 기능만을 호출하기 위한 명령문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compileExpression를 호출하고 pop temp 0 명령으로 최상위 스택 항목을 제거하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Jack 명령문 컴파일 (while, if)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;while, if문들은 vm언어의 goto, if-goto, label로 변형되어야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if, while문의 경우에는 항상 expression의 true, false를 통해 실행할 코드가 변경됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOoyag/btsMI8p03pR/1M8jMlqvYbPHq9Vo47PSvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOoyag/btsMI8p03pR/1M8jMlqvYbPHq9Vo47PSvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOoyag/btsMI8p03pR/1M8jMlqvYbPHq9Vo47PSvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOoyag%2FbtsMI8p03pR%2F1M8jMlqvYbPHq9Vo47PSvk%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;698&quot; height=&quot;427&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;676&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;if 문은 먼저 expression의 값을 계산하고 그 결과에 대해 부정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 부정한값이 참이면 L1으로 이동하고 거짓이면 L2로 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;while문은 label (L1)을 만들고 expression을 계산하는 코드를 생성합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 계산된 expression을 부정합니다. 값이 참이면 label L2로 가서 빠져나갑니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Jack 객체 컴파일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향 언어들은 객체 object라는 집합적인 추상 개념을 다룰 수 있는 수단을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체들은 static, field, local, argument로 참조되는 메모리 블록으로 구현되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 변수 또는 포인터라고 하는 참조 변수들은 메모리 블록의 시작 주소를 담고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잭은 heap을 통해 객체를 구현합니다.&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;p객체의 foo메소드가 있다고 생각해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p.foo()와 같은 호출이 호출되면 (피호출) foo안의 코드들은 this위에서 동작해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 메서드가 실행될 때, 해당 메서드가 호출된 객체의 필드(인스턴스 변수)를 변경할 수 있도록 this를 올바르게 설정해야합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말이 조금 어려운데, 예를들어 Jack 컴파일러가 피호출되는 p.foo();를 VM 코드로 변환할 때,&lt;br /&gt;아래와 같이 this 세그먼트가 p 객체의 필드와 연결되도록 설정하는 과정이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;배열 또한 Array 클래스의 인스턴스로 구현되어 있기에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;배열을 사용할때도 객체의 방법과 유사하게 동작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741874296912&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;push argument 0   // p 객체의 주소 (현재 메서드가 실행되는 객체) 
                  // 메서드는 항상 첫 번째 인자로 자신을 호출한 객체의 주소를 받음
pop pointer 0     // this = p; 객체의 주소 저장&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Jack 생성자 컴파일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 관점에서의 호출 컴파일과 피호출자 관점에서의 컴파일 두가지가 존재합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;생성자 호출 컴파일 (caller)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 생성은 크게 2가지 단계로 나뉩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. var Point p와 같이 클래스 타입 변수가 선언될 때&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. let p = Point.new(2,3)과 같이 클래스 생성자를 호출해서 그 객체가 인스턴스로서 생성 될 때&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 인스턴트 생성&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령문을&lt;span&gt;&amp;nbsp;&lt;/span&gt;let p = Point.new(2,3)이 실행되면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생성자가 Point인스턴스를 나타내는 크기만큼 메모리를 할당하고 그 값을 초기화해야하고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;p가 이 블록의 시작 주소를 참조하도록 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1368&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csclq1/btsMJag28Fi/kcaybdGHixS3aGmj1LKrTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csclq1/btsMJag28Fi/kcaybdGHixS3aGmj1LKrTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csclq1/btsMJag28Fi/kcaybdGHixS3aGmj1LKrTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcsclq1%2FbtsMJag28Fi%2FkcaybdGHixS3aGmj1LKrTK%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;551&quot; height=&quot;383&quot; data-origin-width=&quot;1368&quot; data-origin-height=&quot;950&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;위 그림에서 2개의 객체가 생성되었는데, 생성자는 각각 두 객체의 메모리를 할당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 컴파일이 되면 symbol 테이블이 업데이트되고 저수준 코드가 생성되지만, 그 이후에는 어떠한 동작도 하지않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체는 런타임, 즉 컴파일된 코드가 실행될 때 생성되고 변수에 바인딩됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;생성자 호출 컴파일 (callee)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 생성자가 실행될 때 생성자의 관점 (피호출자)에서도 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자는 argument와 local변수 그리고 명령문을 사용 할 수 있는 서브루틴이며 컴파일러도 생성자를 서브루틴으로 취급합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ0YZv/btsMIw5WcwY/wNojdhnskiRlHLObcP0F10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ0YZv/btsMIw5WcwY/wNojdhnskiRlHLObcP0F10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ0YZv/btsMIw5WcwY/wNojdhnskiRlHLObcP0F10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ0YZv%2FbtsMIw5WcwY%2FwNojdhnskiRlHLObcP0F10%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;693&quot; height=&quot;443&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 객체를 생성하려면 먼저 객체의 크기만큼 RAM에 공간을 할당해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 Memory.alloc(size)라는 os함수를 실행하여 객체가 필요한만큼의 공간크기를 지정하고 그 주소를 반환합니다.&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;Memory.alloc(size)을 호출하기 전 컴파일러는 필요한 메모리 블록의 크기를 계산합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Point객체에 int x, y가 있다면 push constant 2, call Memory.alloc 명령을 생성해서 Memory.alloc(2)함수를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alloc은 크기가 2인 사용 가능한 메모리 블록을 찾고 그 시작 주소를 stack에 push합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 pop point 0명령을 실행하여 this를 alloc이 반환한 주소로 설정합니다.&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;위 그림의 코드를 보면 이렇게 this가 처리된 후 let x = ax;라는 명령이 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 x가 어떤 변수인지 세그먼트 확인을 위해 symbol 테이블을 참조합니다. (x는 this 0, ax는 argument 0입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 compileLet 루틴이 push argument 0, pop this 0명령을 생성합니다.&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;그리고 잭의 모든 생성자는 return this로 끝나야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 컴파일러는 생성자 컴파일의 마지막에 push pointer 0, return을 붙이게 됩니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Jack 메소드 컴파일&lt;/h2&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;클래스의 메소드는 호출되는 클래스 (callee)에 의해 캡슐화 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 vm언어에는 객체나 메서드에 대한 개념이 없기에 객체지향이 아닌 절차지향 언어처럼 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잭 언어는 두 종류의 메소드 호출을 지원합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;varName.methodName(exp1, ..., expn) : varName이 참조하는 객체에 메소드 적용 (caller)&lt;/li&gt;
&lt;li&gt;methodName(exp1, ..., expn) : 현재 객체에 객체에 메소드 적용 (callee)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;varName.methodName(exp1, ..., expn) caller시점&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XGluU/btsMHSPnBN6/aUVwv98juWe0oSo9rkQoQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XGluU/btsMHSPnBN6/aUVwv98juWe0oSo9rkQoQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XGluU/btsMHSPnBN6/aUVwv98juWe0oSo9rkQoQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXGluU%2FbtsMHSPnBN6%2FaUVwv98juWe0oSo9rkQoQk%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;630&quot; height=&quot;433&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 메소드 호출 varName.methodName(exp1, ..., expn) 컴파일되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 주소 push varName 명령을 시작하여 모든 argument와 기본주소(p1)를 stack에 push합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 compileExpresion을 n번 호출합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 call className.methodName n+1을 실행합니다. (만약 argument가 없다면 n+1이 아닌 1)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;methodName(exp1, ..., expn) (callee)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1368&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kL8sN/btsMH4B3LaF/KNkPXTLX6fLDZFQoZkfUkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kL8sN/btsMH4B3LaF/KNkPXTLX6fLDZFQoZkfUkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kL8sN/btsMH4B3LaF/KNkPXTLX6fLDZFQoZkfUkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkL8sN%2FbtsMH4B3LaF%2FKNkPXTLX6fLDZFQoZkfUkk%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;689&quot; height=&quot;488&quot; data-origin-width=&quot;1368&quot; data-origin-height=&quot;968&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;methodName(exp1, ..., expn) 처럼 현재 객체에서 실행되는 메소드도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 this 세그먼트가 설정되어 있기 때문에, 이 경우에는 this가 없이, 필요한 인자를 바로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 argument만 스택에 push하고 필요햔 명령어를 실행하면 됩니다.&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;마지막으로 void메소드는 고수준언어 시점에서는 값을 반환하지 않지만 잭 컴파일러에서는 반드시 값을 반환해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 컴파일러에서는 void메소드의 마지막에 push constant 0, return 명령을 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 caller코드는 pop temp 0를 실행하여 스택에서 불필요한 값을 제거합니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Jack 배열 컴파일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수준언어에서 var Array arr가 선언되면 컴파일러는 이 배열을 local 0로 초기화합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 Array 클래스의 서브루틴을 호출해서 배열에 해당하는 공간을 heap에 할당합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;새로 할당된 heap주소는 local 0에 저장됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;symbol테이블에는 local, 0, Array, arr 이 4개의 컬럼을 첫 로우에 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkIqbz/btsMJeKqJML/0yuPuptyEl8E2I0QKmMVr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkIqbz/btsMJeKqJML/0yuPuptyEl8E2I0QKmMVr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkIqbz/btsMJeKqJML/0yuPuptyEl8E2I0QKmMVr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkIqbz%2FbtsMJeKqJML%2F0yuPuptyEl8E2I0QKmMVr1%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;717&quot; height=&quot;392&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;706&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;위 그림의 vm 코드를 보면 push local 0으로 배열의 주소를 초기화하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 크기인 constant 2도 push 후 add합니다. 이 결과로 대상 주소가 스택에 push됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 pop point 1로 주소를 that 세그먼트에 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;that에 arr의 주소가 들어갔기에 이제 배열을 조작할 수 있습니다.&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;위 그림의 코드에는 없지만 push 17, pop that 0 명령을 사용해서 배열에 값 17을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(배열에 다른 인덱스에 값을 넣을때는 offset을 추가해서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 push that 0, pop local 1과 같은 명령을 통해 배열에 값을 참조 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위와같은 방식에 하나의 문제가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 let a = b[j]같은 명령에는 동작하지만 let a[i] = b[j]처럼 할당 명령 왼편에도 인덱스가 있는 경우에는 실패합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와같은 let arr[expression1] = expression2 에서도 제대로 동작하기 위한 전략이 조금 필요합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSSdfx/btsMJp52Oi1/0uXFbeXtyrCxH9u03dCn1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSSdfx/btsMJp52Oi1/0uXFbeXtyrCxH9u03dCn1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSSdfx/btsMJp52Oi1/0uXFbeXtyrCxH9u03dCn1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSSdfx%2FbtsMJp52Oi1%2F0uXFbeXtyrCxH9u03dCn1k%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;696&quot; height=&quot;284&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;아래 그림은&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;let arr[expression1] = expression2와 같은 코드가 동작하기 위한 vm코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1968&quot; data-origin-height=&quot;1390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyGuES/btsMMJDeYoO/Km5ciDHt9QCHoFti9g03qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyGuES/btsMMJDeYoO/Km5ciDHt9QCHoFti9g03qk/img.png&quot; data-alt=&quot;let arr[expression1] = expression2 을 위한 전략&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyGuES/btsMMJDeYoO/Km5ciDHt9QCHoFti9g03qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyGuES%2FbtsMMJDeYoO%2FKm5ciDHt9QCHoFti9g03qk%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;698&quot; height=&quot;493&quot; data-origin-width=&quot;1968&quot; data-origin-height=&quot;1390&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;let arr[expression1] = expression2 을 위한 전략&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 state1 까지 코드가 실행되면 a[i], b[j]의 RAM주소가 stack에 들어갑니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이후 stack의 b[j]의 기본주소를 pop하여 pointer 1에 넣습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;that 0가 b[j]와 정렬되었기에 b[j]에서 찾은 값을 temp 0에 넣습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;pop pointer 1을 실행해서 a[i]주소도 정렬하고 stack을 비웁니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 방법으로 a[i] = b[j]를 가능하게 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 순간부터 바쁘다는 핑계로 과제를 안하고있는데, 이제 마지막장 운영체제만 남았으니 일단 마저 내용만 다 정리한다음 나중에 다시 보면서 과제를 진행해야겠다..ㅠㅠ&lt;/p&gt;</description>
      <category>NandToTetris</category>
      <category>nandtotetris</category>
      <category>컴파일러</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/96</guid>
      <comments>https://jaykos96.tistory.com/96#entry96comment</comments>
      <pubDate>Mon, 10 Mar 2025 19:25:05 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris : 컴파일러 1: 구문 분석-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/95</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfDvz6/btsMF4mYMzR/XHvmUsWZlkwkWuz04dkFt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfDvz6/btsMF4mYMzR/XHvmUsWZlkwkWuz04dkFt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfDvz6/btsMF4mYMzR/XHvmUsWZlkwkWuz04dkFt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfDvz6%2FbtsMF4mYMzR%2FXHvmUsWZlkwkWuz04dkFt1%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;664&quot; height=&quot;446&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 과정은 크게 둘로 나뉩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 프로그램의 구문(syntax)를 이해하고, 그 구문을 통해 프로그램의 의미(semantics)를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예를 들어 프로그램 코드를 분석(parsing)해서 배열을 선언하거나 객체를 조작하려고 한다는 사실을 파악했다고 하면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그 언어의 구문을 이용해 코드를 다시 표현 할 수 있습니다. 이를 구문 분석(syntac analysis)라고 부릅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구문 분석기는 tokenizer와 parser로 나뉩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 과정인 코드 생성(code generation)은 다음장에서 다룹니다.&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;핵 플랫폼에서는 구문 분석기가 소스코드의 구문 구조를 반영하는 xml 마크업 파일을 출력하도록 하는 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구문 분석기에서 만든 xml 파일을 code generator가 사용합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tokenizer&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ubs5j/btsMF5GbX1l/VEziCCeHNwAskeq2uhZtpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ubs5j/btsMF5GbX1l/VEziCCeHNwAskeq2uhZtpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ubs5j/btsMF5GbX1l/VEziCCeHNwAskeq2uhZtpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUbs5j%2FbtsMF5GbX1l%2FVEziCCeHNwAskeq2uhZtpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;632&quot; height=&quot;443&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구문 분석기에서 입력된&amp;nbsp; 문자들을 토큰이라는 언어의 기본 요소들로 분류하는 tokennizing 단계를 처리하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;토큰들을 의미가 있고 구조화된 명령문들로 분석하는 단계입니다.&lt;/b&gt;&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;b&gt;Tokenizer에 입력이 들어오면 먼저 들어온 문자들을 어휘(lexicon)에 맞게 분류합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/betCEu/btsMFvSMSCE/9DlKcRQgkHvyjrU68uDDI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/betCEu/btsMFvSMSCE/9DlKcRQgkHvyjrU68uDDI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/betCEu/btsMFvSMSCE/9DlKcRQgkHvyjrU68uDDI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbetCEu%2FbtsMFvSMSCE%2F9DlKcRQgkHvyjrU68uDDI0%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;418&quot; height=&quot;581&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍 언어의 명세에는 언어가 인식하는 token 또는 단어의 유형이 존재합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리의 jack 언어는 다섯 가지 유형으로 분류됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 유형으로 정의되는 토큰들을 모아서 언어의 어휘 (lexicon)이라 부릅니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;키워드(class, while)&lt;/li&gt;
&lt;li&gt;기호(+,&amp;lt;)&lt;/li&gt;
&lt;li&gt;정수형 상수(17,314)&lt;/li&gt;
&lt;li&gt;문자열 상수(&quot;FAQ&quot;, &quot;Frequently Asked Questions&quot;)&lt;/li&gt;
&lt;li&gt;식별자(변수, 클래스, 서브 루틴의 이름을 뜻하는 텍스트 레이블)&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;&lt;b&gt;구문 분석의 첫 단계는 공백, 주석을 무시하고 문자들을 언어 어휘로서 정의된 토큰으로 분류하는 것 입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계는 어휘 분석 analysis, 스캐닝 scanning, 토큰화 tokenizing로 불리며 모두 같은 뜻입니다.&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;아래 그림에서 jack언어의 어휘(lexcion)들과 일반적인 코드들이 어떻게 처리되는지 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEYEI1/btsMFIdpOHz/Vw6Mp9oTzvZKS5ksg56yKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEYEI1/btsMFIdpOHz/Vw6Mp9oTzvZKS5ksg56yKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEYEI1/btsMFIdpOHz/Vw6Mp9oTzvZKS5ksg56yKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEYEI1%2FbtsMFIdpOHz%2FVw6Mp9oTzvZKS5ksg56yKk%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;674&quot; height=&quot;476&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;문법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 또는 단어스트림으로 인풋으로 주어진 텍스트를 처리하고 나면, 그 단어들을 유효한 문장으로 묶을 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문법적으로 유효한 문장을 만들기 위한 문법은 메타 언어 meta-language (언어를 기술하는 언어)로 작성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xRa77/btsMETUqIjF/J22P0Bikk3ggcZxu3ZWR6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xRa77/btsMETUqIjF/J22P0Bikk3ggcZxu3ZWR6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xRa77/btsMETUqIjF/J22P0Bikk3ggcZxu3ZWR6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxRa77%2FbtsMETUqIjF%2FJ22P0Bikk3ggcZxu3ZWR6k%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;645&quot; height=&quot;526&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1741523224980&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# if조건문 문법
# 규칙 이름 : 문법
ifStatement: 'if' '(' expression ')' '{' statements '}'&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;Jack의 if문은 'if' 키워드로 시작해야 함&lt;/li&gt;
&lt;li&gt;다음에 '(' (여는 괄호) expression (조건식, 예: x &amp;gt; 5) ')' (닫는 괄호)&lt;/li&gt;
&lt;li&gt;&amp;nbsp;'{' (여는 중괄호) statements (실행할 코드)'}' (닫는 중괄호)&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문법은 3가지 요소로 순서대로 구성되는데&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단말 terminal, 비단말 non-terminal, 한정자 quailifier 입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단말 Terminal : 토큰, 오른쪽이 상수로만 구성된 규칙&lt;/li&gt;
&lt;li&gt;비단말 Non-Terminal : 다른 규칙의 이름과 상수로 구성된 규칙&lt;/li&gt;
&lt;li&gt;한정자 Quailifier : |, *, ?, (, )의 다섯가지 기호&lt;/li&gt;
&lt;/ul&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 style=&quot;color: #333333; text-align: start;&quot;&gt;한정자 quailifier&lt;span&gt; &lt;/span&gt;&lt;/span&gt;는 특정요소가 몇번 등장 할 수 있는지 나타내는 기호입니다.&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;예를들어 &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;statements: statement* 는&amp;nbsp;let y = 10; if (x &amp;gt; y) { let x = y; } 이런식으로 여러개의 statement를 허용한다는 의미입니다.&lt;/li&gt;
&lt;li&gt;statement: letStatement | ifStatement | whileStatement &amp;rarr; statement는 let, if, while 중 하나만 선택 가능하다는 의미가 됩니다.&lt;/li&gt;
&lt;li&gt;expression: term (op term)? &amp;rarr; term은 필수, (op term)은 0개 또는 1개만 허용된다는 의미입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 표에서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단말 Terminal은 아래 그림에서 if, let 처럼 굵게 처리된 단어들입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비단말 Non-Terminal은 기울임꼴로 적혀있는 단어들입니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgaAyE/btsMEAgvrtl/9QKtBUsoEGDZ5EG15wK221/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgaAyE/btsMEAgvrtl/9QKtBUsoEGDZ5EG15wK221/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgaAyE/btsMEAgvrtl/9QKtBUsoEGDZ5EG15wK221/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgaAyE%2FbtsMEAgvrtl%2F9QKtBUsoEGDZ5EG15wK221%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;659&quot; height=&quot;486&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파싱 트리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰화된 코드를 문법적 구조로 변환하는 과정에서 생성하는 트리입니다. (xml로 변환)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파싱트리를 통해 jack 프로그램이 올바른 구문을 따르는지 확인하고, 이후 코드 생성 단계에서 이를 활용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵 플랫폼에서는 파싱 트리 구조를 반영하는 마크업 형식의 xml파일을 파서의 출력이 되도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아래 그림을 보면 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;문법을 기준으로 구문들을 분리하고, 들어가서 또 분리하기를 반복하며 (재귀)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRtdqY/btsMGdxoTVX/JsHkEXncnzMHtzyIrVpkz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRtdqY/btsMGdxoTVX/JsHkEXncnzMHtzyIrVpkz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRtdqY/btsMGdxoTVX/JsHkEXncnzMHtzyIrVpkz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRtdqY%2FbtsMGdxoTVX%2FJsHkEXncnzMHtzyIrVpkz1%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;746&quot; height=&quot;456&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 whileStatement 서브루틴이 실행되면 어떤 결과로 파싱트리가 만들어지는지 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfcZqN/btsMFsonshV/ut5XAI7kdrdmClcm8qshi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfcZqN/btsMFsonshV/ut5XAI7kdrdmClcm8qshi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfcZqN/btsMFsonshV/ut5XAI7kdrdmClcm8qshi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfcZqN%2FbtsMFsonshV%2Fut5XAI7kdrdmClcm8qshi1%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;750&quot; height=&quot;574&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;파서 Parser&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;1292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TS9FO/btsMGf9Qlne/YOjHZ5NLfDMDRSlePEiONK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TS9FO/btsMGf9Qlne/YOjHZ5NLfDMDRSlePEiONK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TS9FO/btsMGf9Qlne/YOjHZ5NLfDMDRSlePEiONK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTS9FO%2FbtsMGf9Qlne%2FYOjHZ5NLfDMDRSlePEiONK%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;469&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;1292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파서는 토큰을 순서대로 입력받아 대응하는 파싱 트리를 xml파일로 생성합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파싱 트리를 구성하는 알고리즘이 몇 가지 있는데, 그중&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;재귀 하향 파싱 recursive descent parsing 방법을 사용해 언어 문법에 사용된 중첩 구조를 이용해 토큰화된 입력을 재귀적으로 분석합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;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;파싱 트리는 &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;토큰단위로 분리하기 위해 계속해서&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;재귀적으로 구문을 처리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;whileStatement는 compileWhileState()로 처리되는데&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저 compileWhileState()메소드 안에는 아래 그림처럼 재귀적으로 각 구문들을 처리하는 함수들이 필요합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pp4As/btsMEnIAz81/XR6sIsujlZruDlTBnmPmjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pp4As/btsMEnIAz81/XR6sIsujlZruDlTBnmPmjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pp4As/btsMEnIAz81/XR6sIsujlZruDlTBnmPmjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpp4As%2FbtsMEnIAz81%2FXR6sIsujlZruDlTBnmPmjk%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;744&quot; height=&quot;544&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에 whileStatement는 간단하지만 클래스 static 변수와 인스턴스 field 변수를 정의하는 규칙에서는 더 복잡해집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 그림은 클래스 static 변수와 인스턴스 field 변수를 정의하는 규칙입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wuSwC/btsMF6kQu4z/S7m9aiNKYK5U4S7S2KDY81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wuSwC/btsMF6kQu4z/S7m9aiNKYK5U4S7S2KDY81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wuSwC/btsMF6kQu4z/S7m9aiNKYK5U4S7S2KDY81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwuSwC%2FbtsMF6kQu4z%2FS7m9aiNKYK5U4S7S2KDY81%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;781&quot; height=&quot;226&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;맨 앞에 static, field 두가지 토큰이 다 시용 가능하고, 클래스의 static 변수와 인스턴스 필드 변수 선언은 여러 변수를 한 번에 선언할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;compileClassVarDec() 함수는 입력에 있는 여러 변수 선언을 처리하기 위해 반복문을 사용합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 반복문은 하나의 변수 선언을 처리한 후, 또 다른 변수 선언이 있을 경우 다시 반복하여 처리하는 방식으로 동작합니다. &lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;LL 문법&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPLo8V/btsMESBEQOO/jUBZWKQXvX4oRhAAVSJC40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPLo8V/btsMESBEQOO/jUBZWKQXvX4oRhAAVSJC40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPLo8V/btsMESBEQOO/jUBZWKQXvX4oRhAAVSJC40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPLo8V%2FbtsMESBEQOO%2FjUBZWKQXvX4oRhAAVSJC40%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;708&quot; height=&quot;400&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 LL 문법을 사용하여 코드를 파싱합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LL 문법이란, 왼쪽에서 오른쪽으로 읽으며, 왼쪽(L) 파생을 사용하는 문법을 의미합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;519&quot; data-start=&quot;411&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;461&quot; data-start=&quot;411&quot;&gt;&lt;b&gt;LL(1):&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;첫 번째 토큰만 보고 어떤 문법을 적용할지 결정할 수 있는 경우&lt;/li&gt;
&lt;li data-end=&quot;519&quot; data-start=&quot;462&quot;&gt;&lt;b&gt;LL(2):&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;첫 번째 토큰만으로 구별할 수 없어서, 두 번째 토큰까지 확인해야 하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 var, let, while을 사용하는게 컴파일러를 만들 때 유리하다고 언급되었는데 여기서 그 이유가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;var, let, while 같은 키워드를 앞에 두면 컴파일러는 첫 번째 토큰을 읽는 순간 바로 어떤 구문인지 알 수 있기 때문에&lt;/b&gt;&lt;br /&gt;백트래킹없이 빠르고 효율적인 파싱이 가능해집니다.&lt;/p&gt;
&lt;p 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;첫번째 토큰으로 어느 문법 규칙을 봐야할지 판단할수 있으면 LL(1),&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫번째 토큰으로 모호하면 LL(2)로 두번째 토큰까지 확인합니다.&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이렇게 하면, 파서가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;첫 번째 토큰만 보고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;어떤 규칙을 따라야 하는지 결정할 수 있어 백트래킹 없이도 효율적인 파싱이 가능합니다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Jack문법 정리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;jack 언어의 문법은 아래와 같은 문법을 따르며&lt;/span&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-origin-width=&quot;796&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc9Ujr/btsMGeDCb9n/Q6x6pd2ZKo9rkLDl6NBKTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc9Ujr/btsMGeDCb9n/Q6x6pd2ZKo9rkLDl6NBKTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc9Ujr/btsMGeDCb9n/Q6x6pd2ZKo9rkLDl6NBKTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc9Ujr%2FbtsMGeDCb9n%2FQ6x6pd2ZKo9rkLDl6NBKTk%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;575&quot; height=&quot;289&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OEu7a/btsMFuGZuO2/tURi5DVmH2B4cp7HK4keG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OEu7a/btsMFuGZuO2/tURi5DVmH2B4cp7HK4keG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OEu7a/btsMFuGZuO2/tURi5DVmH2B4cp7HK4keG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOEu7a%2FbtsMFuGZuO2%2FtURi5DVmH2B4cp7HK4keG1%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;743&quot; height=&quot;1009&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구문 분석기 구현목록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵 플랫폼에서는 구문 분석기를 만들기 위해서는 아래의 3가지 모듈을 구현하는것을 제안합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음장에서 기호테이블과 vm코드 생성기를 추가로 만들면 컴파일러가 완성됩니다.&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;JackAnalyzer : 다른 모듈을 불러오고 설정하는 메인 프로그램&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JackTokenizer : 토큰화 모듈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ComplicationEngine : 재귀 하향식 파서 (추후 컴파일 엔진 역할도 함)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JackTokenizer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 스트림의 주석과 공백을 제거하고 한번에 하나의 토큰에 접근하도록 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 JackTokenizer의 함수들입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;1158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwdeq/btsMF7dxVgC/s3CcnVROUCxmHPC7kxqm30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwdeq/btsMF7dxVgC/s3CcnVROUCxmHPC7kxqm30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwdeq/btsMF7dxVgC/s3CcnVROUCxmHPC7kxqm30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmwdeq%2FbtsMF7dxVgC%2Fs3CcnVROUCxmHPC7kxqm30%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;644&quot; height=&quot;643&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;1158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ComplicationEngine&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구문 분석기에서 컴파일 엔진은 JackTokenizer를 통해 들어온 입력코드를 xml태그로 변환해 출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력은 xxx를 컴파일하는 compilexxx루틴들이 생성합니다.&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;대응되는 compilexxx이 없는 문법 규칙들 (type, className, subroutineName, varName, statement, subroutineCall&lt;span&gt;&amp;nbsp;&lt;/span&gt;등) 은&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구조화를 위해 따로 함수(compileXXX())로 만들지 않습니다.&lt;br /&gt;대신, 해당 문법 규칙이 필요한&lt;span&gt;&amp;nbsp;&lt;/span&gt;다른 compileXXX() 함수 안에서 직접 처리합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대부분 LL(1)으로 처리되지만 term, subroutineCall과 같은 경우를 처리할 때에는 LL(2)가 반드시 필요하게됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;subroutineCall 예시 : bar(x + 2)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;term 예시 : (x + 1), arr[2]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;1428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5U4B1/btsMGqDVYyf/dsYKkFuY3TR6GVX6OEcmY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5U4B1/btsMGqDVYyf/dsYKkFuY3TR6GVX6OEcmY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5U4B1/btsMGqDVYyf/dsYKkFuY3TR6GVX6OEcmY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5U4B1%2FbtsMGqDVYyf%2FdsYKkFuY3TR6GVX6OEcmY1%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;732&quot; height=&quot;923&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;1428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JackAnalyzer&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hq27T/btsMETtVxnd/lKKtMNb8IKmK8ADyf6O9dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hq27T/btsMETtVxnd/lKKtMNb8IKmK8ADyf6O9dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hq27T/btsMETtVxnd/lKKtMNb8IKmK8ADyf6O9dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHq27T%2FbtsMETtVxnd%2FlKKtMNb8IKmK8ADyf6O9dK%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;529&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JackTokenizer와 ComplicationEngine를 통해 전체 구문분석을 진행하는 모듈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xxx.jack 파일마다 JackAnalyzer 모듈은 아래와 같은 작업을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Jack 파일을 컴파일하려면 가장 먼저 compileClass가 실행되어야합니다.)&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. Xxx.jack 파일마다 JackTokenizer를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. XXX.xml이라는 출력파일을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JackTokenizer와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;ComplicationEngine를 사용하여 입력파일을 파싱하고 파싱된 코드를 출력파일에 기록합니다.&lt;/p&gt;</description>
      <category>NandToTetris</category>
      <category>nandtotetris</category>
      <category>parser</category>
      <category>구문 분석</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/95</guid>
      <comments>https://jaykos96.tistory.com/95#entry95comment</comments>
      <pubDate>Sun, 9 Mar 2025 21:32:34 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris : 고수준 언어 Jack-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 핵 플랫폼의 고수준 언어 Jack이 드디어 나타났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 java, c++같은 객체기반 언어라고 합니다.&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;'컴파일러와 os를 개발할 때 지지대 역할을 하는 언어'정도로만 생각하고 학습하려합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hello World&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일된 잭 프로그램이 실행될 때는 항상 Main.main함수에서 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잭은 표준 클래스 라이브러기가 존재하며 이를 잭 OS라이브러리라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FwuRr/btsMEbac1Al/XqheCITjicXuT66U8G9Lqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FwuRr/btsMEbac1Al/XqheCITjicXuT66U8G9Lqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FwuRr/btsMEbac1Al/XqheCITjicXuT66U8G9Lqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFwuRr%2FbtsMEbac1Al%2FXqheCITjicXuT66U8G9Lqk%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;661&quot; height=&quot;325&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;절차적 프로그래밍과 배열 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 할당 및 반복을 처리하는 전형적인 명령문들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keyboard와 Output은 os라이브러리의 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잭에서 Array객체는 타입과 상관없이 모든 값들을 저장 할 수 있습니다.&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;추가로 잭에서는 기본데이터 타입으로 int, char, boolean을 지원합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gudly/btsMFxvTp5B/6i4kdZ8BCmmMllvjqchjik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gudly/btsMFxvTp5B/6i4kdZ8BCmmMllvjqchjik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gudly/btsMFxvTp5B/6i4kdZ8BCmmMllvjqchjik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGudly%2FbtsMFxvTp5B%2F6i4kdZ8BCmmMllvjqchjik%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;596&quot; height=&quot;368&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;클래스 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 사진에 Fraction 클래스가 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근자, 생성자, this등 객체지향과 관련된 익숙한 명령들이 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jack은 가비지 컬랙터가 없기에 사용한 객체는 명시적으로 free가 필요하고, 서브루틴은 항상 return문으로 종료해야합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;1304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RJ8Ow/btsMEnaeWnA/8kaQK4ZY9y9Ic4y21l0yHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RJ8Ow/btsMEnaeWnA/8kaQK4ZY9y9Ic4y21l0yHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RJ8Ow/btsMEnaeWnA/8kaQK4ZY9y9Ic4y21l0yHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRJ8Ow%2FbtsMEnaeWnA%2F8kaQK4ZY9y9Ic4y21l0yHK%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;834&quot; height=&quot;687&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;1304&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;아래는 Main함수에서 Fraction 클래스를 사용하는 예제입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sX7jo/btsMEZfs4oO/Fj5emGr8ihk9IyJFwsbFIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sX7jo/btsMEZfs4oO/Fj5emGr8ihk9IyJFwsbFIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sX7jo/btsMEZfs4oO/Fj5emGr8ihk9IyJFwsbFIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsX7jo%2FbtsMEZfs4oO%2FFj5emGr8ihk9IyJFwsbFIk%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;667&quot; height=&quot;313&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모리 관점에서 class&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스와 클래스 인스턴트를 2개 생성하는 main함수가 있습니다.&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;컴파일러에 생성자를 컴파일 할때 os를 호출하고 이때 os가 사용가능한 RAM 공간를 찾아서 RAM 공간에다가 객체를 저장합니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a인스턴트의 local 변수에는 15087이 들어있고 이 변수를 포인터로 취급해서 값을 찾으면 heap에 있는 값 '2' 을 찾을 수 잇습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 heap의주소에는 a인스턴스의 다른 값이 들어있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1972&quot; data-origin-height=&quot;1378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSRSNp/btsMEqETauW/fC96SZpJHGjzFoDZNHSAg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSRSNp/btsMEqETauW/fC96SZpJHGjzFoDZNHSAg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSRSNp/btsMEqETauW/fC96SZpJHGjzFoDZNHSAg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSRSNp%2FbtsMEqETauW%2FfC96SZpJHGjzFoDZNHSAg0%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;1972&quot; height=&quot;1378&quot; data-origin-width=&quot;1972&quot; data-origin-height=&quot;1378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;List&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트의 각 데이터들의 주소는 통제할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자가 동작하면 데이터들은 메모리 전체에 분산되지만, vm머신은 시작주소만 알고있다면 다음 요소의 위치를 계산해서 다음 데이터를 읽을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;1374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yE2Oe/btsMEDKKkzS/wwtqPZSVKdUZyVSIkfwCJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yE2Oe/btsMEDKKkzS/wwtqPZSVKdUZyVSIkfwCJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yE2Oe/btsMEDKKkzS/wwtqPZSVKdUZyVSIkfwCJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyE2Oe%2FbtsMEDKKkzS%2FwwtqPZSVKdUZyVSIkfwCJ0%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;685&quot; height=&quot;489&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;1374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드처럼 리스트를 구현해서 LinkedList를 만들어 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;1196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEv8VG/btsMEJYgG5w/J9STZMLqUx8IxXYJNI0Pb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEv8VG/btsMEJYgG5w/J9STZMLqUx8IxXYJNI0Pb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEv8VG/btsMEJYgG5w/J9STZMLqUx8IxXYJNI0Pb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEv8VG%2FbtsMEJYgG5w%2FJ9STZMLqUx8IxXYJNI0Pb1%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;701&quot; height=&quot;516&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;1196&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;h2 data-ke-size=&quot;size26&quot;&gt;잭 언어 구문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jack 언어에서 임의의 수의 공백, 주석으로 구분되는 기호, 예약어, 상수, 식별자입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;1744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zZQsW/btsME0lcoKw/WudUmcvXnwFOvtTeG5XAMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zZQsW/btsME0lcoKw/WudUmcvXnwFOvtTeG5XAMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zZQsW/btsME0lcoKw/WudUmcvXnwFOvtTeG5XAMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzZQsW%2FbtsME0lcoKw%2FWudUmcvXnwFOvtTeG5XAMk%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;669&quot; height=&quot;738&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;1744&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스들은 xxx.jack이라는 개별파일에 저장되며, 각각 별도로 컴파일됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qgujv/btsMEnIaOX3/xH6oAGHf1mAlU1w337fux0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qgujv/btsMEnIaOX3/xH6oAGHf1mAlU1w337fux0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qgujv/btsMEnIaOX3/xH6oAGHf1mAlU1w337fux0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqgujv%2FbtsMEnIaOX3%2FxH6oAGHf1mAlU1w337fux0%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;768&quot; height=&quot;175&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 선언 이후에는 하나 이상의 서브루틴을 생성 할 수 있으며 각각 생성자, 함수, 메서드 중 하나를 정의합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnAdfl/btsMEpsr4bE/T7NpuwH2kk4JKTFdMS2K21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnAdfl/btsMEpsr4bE/T7NpuwH2kk4JKTFdMS2K21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnAdfl/btsMEpsr4bE/T7NpuwH2kk4JKTFdMS2K21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnAdfl%2FbtsMEpsr4bE%2FT7NpuwH2kk4JKTFdMS2K21%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;552&quot; height=&quot;175&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 문법&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W75P7/btsMD51ZZWL/NKNdv3EwyFs2YWo1ArnZr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W75P7/btsMD51ZZWL/NKNdv3EwyFs2YWo1ArnZr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W75P7/btsMD51ZZWL/NKNdv3EwyFs2YWo1ArnZr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW75P7%2FbtsMD51ZZWL%2FNKNdv3EwyFs2YWo1ArnZr1%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;699&quot; height=&quot;344&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//, /* */ 으로 주석을 표시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;while, function 등 약 70개 정도의 keyword가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var, let, do는 컴파일러 작성을 쉽게하기위해 의도적으로 만들어졌습니다. (추후 컴파일러에서 알게된다고 함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수에는 int, string, boolean 상수가 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;타입 변환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jack에서 문자(char)와 숫자타입은 타입변환이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;string을 char를 변환하려면 charAt()를 통해 변환해야합니다.&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;Array를 선언할때 조금 특이한 문법이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arr = 5000 이런식으로 숫자를 사용해서 배열을 초기화하는데 이는 배열의 기본주소가 5000에서 시작한다는걸 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Fraction 객체를 x를 정의한 다음, 배열객체 arr을 넣는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 x는 arr배열을 가리키게 되며, x.print와 같은 Fraction의 메소드를 사용 가능하게됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와 c++에서도 객체를 배열로 쉽게 직렬화가 가능한데 jack은 그런 관계를 더 명확하게 정의했다고 합니다.&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;그리고 jack은 연산 우선순위가 없어서 4 + 3*3라는 계산식이 21이 나올수도 있습니다.&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;1876&quot; data-origin-height=&quot;1376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfDTiS/btsMEo8fizL/RNoJfl9S3PiKZorPWJ6XFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfDTiS/btsMEo8fizL/RNoJfl9S3PiKZorPWJ6XFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfDTiS/btsMEo8fizL/RNoJfl9S3PiKZorPWJ6XFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfDTiS%2FbtsMEo8fizL%2FRNoJfl9S3PiKZorPWJ6XFk%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;728&quot; height=&quot;534&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;1376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스에서는 서브루틴에서 접근가능한 정적 변수, 클래스 단위에서 정의되어 사용되는 필드 변수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 서브루틴 안에서만 사용되는 로컬 변수, 서브루틴에 전달되는 파라미터 변수 총 4가지가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHXC6W/btsMENsUfeg/CjJZEdjRGPRFKQXRUp5rK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHXC6W/btsMENsUfeg/CjJZEdjRGPRFKQXRUp5rK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHXC6W/btsMENsUfeg/CjJZEdjRGPRFKQXRUp5rK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHXC6W%2FbtsMENsUfeg%2FCjJZEdjRGPRFKQXRUp5rK0%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;715&quot; height=&quot;504&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;명령문&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수준 언어에서 익숙한 명령문들의 문법입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;1030&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brAszx/btsMFL1RHU3/ku8ycN1EpU3A1dM1YSmvb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brAszx/btsMFL1RHU3/ku8ycN1EpU3A1dM1YSmvb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brAszx/btsMFL1RHU3/ku8ycN1EpU3A1dM1YSmvb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrAszx%2FbtsMFL1RHU3%2Fku8ycN1EpU3A1dM1YSmvb0%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;707&quot; height=&quot;561&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;1030&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;jack application 개발&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 작성한 jack언어와 jack os라이브러리(&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;graphics,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;inputs,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Math, String, Array,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Memory,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Sys,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Output&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;)를 사용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 다양한 프로그램을 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;1496&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zVsK9/btsMEXhKzFz/jppOD3hEyxNEAUorVcKSA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zVsK9/btsMEXhKzFz/jppOD3hEyxNEAUorVcKSA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zVsK9/btsMEXhKzFz/jppOD3hEyxNEAUorVcKSA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzVsK9%2FbtsMEXhKzFz%2FjppOD3hEyxNEAUorVcKSA1%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;708&quot; height=&quot;390&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jack 클래스 파일을 만들고 jack언어로 프로그램을 만든다음 jackCompiler에 파일이나 directory를 넣어서 컴파일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 vm파일을 만들고 vm에뮬레이터를 통해 실행시킵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1954&quot; data-origin-height=&quot;1504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qnTiO/btsMEXIN2Gs/JIHSgdci0x4i79XvBJ8qQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qnTiO/btsMEXIN2Gs/JIHSgdci0x4i79XvBJ8qQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qnTiO/btsMEXIN2Gs/JIHSgdci0x4i79XvBJ8qQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqnTiO%2FbtsMEXIN2Gs%2FJIHSgdci0x4i79XvBJ8qQ1%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;672&quot; height=&quot;517&quot; data-origin-width=&quot;1954&quot; data-origin-height=&quot;1504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>NandToTetris</category>
      <category>Jack</category>
      <category>jack 문법</category>
      <category>nandtotetris</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/94</guid>
      <comments>https://jaykos96.tistory.com/94#entry94comment</comments>
      <pubDate>Fri, 7 Mar 2025 21:45:46 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris : 가상머신2-제어-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/93</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;런타임 시스템&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; 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;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; 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;&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;핵 플랫폼에서는 이 문제들을 HACK 플랫폼의 표준 맵핑과 함께 VM 언어 명세로 다룹니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;명세에 따라 vm 번역기를 개발하면 최종적으로 실행 가능한 런타임을 구현 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 vm 번역기는 vm명령 (push, pop, add)등을 어셈블리 명령어로 번역할 뿐 아니라, 위에서 언급한 모든 질문들에 대한 대답을 어셈블리어로 만들 수 있습니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;고수준 마법&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E3fhw/btsMxrDOano/1i8T4OfAWMv8KBABf9rBd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E3fhw/btsMxrDOano/1i8T4OfAWMv8KBABf9rBd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E3fhw/btsMxrDOano/1i8T4OfAWMv8KBABf9rBd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE3fhw%2FbtsMxrDOano%2F1i8T4OfAWMv8KBABf9rBd0%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;521&quot; height=&quot;387&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 수식은 고수준 언어를 이용하면 실제 수직과 거의 유사한 형태로 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿐만아니라 if와 같은 분기문을 이용해서 더 고수준의 논리를 적용 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 컴파일러 및 vm 에서는 저수준에서 분기 명령 및 함수 호출-반환 명령을 구현해야합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함수&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모듈식 프로그래밍의 기본 단위로서, 특정 기능을 수행하도록 서로를 호출 할 수 있는 독립적인 프로그래밍 단위를 말합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;solve라는 함수는 sqrt() 함수를 호출하고 또 sqrt()는 power() 함수를 호출 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 경우에는 재귀적으로 깊이 들어갈수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적으로 호출하는 함수 (caller)는, 호출 받는 함수를 콜리 (callee)에게 인수를 전달하고, 호출되는 함수가 실행을 완료 할 때 까지 실행을 일시 중단합니다.&lt;/b&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;/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;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한 함수가 다른 함수를 호출할 때마다 아래와 같은 추가작업(overhead)들이 필요합니다.&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;size18&quot;&gt;&lt;b&gt;콜러가 콜리 호출 시 발생하는 오버헤드&lt;/b&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;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; 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;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; 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;/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;size18&quot;&gt;&lt;b&gt;&amp;nbsp;콜리가 값 반환시 발생하는 오버해드&lt;/b&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;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; 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;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; 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;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;분기&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mjW6x/btsMxC6jU3n/fjfQFrg8thtdQrG9kc1eP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mjW6x/btsMxC6jU3n/fjfQFrg8thtdQrG9kc1eP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mjW6x/btsMxC6jU3n/fjfQFrg8thtdQrG9kc1eP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmjW6x%2FbtsMxC6jU3n%2FfjfQFrg8thtdQrG9kc1eP1%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;801&quot; height=&quot;257&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&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;저수준 프로그래밍에서 분기는 goto destination 명령으로 실행됩니다.&lt;/b&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;목적기 (destination)에는 여러 형식이 있지만, 가장 기초적인건 다음에 실행될 실제 메모리의 주소입니다.&lt;/b&gt;&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;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; 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;&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;핵 플랫폼에서 만드는 vm언어에서는 label symbol이라는 구문으로 레이블 지시문을 사용 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;vm언어에서는&amp;nbsp;무조건 분기 (unconditional branching), 조건부 분기 (conditional branching) 두가지가 존재합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;vm언어에서는 불 표현식을 스택에 넣는 방식으로 조건을 지정합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 if (n&amp;lt;100) goto LOOP를 push n, push 100, lt, if-goto LOOP로 번역합니다.&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;size18&quot;&gt;&lt;b&gt;무조건 분기 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;(unconditional branching)&lt;/span&gt;&lt;/b&gt;&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; - goto symbol 명령으로 수행&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; - 코드에서 label symbol바로 다음 명령으로 점프해서 그 명령을 실행하라는 뜻&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;size18&quot;&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;(conditional branching)&lt;/span&gt;&lt;/b&gt;&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; - if-goto symbol몀령으로 수행&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; - 스택의 최상위 값을 pop하여, 그 값이 true이면 label symbol 명령의 바로 다음 명령으로 점프해 그 명령을 실행하고, false면 코드에서 바로 다음 명령을 실행하라는 뜻&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;x와 y에 대한 함수가 있습니다. (sum = sum + x, y times)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;while문에 조건을 이용하면 간단하지만 vm 코드에서는 vm의 분기명령인 goto, if-goto, lable을 이용해 루프를 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;1396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cM620k/btsMxkEQUK8/6zh31EabfRGht0b1RZVxKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cM620k/btsMxkEQUK8/6zh31EabfRGht0b1RZVxKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cM620k/btsMxkEQUK8/6zh31EabfRGht0b1RZVxKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcM620k%2FbtsMxkEQUK8%2F6zh31EabfRGht0b1RZVxKK%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;549&quot; height=&quot;605&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;1396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;함수&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;1138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctwztD/btsMzROWJoE/QIeCDO4zqMk7KVEkfqRfl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctwztD/btsMzROWJoE/QIeCDO4zqMk7KVEkfqRfl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctwztD/btsMzROWJoE/QIeCDO4zqMk7KVEkfqRfl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctwztD%2FbtsMzROWJoE%2FQIeCDO4zqMk7KVEkfqRfl1%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;566&quot; height=&quot;467&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;1138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 사진의 vm코드쪽을 보면 push나 sub같은 명령어들은 이제 익숙하지만 call Math.multiply라는 함수를 호출하는 명령어가 등장합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앞서 구현했던 ALU에 곱셈은 없기에 핵 컴퓨터에서 곱셈연산을 하기위해선 Math.multiply라는 운영체제의 함수가 필요합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;vm언어는 운영체제와 인터페이스가 가능합니다!&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;함수는 자신만의 작업스택과 메모리 세그먼트를 사용합니다.&lt;/b&gt;&lt;b&gt;&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;&lt;b&gt;예시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수는 일반적으로 지역(local)변수와 인수(argument)변수들을 사용합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd7PxG/btsMzeDDHiI/oeN6MLiof7NWkBuqtpHCp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd7PxG/btsMzeDDHiI/oeN6MLiof7NWkBuqtpHCp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd7PxG/btsMzeDDHiI/oeN6MLiof7NWkBuqtpHCp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd7PxG%2FbtsMzeDDHiI%2FoeN6MLiof7NWkBuqtpHCp0%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;763&quot; height=&quot;269&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;644&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l6IkD/btsMx5U5BQn/ryu6BIOGMrKXGMpNnv0cN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l6IkD/btsMx5U5BQn/ryu6BIOGMrKXGMpNnv0cN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l6IkD/btsMx5U5BQn/ryu6BIOGMrKXGMpNnv0cN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl6IkD%2FbtsMx5U5BQn%2Fryu6BIOGMrKXGMpNnv0cN1%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;739&quot; height=&quot;311&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 main함수는 3,8,5를 스택에 넣고 mult 2 함수를 실행합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;mult 2 함수는 실행되면 자신만의 독립적인 스택공간들을 가집니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 0번줄 : argument세그먼트에 스택에 들어있던 2개의 인수 8,5가져오고, local 세그먼트는 인수를 2개로 받았기에 2개의 공간을 0으로 초기화 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 1~4번줄 : 함수는 push constant 0, pop local 0, push constant 1, pop local 1 명령을 실행해 로컬 세그먼트를 0,1로 초기화 시킵니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 7~8번줄 : 함수는 local 1의 값과 argument 1의 값을 함수의 private 스택에 추가합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 연산이 수행되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에 20번줄에서 push local 0명령어를 통해 연산된 값 40을 함수의 private 스택에 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 값은 caller로 반환되어 main함수의 스택에 값 40이 들어갑니다.&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;다른 예제도 간단하게 확인해 보겠습니다.&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;아래 예제에서는 main -&amp;gt; hypot -&amp;gt; mult 순서대로 함수가 호출되는데 이렇게 프로그램에 실행에 관련된 모든 함수를 개념적으로 가리키기 위해 이와같은 상황 및 함수들의 관계를 콜링 체인 (calling chain)이라고 부릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zpQ2u/btsMzfikgz1/2bEAtdPCT5mRQ2qo5z3Vrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zpQ2u/btsMzfikgz1/2bEAtdPCT5mRQ2qo5z3Vrk/img.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;1032&quot; data-is-animation=&quot;false&quot; width=&quot;368&quot; style=&quot;width: 22.4178%; margin-right: 10px;&quot; data-widthpercent=&quot;22.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zpQ2u/btsMzfikgz1/2bEAtdPCT5mRQ2qo5z3Vrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzpQ2u%2FbtsMzfikgz1%2F2bEAtdPCT5mRQ2qo5z3Vrk%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;522&quot; height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/banxBa/btsMAl9Z4wT/dQo50KX1lBzoNEbE8VNWzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/banxBa/btsMAl9Z4wT/dQo50KX1lBzoNEbE8VNWzK/img.png&quot; width=&quot;433&quot; height=&quot;251&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;602&quot; data-origin-width=&quot;1038&quot; data-widthpercent=&quot;77.32&quot; style=&quot;width: 76.4194%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/banxBa/btsMAl9Z4wT/dQo50KX1lBzoNEbE8VNWzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbanxBa%2FbtsMAl9Z4wT%2FdQo50KX1lBzoNEbE8VNWzK%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;1038&quot; height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&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;main 함수가 hypot을 실행하면 hypot 함수에 기존 stack에 있던 인자 3,4가 hypot 함수의 stack에 들어갑니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 hypot함수는 인자로 들어온 x(3)을 함수의 private 스택에 2번 넣고 mult 함소를 호출합니다.&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;mult함수에서는 local 세그먼트의 인덱스를 sum, i로 표기하고&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;16 ~ 19번 명령을 통해 초기화합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;연산이 수행되고 36번줄에서 확인해보면 multi함수의 private 스택에는 연산결과 9를 확인 할 수 있고 이 값은 hypot로 반환됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 hypot 함수의 private 스택에는 mult의 결과값인 9가 남고, 이후 yy도 동일하게 하여 9,16을 만듭니다.&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;h2 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Function Call And Return&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;함수가 호출될때와 종료될때에 대해 알아보겠습니다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1. Sets arg&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;caller에서 함수를 호출하면 stack에는 먼저 callee로 전달될 인수(argument)들이 필요합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 작업하고있는 스택의 바로 아래서부터 callee가 필요한 인수만큼 만큼 argument들의 주소가 할당됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxSU7/btsMxJSg1q3/KhgYwYAz6PNKcgStoioJj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxSU7/btsMxJSg1q3/KhgYwYAz6PNKcgStoioJj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxSU7/btsMxJSg1q3/KhgYwYAz6PNKcgStoioJj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxSU7%2FbtsMxJSg1q3%2FKhgYwYAz6PNKcgStoioJj1%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;780&quot; height=&quot;293&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. Saves the caller's frame&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;callee가 호출되면 caller는 작업하던 상태를 보관하고 callee로 넘어가야합니다.&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;그 와중에 callee는 callee의 지역 변수, 매개변수, this, that과 같은 포인터가 사용되는데 이때 caller의 정보들에 덮어씌우면 안되기에&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;caller만의 정보를 저장해 두어야 한다. &lt;b&gt;이때 caller의 정보를 스택에 쌓아두는데 이 값들을 모두 합쳐 프레임이라고 부릅니다.&lt;/b&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;1292&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l9xKP/btsMyCyIXGZ/pa7H851z95UdlIThe7ZiMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l9xKP/btsMyCyIXGZ/pa7H851z95UdlIThe7ZiMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l9xKP/btsMyCyIXGZ/pa7H851z95UdlIThe7ZiMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl9xKP%2FbtsMyCyIXGZ%2Fpa7H851z95UdlIThe7ZiMK%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;687&quot; height=&quot;341&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3. Jumps to execute callee&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같은 작업이 끝나면 callee로 이동합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2oUWV/btsMAWJbbp1/KjIXZ0ck1OfV8CMHCNcpW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2oUWV/btsMAWJbbp1/KjIXZ0ck1OfV8CMHCNcpW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2oUWV/btsMAWJbbp1/KjIXZ0ck1OfV8CMHCNcpW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2oUWV%2FbtsMAWJbbp1%2FKjIXZ0ck1OfV8CMHCNcpW0%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;744&quot; height=&quot;366&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;이제 callee에서 어떤일이 일어나는지 알아보겠습니다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1. Sets up the local segment of the called function&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출된 함수에 대한 지역 변수 세그면트(LCL)를 생성하고 값을 초기화 합니다.&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;1372&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QEBb2/btsMz6r9VUm/nQ5Ig1thiEkDSJH5t17Auk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QEBb2/btsMz6r9VUm/nQ5Ig1thiEkDSJH5t17Auk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QEBb2/btsMz6r9VUm/nQ5Ig1thiEkDSJH5t17Auk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQEBb2%2FbtsMz6r9VUm%2FnQ5Ig1thiEkDSJH5t17Auk%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;663&quot; height=&quot;363&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. Retuen&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에 정의된 작업이 끝나면 응답을 반환해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;callee는 리턴값을 argument[0]에 저장합니다. (이렇게 하면 callee의 리턴값이 caller의 스택 가장 위에 들어가게 됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;sp를 arg + 1로 조정해서,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;caller가 함수를 호출했던 반환 주소로 돌아갑니다.&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;그후 caller의 LCL, ARG, THIS, THAT을 복구합니다. (이 과정에서 callee의 LCL과 ARG는 사라짐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 return address로 jump합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kt1Op/btsMysbWnT1/GeO7BNzLKuIuGLy4Bjp21K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kt1Op/btsMysbWnT1/GeO7BNzLKuIuGLy4Bjp21K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kt1Op/btsMysbWnT1/GeO7BNzLKuIuGLy4Bjp21K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKt1Op%2FbtsMysbWnT1%2FGeO7BNzLKuIuGLy4Bjp21K%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;664&quot; height=&quot;436&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Global Stack (전역 스택)&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;위에서 하나의 caller와 callee를 살펴보았는데, 프로그램은 무수히 많은 caller와 callee쌍이 존재합니다.&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;이 모든 정보들 또한 stack에 존재하는데 이 스택을 Global Stack이라고 부릅니다.&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;Global Stack에서 현재 실행 중인 caller와 callee들을 block이라고 합니다.&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;callee가 종료되고 caller가 실행 되면서, 기존에 frame들과, callee에서 사용했던 값들은 이후 caller가 실행되며 값들이 덮어씌우면서 재사용 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;1108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vZUNU/btsMAi64bPA/BtmvIIx1t2fyPKqkylcdB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vZUNU/btsMAi64bPA/BtmvIIx1t2fyPKqkylcdB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vZUNU/btsMAi64bPA/BtmvIIx1t2fyPKqkylcdB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvZUNU%2FbtsMAi64bPA%2FBtmvIIx1t2fyPKqkylcdB1%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;661&quot; height=&quot;598&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;1108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수준 코드를 컴파일하게 되면 vm코드가 생성됩니다.&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;900&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lhk4r/btsMzgVKFoR/HFQJ95gieCpYg1FEkX80pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lhk4r/btsMzgVKFoR/HFQJ95gieCpYg1FEkX80pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lhk4r/btsMzgVKFoR/HFQJ95gieCpYg1FEkX80pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLhk4r%2FbtsMzgVKFoR%2FHFQJ95gieCpYg1FEkX80pk%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;559&quot; height=&quot;452&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL6CJR/btsMx1L65cR/FojPltDkbBkiyWKQ5fbNok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL6CJR/btsMx1L65cR/FojPltDkbBkiyWKQ5fbNok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL6CJR/btsMx1L65cR/FojPltDkbBkiyWKQ5fbNok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL6CJR%2FbtsMx1L65cR%2FFojPltDkbBkiyWKQ5fbNok%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;677&quot; height=&quot;299&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vm코드를 순서대로 따라가 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push 3을 해서 global stack에 3이 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 call factorical 명령이 실행되어 argument[0]이 3을 바라보게 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main함수의 frame들을 저장합니다.&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;이제 f(3)의 명령어들이 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push argument 0 (n) , push constant 1을 비교(eq)합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같지 않기에 BASECASE로 이동하지 않습니다.&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;push argument 0 (n), push argument 0 (n), push constant 1, sub 가 실행되면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack에 3,2가 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 f(2)가 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 callee가 생성됨에 따라, global stack에는 f(3)의 frame이 추가됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f(2)의 argument 세그먼트도 새롭게 생성됩니다.&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;f(2)는 f(3)과 argument만 다른 상태로 동일한 적업을 수행하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택에 f(2)의 프레임, f(1)의 argument를 생성하며 f(1)이 시작됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 f(1)에서는 eq명령에 따라 BASECASE로 jump되고 push constatn 1 명령이 실행되고 f(1)함수가 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;argument[0]에 f(1)의 리턴값(1)이 복사됩니다. f(2)와 f(3)도 동일하게 반환되면서 main함수는 최종 리턴값을 얻게됩니다.&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;vm 머신 정리&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YwkFb/btsMAHm7ry9/MSK5vVS0rwrnUEc0uooWs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YwkFb/btsMAHm7ry9/MSK5vVS0rwrnUEc0uooWs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YwkFb/btsMAHm7ry9/MSK5vVS0rwrnUEc0uooWs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYwkFb%2FbtsMAHm7ry9%2FMSK5vVS0rwrnUEc0uooWs0%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;686&quot; height=&quot;300&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리의 vm모델은 스택기반입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push/pop 명령을 통해 스택과 메모리 세그만트 사이에 데이터를 전송하고, 분기와 함수를 이용하여 산술 및 논리 연산을 수행합니다.&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;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8GL51/btsMAJynGZi/q39kJ4zZODt5tOXq3CnKm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8GL51/btsMAJynGZi/q39kJ4zZODt5tOXq3CnKm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8GL51/btsMAJynGZi/q39kJ4zZODt5tOXq3CnKm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8GL51%2FbtsMAJynGZi%2Fq39kJ4zZODt5tOXq3CnKm1%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;674&quot; height=&quot;353&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vm번역기는 컴파일러가 생성한 vm언어들을 어셈블리어로 변경하는 프로그램입니다.&amp;nbsp;(FileName.vm -&amp;gt; FileName.asm)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAM에 지정된 메모리를 이용해서 vm의 스택을 표현하며, RAM 블록의 밑바닥은 고정된 기본 주소가 되고, 최상단은 스택에 따라 변화하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 고정 주소(stackbase)가 주어지면, 스택의 최상단 값 바로 뒤의 RAM 주소를 추적하는 스택포인터 (sp)를 통해 스택을 관리합니다. (push x 명령은 RAM[SP] = x, SP++, pop x명령은 SP-, x = RAM[SP] 와 같은 의사코드로 구현 가능)&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;vm프로그램은 잭과 같은 고수준 프로그램 언어로 생성되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잭 컴파일러를 적용하면 Test.jack 클래스의 파일들은 -&amp;gt; Test.vm으로 번역됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 이후 Main.jack 파일 내의 bar라는 이름의 생성자, 함수, 메서드들은 vm함수로 번역되며 vm함수는 전역적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 함수의 이름은 Test.FuncName 처럼 고유한 이름이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램의 진입점은 반드시 Main.jack이어야 하고, 이 파일에서 vm함수 하나는 이름이 Main.main이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vm프로그램이 실행을 시작할 때, 항상 실행되는 첫 번째 함수는 인수가 없는 Sys.init이라는 함수로 os의 일부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 os함수는 사용자 프로그램에서 진입점이 되는 함수를 호출하도록 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sys.init은 잭 프로그램에서는 Main.main을 호출합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RAM활용&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlCkwM/btsMC3ocWC5/Qwms0Xdj3sMHFrwynPdKYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlCkwM/btsMC3ocWC5/Qwms0Xdj3sMHFrwynPdKYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlCkwM/btsMC3ocWC5/Qwms0Xdj3sMHFrwynPdKYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlCkwM%2FbtsMC3ocWC5%2FQwms0Xdj3sMHFrwynPdKYk%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;630&quot; height=&quot;192&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vm에서 RAM을 활용하는 방식은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세그먼트의 기본 주소들은 각각 LCL, ARG, THIS, THAT레지스터에 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세그먼트에 접근하려면 push segument Name i라는 명령이 필요한데 이는 (base+i)RAM주소로 접근하라는 어셈블리 코드로 변경되어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uT5gh/btsMCmIHNFH/q9SdHzfGFVF0uGq7mJyZ40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uT5gh/btsMCmIHNFH/q9SdHzfGFVF0uGq7mJyZ40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uT5gh/btsMCmIHNFH/q9SdHzfGFVF0uGq7mJyZ40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuT5gh%2FbtsMCmIHNFH%2Fq9SdHzfGFVF0uGq7mJyZ40%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;666&quot; height=&quot;390&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;부트스트랩 코드&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵 플랫폼에서는 RAM이 256부터 매핑되고, 실행을 시작하는 첫 vm함수가 OS함수인 Sys.init이어야 한다고 규정합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ROM[0]주소에 SP=256이 실행되고 이후 call Sys.init 명령어가 실행됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;운영체제의 일부인 Sys.init함수는 응용프로그램의 main함수를 호출하고 무한루프에 들어가게됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이 작업으로 번역된 vm프로그램이 실행됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로그램 구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서는 vm번역기는 3가지 모듈 VMTranslator, Parser, CodeWriter로 구현하기를 권장한다고 말합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VmUpu/btsMCYN5Nrg/xQGNWoTBaqozLKmcYN6er1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VmUpu/btsMCYN5Nrg/xQGNWoTBaqozLKmcYN6er1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VmUpu/btsMCYN5Nrg/xQGNWoTBaqozLKmcYN6er1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVmUpu%2FbtsMCYN5Nrg%2FxQGNWoTBaqozLKmcYN6er1%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;789&quot; height=&quot;188&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;VMTranslator&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Parser와 CodeWriter를 이용해서 번역 과정을 수행하는 메인 프로그램입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 소스 파일명에 .vm파일을 받아 해당 파일을 파싱하기 위한 Parser모듈을 생성하고 번역된 어셈블리 명령어들을 출력할 기록파일 .asm을 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 파일 내 vm명령어들을 반복하는 루프를 실행해 명령을 번역합니다.&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;Parser&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vm 파일의 파싱(구문 분석) 처리, 이 모듈은 vm 명령을 읽고 명령을 다양한 구성 성분으로 분석하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 성분들에 편리하게 접근 하도록 도와줍니다. (분기 및 함수명령을 포함한 모든 vm명령을 처리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;간단하게 말하면 vm코드 파일을 읽고, 명령을 해석합니다&amp;nbsp;&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;예를들어 아래와 같은 명령어가 있다면 'push constant 10' 같은 vm코드가 들어오면 명령유형 (push), 첫번째 인자 (constant), 두번쨰 인자 (10)을 CodeWriter에 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgo5q2/btsMBlqlWzC/76lktcWkkLpkk8oSZ68Xo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgo5q2/btsMBlqlWzC/76lktcWkkLpkk8oSZ68Xo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgo5q2/btsMBlqlWzC/76lktcWkkLpkk8oSZ68Xo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgo5q2%2FbtsMBlqlWzC%2F76lktcWkkLpkk8oSZ68Xo0%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;689&quot; height=&quot;490&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CodeWriter&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파싱된 vm명령을 핵 어셈블리 코드로 변환합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 writePushPop (C_PUSH, 'local',2)를 호출하면 push local 2라는 vm명령을 구현하는 어셈블리 명령어들이 생성되어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J3tR2/btsMBVShB6U/QLX6HAQfLQX2tE31rDjSC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J3tR2/btsMBVShB6U/QLX6HAQfLQX2tE31rDjSC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J3tR2/btsMBVShB6U/QLX6HAQfLQX2tE31rDjSC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ3tR2%2FbtsMBVShB6U%2FQLX6HAQfLQX2tE31rDjSC1%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;622&quot; height=&quot;465&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;함수 호출과 반환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 호출하고, 함수를 반환받는 이벤트에서는 호출자(caller)의 관점과 피호출자(callee)의 관점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 관점 보두 call, function, return 명령어 처리에 기대되는 동작과 책임이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;call은 호출자의 스택을 프레임에 저장하고, 피호출자 실행으로 점프하는 코드를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function은 피호출자의 지역변수를 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return은 반환값을 호출자의 스택 최상단에 복사하고, 호출자의 세그먼트 포인터를 복원 후, 반환 주소 다음부터 실행하도록 점프하는 코드를 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGxaI0/btsMCaoc4hG/wXkbkkKwyncmjk48icjQK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGxaI0/btsMCaoc4hG/wXkbkkKwyncmjk48icjQK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGxaI0/btsMCaoc4hG/wXkbkkKwyncmjk48icjQK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGxaI0%2FbtsMCaoc4hG%2FwXkbkkKwyncmjk48icjQK0%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;556&quot; height=&quot;474&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1090&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;h4 data-ke-size=&quot;size20&quot;&gt;vm번역기가 사용하는 특수기호 목록&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;1500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lXpd9/btsMBRP7www/L0z2aYS5k5yH7U5qnLqey1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lXpd9/btsMBRP7www/L0z2aYS5k5yH7U5qnLqey1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lXpd9/btsMBRP7www/L0z2aYS5k5yH7U5qnLqey1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlXpd9%2FbtsMBRP7www%2FL0z2aYS5k5yH7U5qnLqey1%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;608&quot; height=&quot;491&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;1500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SP와 LCL, ARG, THIS, THAT은 익숙하기에 생락하구..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R13-15s : 임시 저장용 레지스터. 보통 리턴 주소나 임시 데이터 저장 등에 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xxx.i : Xxx.vm 파일의 i번째 정적 변수는 어셈블리어에서 Xxx.i로 변환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;functionName : 함수 시작 지점을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;functionName$ret.i : 함수에서 i번째 리턴 후 시작될 위치를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;functionName$label : 함수 내부의 특정 위치를 나타냅니다. (조건문에서 분기할 때 사용)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;vm코드 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1741226295148&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Main.main 0  

  push constant 3    
  push constant 4    
  call Multiply 2    // 프레임 및 리턴주소(Main.main$ret.1) 생성

  call CheckEven 1   // 프레임 및 리턴주소(Main.main$ret.2) 생성

  goto Main$End      // 프로그램 종료

function Multiply 0  
  push argument 0    
  push argument 1    
  mul                
  return             // (Main.main$ret.1)로 이동

// CheckEven 함수 (짝수 판별)
function CheckEven 0  
  push argument 0    
  push constant 2    
  mod                
  if-goto Main$Failure  // 나머지가 0이 아니면 실패 (홀수)
  goto Main$Success     // 나머지가 0이면 성공 (짝수)

// 성공 label
(Main$Success)   
  label Main$Success
  // 여기서 성공 처리
  goto Main$End

// 실패 label
(Main$Failure)   
  label Main$Failure
  // 여기서 실패 처리
  goto Main$End

// 프로그램 종료 label
(Main$End)  
  label Main$End&lt;/code&gt;&lt;/pre&gt;</description>
      <category>NandToTetris</category>
      <category>nandtotetris</category>
      <category>vm머신</category>
      <category>vm번역기</category>
      <category>가상머신</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/93</guid>
      <comments>https://jaykos96.tistory.com/93#entry93comment</comments>
      <pubDate>Wed, 26 Feb 2025 23:41:50 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris : 가상머신1-프로세싱-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/92</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;가상 머신 패러다임&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AQQ5t/btsMoeLFDgs/wCEhKhz64EZjuztlAwvo90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AQQ5t/btsMoeLFDgs/wCEhKhz64EZjuztlAwvo90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AQQ5t/btsMoeLFDgs/wCEhKhz64EZjuztlAwvo90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAQQ5t%2FbtsMoeLFDgs%2FwCEhKhz64EZjuztlAwvo90%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;794&quot; height=&quot;254&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램들은 컴퓨터에서 실행되려면 기계어로 번역되어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 고수준 언어들이 생겨남에따라, 동일하게 컴파일러들도 생겨났고 이러한 컴파일러들은 원본언어에 종속적이게 되었습니다.&lt;/p&gt;
&lt;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;b&gt;첫 단계는 고수준 언어의 구문을 분석(parse)하여 그 명령들을 중간처리단계로 번역합니다.&amp;nbsp; (컴파일러)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째 단계에서는 이 중간 단계를 다시 대상 하드웨어의 기계어로 번역합니다. (VM번역기)&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;두 단계(컴파일러, vm번역기)를 이어주는 인터페이스를 &lt;b&gt;가상 머신 (virtual machine)&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;고수준 언어가 기계어로 번역되는&amp;nbsp; 과정&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;컴파일러가 고수준 코드를 중간 VM 명령으로 번역&lt;/li&gt;
&lt;li&gt;vm 변역기가 중간 vm 명령을 기계 명령어로 번역&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java로 예시를 들면 컴파일러인 javac가 .class코드를 생성합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가상머신 jvm은 .class코드를 읽어 직접 기계어로 변환합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(jvm은 어셈블리없이 바로 바이트코드 -&amp;gt; jvm에서 어셈블리어 변환 후 다시 기계어 변환)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;vm방식으로 생성된 기계코드는 c에서 사용하는 고전적인 1단계 컴파일러에 비해 장황하고 복잡한 코드가 되어 다소 효율성이 저하됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 프로세서가 최적화 되고 vm구현이 최적화 되어 크게 눈에 띄지는 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물론 고성능이 필요한 응용프로그램이나 임베디드 시스템에서는 항상 보다 효율적인 코드가 필요합니다.&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;72-스택-머신&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;vm 추상화와 스택 머신&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;vm 명령은 고수준언어와 저수준언어의 인터페이스이기에 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;컴파일러가 적절히 구조화된 vm 코드를 생성할 수 있도록&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;충분히 '고수준'이면서 동시에 vm번역기가 효율적인 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;기계어 코드를 생성할 수 있도록 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;충분히 '저수준'이기도 해야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;고수&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;준, 저수준 언어를 인터페이스 하기 위해 가상환경을 제공해야하는데 이를 vm추상화라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;vm추상화는 메모리 세그먼트와 스택 두가지 방법이 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;vm추상화를 달성하기 위한 방법 중 하나로 스택을 사용하게 되면, &lt;/span&gt;&lt;b&gt;스택 머신&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;이라는 추상적인 아키텍처를 중간 vm 언어의 기반으로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&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;/li&gt;
&lt;li&gt;스택 : 최상단만 접근 가능, 최상단 값을 읽으려면 해당 값을 스택에서 삭제해야함, 최상단에만 값을 추가하며 나머지 값들은 변화 X&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 스택에 최상단 값 바로 아래 위치는 sp (stack pointer)라는 이름으로 참조합니다.&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스택머신이 어떻게 메모리의 데이터와 관계되어 있는지 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bahbS5/btsMuxYnMu9/WgHUtcqRv5STiUkhG9C6a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bahbS5/btsMuxYnMu9/WgHUtcqRv5STiUkhG9C6a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bahbS5/btsMuxYnMu9/WgHUtcqRv5STiUkhG9C6a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbahbS5%2FbtsMuxYnMu9%2FWgHUtcqRv5STiUkhG9C6a0%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;208&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/di4YtN/btsMwcMgw9M/BGcoxGUXsR2d2CUqckYkq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/di4YtN/btsMwcMgw9M/BGcoxGUXsR2d2CUqckYkq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/di4YtN/btsMwcMgw9M/BGcoxGUXsR2d2CUqckYkq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdi4YtN%2FbtsMwcMgw9M%2FBGcoxGUXsR2d2CUqckYkq0%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;786&quot; height=&quot;288&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;704&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;위에 그림과 같이 constant 17, 즉 상수 17을 스택에 넣는다면 스택에서는 값 17이 들어가고 sp는 스택의 다음 자리를 가리키게 됩니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sp (stack pointer)는 RAM[0]를 의미합니다.&lt;/li&gt;
&lt;li&gt;스택에 들어가는 데이터는 RAM[256]부터 시작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 위에 그럼의 스택에는 이미 12, 5 두 데이터가 들어가 있기에 현재 스택포인터는 256 + 2 즉 258을 가리키고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 새로운 값 17이 추가되면 sp가 가리키는 위치 258에 17이 추가되고 sp의 값, RAM[0]의 값은 259로 업데이트 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스택 산술&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택 머신에서 x op y와 같은 연산들은 아래와 같이 수행됩니다. (op는 +, - 등등)&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. 피연산자 x,y를 스택 최상단에서 꺼냅니다. (pop)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. x op y를 계산합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 계산된 값이 스택 최상단에 들어갑니다. (push)&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 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;고수준 산술 및 논리 표현식을 순차적인 스택 명령들로 번역하는 컴파일러를 만들 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덧셈(add)과 부호반전(neg)을 처리하는 스택산술 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJwugQ/btsMtdyYZGc/Gw9PCAmFgA0BnvsY5KmLL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJwugQ/btsMtdyYZGc/Gw9PCAmFgA0BnvsY5KmLL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJwugQ/btsMtdyYZGc/Gw9PCAmFgA0BnvsY5KmLL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJwugQ%2FbtsMtdyYZGc%2FGw9PCAmFgA0BnvsY5KmLL1%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;671&quot; height=&quot;192&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;d = (2-x) + (y+9)의 스택 산술 과정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4ZAmv/btsMr942qD4/KkEk2IsoN8mNut4N1SIsl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4ZAmv/btsMr942qD4/KkEk2IsoN8mNut4N1SIsl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4ZAmv/btsMr942qD4/KkEk2IsoN8mNut4N1SIsl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4ZAmv%2FbtsMr942qD4%2FKkEk2IsoN8mNut4N1SIsl1%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;740&quot; height=&quot;402&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( x &amp;lt; 7 ) or (y == 8) 을 처리하는 논리 표현식 과정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ACcYW/btsMuIYIOu2/CXMchrHvlPToJwBQHoD1IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ACcYW/btsMuIYIOu2/CXMchrHvlPToJwBQHoD1IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ACcYW/btsMuIYIOu2/CXMchrHvlPToJwBQHoD1IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FACcYW%2FbtsMuIYIOu2%2FCXMchrHvlPToJwBQHoD1IK%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;791&quot; height=&quot;391&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가상 메모리 세그먼트&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1934&quot; data-origin-height=&quot;1314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SzIav/btsMtGBuiep/4ZtgrKg4rGXkst14c8aGB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SzIav/btsMtGBuiep/4ZtgrKg4rGXkst14c8aGB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SzIav/btsMtGBuiep/4ZtgrKg4rGXkst14c8aGB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSzIav%2FbtsMtGBuiep%2F4ZtgrKg4rGXkst14c8aGB0%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;800&quot; height=&quot;544&quot; data-origin-width=&quot;1934&quot; data-origin-height=&quot;1314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수준 언어는 x,y,sum 같은 기호 변수를 지원합니다.&lt;br /&gt;이러한 기호 변수들은 클래스 레벨에선 정적 변수, 인스턴스 레벨에서는 객체 필드 변수, 메서드 레벨에서는 지역 변수 또는 인수가 될 것 입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 가상 머신에서는 기호 변수가 존재하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;대신 변수들은 static, this, local, argument 같은 이름의 가상 메모리 세그먼트 내 항목들로 표현된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 컴파일러는 고수준 프로그램에서 나오는 첫번쨰 두번째 세번째 정적 변수들을&lt;br /&gt;static 0, static 1, static 2 같은 식으로 매핑합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 다른 종류의 변수들도 비슷하게 this, local, argument 세그먼트로 매핑됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 지역 변수 x와 필드 y가 각각 local 1과 this 3에 매핑된 경우, 컴파일러는 let x = y 같은 고수준 명령문을 push this 3, pop local 1로 번역합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가로 핵 vm 모델은 아래와 같은 8개의 메모리 세그먼트를 지원합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;vm 명령은 세그먼트 이름 다음에 인덱스(음수 X)를 사용하는 방법을 사용해 메모리 세그먼트에 접근합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;포인터 조작&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doHcfU/btsMv50G0PF/gVwdBuOkU6QMgoVsy7zLCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doHcfU/btsMv50G0PF/gVwdBuOkU6QMgoVsy7zLCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doHcfU/btsMv50G0PF/gVwdBuOkU6QMgoVsy7zLCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoHcfU%2FbtsMv50G0PF%2FgVwdBuOkU6QMgoVsy7zLCK%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;647&quot; height=&quot;373&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;핵 컴퓨터에서 메모리 세그먼트를 사용할때는 포인터를 이용한 조작이 많습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;D = *p라는 vm명령어는 핵 어셈블리어에서 아래와 같이 표현됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;@p&amp;nbsp; &amp;nbsp; // p는 0와 동일합니다. 즉 A레지스터에 0값을 설정합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;A=M&amp;nbsp; // RAM[0]의 값 257이 다시 A레지스터에 설정됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;D=M&amp;nbsp; // RAM[257]의 데이터 23을 D에 지정합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;핵 vm모델의 메모리 세그먼트 종류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 8개의 메모리 세그먼트가 존재하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 세그먼트들의 정의와 주소는 아래 표와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;argument : 함수의 인수를 나타냅니다.&lt;/li&gt;
&lt;li&gt;local : 함수의 지역 변수를 나타&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;냅니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;static : 함수에서 볼 수 있는 정적 변수를 나타&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;냅니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;constant : 상수 변수 0,1,...,32767을 나타&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;냅니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;this, that, pointer, temp : 우리가 익숙한 그것들,,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/osRyF/btsMuRbdX2p/QQhXoF7AMFvatzB2rNRTt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/osRyF/btsMuRbdX2p/QQhXoF7AMFvatzB2rNRTt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/osRyF/btsMuRbdX2p/QQhXoF7AMFvatzB2rNRTt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FosRyF%2FbtsMuRbdX2p%2FQQhXoF7AMFvatzB2rNRTt0%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;736&quot; height=&quot;365&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&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;핵 컴퓨터에서 RAM은 0~15는 레지스터, 16~255는 정적 변수, 256~2047은 스택 영역으로 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;이름&lt;/td&gt;
&lt;td&gt;위치&lt;/td&gt;
&lt;td&gt;내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SP&lt;/td&gt;
&lt;td&gt;RAM[0]&lt;/td&gt;
&lt;td&gt;스택 포인터 주소, 최초 256으로 초기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCL&lt;/td&gt;
&lt;td&gt;RAM[1]&lt;/td&gt;
&lt;td&gt;local 메모리 세그먼트 기본주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARG&lt;/td&gt;
&lt;td&gt;RAM[2]&lt;/td&gt;
&lt;td&gt;argment 메모리 세그먼트 기본주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;THIS&lt;/td&gt;
&lt;td&gt;RAM[3]&lt;/td&gt;
&lt;td&gt;this segment의 기본주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;THAT&lt;/td&gt;
&lt;td&gt;RAM[4]&lt;/td&gt;
&lt;td&gt;that segment의 기본주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TEMP&lt;/td&gt;
&lt;td&gt;RAM[5-12]&lt;/td&gt;
&lt;td&gt;temp segment를 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R13, R14, R15&lt;/td&gt;
&lt;td&gt;RAM[13-15]&lt;/td&gt;
&lt;td&gt;VM 번역기가 생성한 어셈블리 코드에서 별도의 변수가 필요할 때 사용하는 레지스터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;h3 data-ke-size=&quot;size23&quot;&gt;각 메모리 세그먼트간 데이터 입출력 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack머신은 아래와 같이 각 세그먼트들과 데이터를 전달받고 전달합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbmMhr/btsMuJxvmf0/riBUol4Hsvh9oLndfTkxOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbmMhr/btsMuJxvmf0/riBUol4Hsvh9oLndfTkxOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbmMhr/btsMuJxvmf0/riBUol4Hsvh9oLndfTkxOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbmMhr%2FbtsMuJxvmf0%2FriBUol4Hsvh9oLndfTkxOK%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;654&quot; height=&quot;194&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 위와 같은 상황에서 &lt;b&gt;let static 2 = argument 1&lt;/b&gt;과 같은 vm 명령을 작업을 해야한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택에 &lt;b&gt;push argument 1, pop static 2&lt;/b&gt;와 같은 명령이 필요합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Local 세그먼트&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y1MXb/btsMxCRWoKG/8FJxH5IStpI6DUyzTg4A50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y1MXb/btsMxCRWoKG/8FJxH5IStpI6DUyzTg4A50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y1MXb/btsMxCRWoKG/8FJxH5IStpI6DUyzTg4A50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY1MXb%2FbtsMxCRWoKG%2F8FJxH5IStpI6DUyzTg4A50%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;253&quot; height=&quot;293&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 세그먼트의 기본 주소(LCL)는 1015입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LCL은 SP와 다르게 스택의 값이 변경되어도 값이 변하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉 LCL의 계속해서 최초주소 1015를 가지고 있습니다.&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;pop/push local i 가 실행되면 addr=LCL + i를 하여 로컬 세그먼트 기본주소(1015)에 해당 로컬 변수의 번호를 더한 주소에 값을 넣거나 가져옵니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 *SP = *&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;LCL+i&lt;/span&gt;, SP++/-- 연산을 진행합니다.&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;즉 RAM[LCL+i]의 값을 스택 포인터가 가리키고 있는 위치에다가 대입하고, SP를 ++/--합니다.&lt;/b&gt;&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;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;추가로 Argument, This, That 세그먼트들도 Local 세그먼트와 동일하게 동작합니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF3QSa/btsMvNmKG6t/W2StBNpD0SQLv8IjLepDw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF3QSa/btsMvNmKG6t/W2StBNpD0SQLv8IjLepDw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF3QSa/btsMvNmKG6t/W2StBNpD0SQLv8IjLepDw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF3QSa%2FbtsMvNmKG6t%2FW2StBNpD0SQLv8IjLepDw0%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;1926&quot; height=&quot;416&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위와 같이 pop local 2 명령이 실행 되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 관점에서는 스택의 최상단 값(*SP-1)을 꺼내(pop) RAM[LCL+2]에 저장시키고 SP를 1 감소시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddoAUL/btsMxn8zoUI/1bZ6urnQBy4jsivTtHCs0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddoAUL/btsMxn8zoUI/1bZ6urnQBy4jsivTtHCs0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddoAUL/btsMxn8zoUI/1bZ6urnQBy4jsivTtHCs0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddoAUL%2FbtsMxn8zoUI%2F1bZ6urnQBy4jsivTtHCs0k%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;710&quot; height=&quot;336&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;890&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;local pop 2 핵 어셈블리어로는 아래와 같이 동작 할 것 같습니다.. (R[13]~R[15]를 이렇게 쓰는지 확신이 없습니다 ㅠㅠ)&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740487730769&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@LCL    // A = LCL
D=M     // D = RAM[1015]
@1      // A = 1
D=D+A   // D = 1015 + 2 = 1017

@R13    // 임시 저장소 사용 (RAM[13]에 local 2 주소 저장)
M=D     // RAM[13] = 1017

@SP     // A = SP
M=M-1   // SP--
A=M     // A = SP
D=M     // D = RAM[SP]

@R13    // A = R13 (저장된 local 2 주소 1017)
A=M     // A = 1017
M=D     // RAM[1017] = 스택에서 꺼낸 값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;local push 1 는 아래와 같이 동작 할 것 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1740488290154&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@LCL    // A = LCL
D=M     // D = RAM[1015]
@1      // A = 1
A=D+A   // A = 1015 + 1 = 1016
D=M     // D = RAM[1016]

@SP     // A = SP
A=M     // A = RAM[0]
M=D     // RAM[256] = RAM[1016] (local 1 값 복사)

@SP     // A = SP
M=M+1   // SP++&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Constant 세그먼트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Constant 세그먼트에서는 pop명령이 따로 존재하지 않습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PaYDP/btsMx2ivurh/eZIbheQhnTMGsTQIMKYkw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PaYDP/btsMx2ivurh/eZIbheQhnTMGsTQIMKYkw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PaYDP/btsMx2ivurh/eZIbheQhnTMGsTQIMKYkw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPaYDP%2FbtsMx2ivurh%2FeZIbheQhnTMGsTQIMKYkw0%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;706&quot; height=&quot;179&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt; Static 세그먼트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foo.vm 파일에 저장된 vm프로그램 내에 static i변수가 있다면, 어셈블리 기호 Foo.i로 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 static 변수들은 RAM[16] ~ RAM[255] 사이에 vm명령이 들어오는 순서대로 매핑됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;1238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnjjjs/btsMuSWCiXA/JnHMOiwB1RYekFYoCwIEl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnjjjs/btsMuSWCiXA/JnHMOiwB1RYekFYoCwIEl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnjjjs/btsMuSWCiXA/JnHMOiwB1RYekFYoCwIEl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnjjjs%2FbtsMuSWCiXA%2FJnHMOiwB1RYekFYoCwIEl1%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;568&quot; height=&quot;490&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;1238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt; Temp 세그먼트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Temp 세그먼트는 RAM[5] ~ RAM[12] 사이에 매핑됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;temp i에 대한 접근은 RAM[5+i]와 같은 방식으로 진행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/olwMQ/btsMvAuqXrV/uCkR6Kgp49LvVPQ05Kf4MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/olwMQ/btsMvAuqXrV/uCkR6Kgp49LvVPQ05Kf4MK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/olwMQ/btsMvAuqXrV/uCkR6Kgp49LvVPQ05Kf4MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FolwMQ%2FbtsMvAuqXrV%2FuCkR6Kgp49LvVPQ05Kf4MK%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;672&quot; height=&quot;294&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pointer 세그먼트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;this와 that의 주소를 저장하는 특수한 세그먼트입니다.&lt;br /&gt;RAM[3]에 this의 주소, RAM[4]에 that의 주소가 저장됩니다.&lt;br /&gt;this와 that이 가리키는 메모리 위치를 바꿀 때 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, pointer 0 = this, pointer 1 = that이 됩니다.&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;this : 현재 객체(혹은 특정 메모리 영역)를 가리킬 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;that : 다른 메모리 공간(배열 등)을 가리킬 때 사용&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;976&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nsYri/btsMx2pgmMU/UNW9ItMdQyKM7O9DNpJLA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nsYri/btsMx2pgmMU/UNW9ItMdQyKM7O9DNpJLA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nsYri/btsMx2pgmMU/UNW9ItMdQyKM7O9DNpJLA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnsYri%2FbtsMx2pgmMU%2FUNW9ItMdQyKM7O9DNpJLA1%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;657&quot; height=&quot;376&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;976&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 push pointer 0 (THIS의 주소를 스택에 저장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 명령은 아래와 같이 처리 될 수 있고&lt;/p&gt;
&lt;pre id=&quot;code_1740489871070&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// push pointer 0
@3     // A = pointer 0 (THIS)
D=M    // D = RAM[3] (THIS의 주소)

@SP    // A = 스택 포인터
A=M    // A = RAM[SP] (현재 스택의 위치)
M=D    // RAM[SP] = THIS의 주소 (스택에 저장)

@SP    // A = 스택 포인터
M=M+1  // SP++&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push pointer 1 (스택에서 값을 꺼내 THAT의 주소로 저장) 은 아래와 같이 처리됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1740489905601&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SP    // A = 스택 포인터
M=M-1  // SP--
A=M    // A = RAM[SP] (스택의 최상단)
D=M    // D = RAM[SP] (스택에서 꺼낸 값)

@4     // A = pointer 1 (THAT)
M=D    // RAM[4] = 스택에서 꺼낸 값 (THAT이 가리키는 주소 변경)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>NandToTetris</category>
      <category>nandtotetris</category>
      <category>vm머신</category>
      <category>메모리 세그먼트</category>
      <category>스택머신</category>
      <category>프로세싱</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/92</guid>
      <comments>https://jaykos96.tistory.com/92#entry92comment</comments>
      <pubDate>Wed, 19 Feb 2025 23:46:06 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris : 어셈블러-밑바닥부터 만드는 컴퓨팅 시스템, 어셈블러 구현 X</title>
      <link>https://jaykos96.tistory.com/91</link>
      <description>&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;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: #0f1114; text-align: start;&quot;&gt;&lt;b&gt;모든 컴퓨터에는 명령어가 0과 1의 연속으로 작성되는 이진 기계어와 인간이 이해하기 쉬운 표현을 사용하여 명령어를 표현하는 어셈블리 언어가 있습니다.&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: #0f1114; 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: #0f1114; 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: #0f1114; 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: #0f1114; text-align: start;&quot;&gt;이 번역 서비스를 어셈블러가 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0f1114; text-align: start;&quot;&gt;어셈블리어는 2진 코드와 직접 대응되므로, 어셈블러를 구현하는 일은 그렇게 어렵지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0f1114; 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: #0f1114; text-align: start;&quot;&gt;이 기능은 기호 테이블(symbol table)이라는 일반적인 데이터 구조로 구현됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기계어는 보통 두 가지 형식, 즉 기호와 2진 형식으로 정의됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 2진 명령어가 앞에 8개 비트는 load, 뒤에 8가 bit는 레지스터 R3을 나머지는 7을 나타낸다고 할때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 의미들을 load, R3, 7과 같이 작성하면 자연스러울 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 load같은 연산코드는 &lt;b&gt;연상 기호&lt;/b&gt;라 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 기호언어는 어셈블리, 어셈블리를 기계어로 변역하는 도구는 어셈블러라고 합니다.&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기호&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 레이블 : 어셈블리 프로그램은 코드 내 위치를 표시하기 위해 기호를 정의하고 사용합니다. (ex : LOPP, END)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 변수 : 어셈블리 프로그램은 기호 변수를 정의하고 사용할 수 있습니다. (ex : i, sum)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 선언 기호 : 어셈블리 프로그램은 미리 선언된 기호를 사용해서 컴퓨터 메모리 내의 특정 주소를 참조할 수 있습니다. (ex : SCREEN, KBD)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 기호들은 결국 각각 어떤 다른 주소를 의미하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 기호들을 처리하는 작업들이 어셈들러에서 가장 중요한 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석 및 레이블 선언은 아무런 코드도 생성하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 때문에 레이블 선언이 의사 명령어 (pseudo-instruction)이라고도 합니다.&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어셈블리 명령어 : 기호 A명령어 또는 기호 C명령어&lt;/li&gt;
&lt;li&gt;레이블 선언 : (xxx) 형식의 라인, xxx는 기호&lt;/li&gt;
&lt;li&gt;주석 : 두 개의 빗금(//)으로 시작되는 라인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기호&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언기호, 레이블 기호, 변수 기호로 나뉩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;선언 기호&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기호들의 값은 핵 RAM의 주소로 해석됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;R0, R1, ... , R15는 각각 0, 1, ... , 15 를 가리킵니다.&lt;/li&gt;
&lt;li&gt;Sp, LCL, ARG, THIS, THAT 은 각각 0,1,2,3,4 를 나타냅니다.&lt;/li&gt;
&lt;li&gt;SCREEN, KBD는 각각 16384와 24576를 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;레이블 기호&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의사명령어(xxx)는 프로그램의 다음 명령어를 저장하고 있는 ROM의 주소를 기호 xxx가 참조하도록 정의합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;변수 기호&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기호 xxx가 선언기호가 아니거나, 레이블 선언으로 정의되지 않은 경우에는 변수로 취급됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수는 등장한 순서대로 RAM주소 16에서 시작하는 RAM위치에 차례대로 매핑됩니다.&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;어셈블리-2진 코드 번역&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어셈블러는 어셈블리 명령어 마다 다음을 수행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명령어의 필드들을 파싱한다.&lt;/li&gt;
&lt;li&gt;각 필드마다 그에 대응하는 비트코드를 생성한다.&lt;/li&gt;
&lt;li&gt;명령어에 참조 기호가 있으면, 그 기호를 숫자 값으로 해석한다.&lt;/li&gt;
&lt;li&gt;생성된 비트 코드들을 조립하며 16개의 0 또는 1로 이루어진 문자열로 만든다.&lt;/li&gt;
&lt;li&gt;조립된 문자열을 출력 파일에 기록한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기호 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어셈블리 프로그램에서는 기호 레이블을, 그 기호가 정의된 라인 앞부분에서도 사용 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 코드를 처음부터 끝까지 두번 읽는 2패스 어셈블러로 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어셈블러가 1차로 코드를 읽을 때는 기호 테이블을 만들고, 레이블 기호들을 모두 테이블에 기록하며 아무런 코드도 생성하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차로 코드를 읽을 때 앞에서 만든 기호 테이블을 활용하여 변수 기호들을 처리하고 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;초기화&lt;/b&gt;&lt;br /&gt;어셈블러는 기호 테이블을 만들고, 선언 기호와 그 기호에 할당된 값들로 테이블을 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1 패스&lt;/b&gt;&lt;br /&gt;어셈블러는 줄 번호를 추적하면서 한 줄씩 전체 어셈블리 프로그램을 훑는다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 줄 번호는 0에서 시작하며 A-명령어나 C-명령어를 만날 때 마다 그 값을 1씩 증가시키며,&lt;br /&gt;주석이나 레이블 선언이 나오면 값을 그대로 둡니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어셈블러는 레이블 선언 (xxx)가 나올 때 마다 기호 테이블에 새 항목을 추가하고,&lt;br /&gt;기호 xxx에 현재 줄 번호 + 1 값을 연결시킵니다. (ROM의 주소가 된다.)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1 패스가 끝나면 프로그램의 모든 레이블 기호가 그에 대응하는 값과 함께 기호 테이블에 추가됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2 패스&lt;/b&gt;&lt;br /&gt;어셈블러는 전체 프로그램을 다시 훑으며 각 라인을 다음과 같이 파싱합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 기호가 있는 A명령어 (@xxx에서 xxx가 기호)를 만날 때 마다, 기호 테이블에서 xxx를 조사하고&lt;br /&gt;테이블에 그 기호가 있으면 어셈블러는 그 기호를 대응하는 숫자값으로 교체하고 해당 명령어를 번역합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;없으면 그 기호는 새 변수라는 뜻이므로, 어셈블러는 기호 테이블에 &amp;lt;xxx, value&amp;gt; 항목을 추가하는데,&lt;br /&gt;여기서 value는 변수를 위해 RAM 공간 내에서 다음 번으로 사용 가능한 주소를 뜻합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 이 주소를 이용하여 해당 명령어의 번역을 완료합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;변수를 저장하기 위해 지정된 RAM 공간의 주소는 16부터 시작하며,&lt;br /&gt;코드에 새 변수가 나타날 때 마다 1씩 증가합니다.&lt;/p&gt;</description>
      <category>NandToTetris</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/91</guid>
      <comments>https://jaykos96.tistory.com/91#entry91comment</comments>
      <pubDate>Tue, 18 Feb 2025 23:48:27 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris-Hardware simulator / 컴퓨터 아키텍쳐-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/90</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;폰 노이만 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜링기계는 간단한 추장석 컴퓨터 모델로, 주로 이론 컴퓨터 과학에서 계산의 논리적 기초를 분석하는 데 활용됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면 폰 노이만 기계는 모든 현태 컴퓨터 플랫폼을 구성하는 실제 모델로서 폰 노이만 구조는 메모리 장치와 통신하고, 입력 장치에서 데이터를 받고, 출력 장치로 데이터를 내보내는&lt;span&gt;&amp;nbsp;&lt;/span&gt;중앙 처리 장치(CPU)를 바탕으로 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;컴퓨터가 조작하는 데이터 외에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;컴퓨터가 수행할 작업을 지시하는 명령어&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;도 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;메모리에 저장된다는 개념입니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cY0jKI/btsMh7SB3Bg/He96S9xo8LkjZI93yUHPS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cY0jKI/btsMh7SB3Bg/He96S9xo8LkjZI93yUHPS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cY0jKI/btsMh7SB3Bg/He96S9xo8LkjZI93yUHPS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcY0jKI%2FbtsMh7SB3Bg%2FHe96S9xo8LkjZI93yUHPS1%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;701&quot; height=&quot;321&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;메모리&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1032&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TJ49g/btsMjfbb37q/HtSCs4uAGZeQksmEMxOsV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TJ49g/btsMjfbb37q/HtSCs4uAGZeQksmEMxOsV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TJ49g/btsMjfbb37q/HtSCs4uAGZeQksmEMxOsV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTJ49g%2FbtsMjfbb37q%2FHtSCs4uAGZeQksmEMxOsV0%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;674&quot; height=&quot;377&quot; data-origin-width=&quot;1032&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리는 물리적관점과 논리적관점에서 바라 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리적으로는 주소를 지정 할 수 있는 고정된 크기의 레지스터들을 선형적으로 배열한 것 이며, 레지스터들은 각각의 값을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;논리적으로는 데이터 저장과 명령어 저장이라는 두가지 용도로 사용됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어와 데이터 모두 동일한 방식, 비트 열로 표현됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터가 저장되는 전용 메모리 영역은 데이터 메모리 (data memory), 명령어가 저장되는 전용 메모리 영역은 명령어 메모리(instruction memory)라고 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폰 노이만 구조를 따르는 모델중에서 데이터 메모리와 명령어 메모리가 필요에 따라 물리적으로 동일한 주소공간에서 동적으로 할당 및 관리되는 모델도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 모델은 두 메모리가 개별 주소공간을 가집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 모델 모두 장단점이 존재합니다.&lt;/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;이 작업을 주소지정 (addressing)이라고 합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;데이터 메모리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수, 배열, 객체와 같은 개념은 모두 2진 숫자열로 변환되어 데이터 메모리에 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소지정을 통해 데이터들의 개별적인 읽기, 쓰기작업을 할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명령어 메모리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수준 프로그램은 컴퓨터에서 실행되기 전에 먼저 대상 컴퓨터의 기계어로 변역되어야 합니다.&lt;br /&gt;고수준 명령문들은 항상 하나 이상의 저수준 명령어로 번역 되어서 2진, 또는 실행 가능한 버전 프로그램이라는 파일에 2진 값으로 기록됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 실행하려면 먼저 대용량 저장 장치에서 2진 버전 프로그램을 불러온 후&lt;br /&gt;그 명령어를 컴퓨터의 명령어 메모리에 직렬화해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;순수한 컴퓨터 아키텍처 관점에서 프로그램을 컴퓨터 메모리에&lt;br /&gt;어떻게 불러오는지는 외부의 문제로 취급합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;중앙 처리 장치 : CPU (&lt;/span&gt;Central Processing&lt;span&gt;&amp;nbsp;&lt;/span&gt;Unit)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;CPU는 현재 실행 중인 프로그램의 명령어를 실행하는 일을 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;각 명령어는 CPU에 수행할 계산, 접근할 레지스터, 다음에 불러와서 실행할 명령어를 알려줍니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;CPU는 산술 논리 장치, 레지스터 집합, 제어 장치를 활용해서 이 작업들을 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ALU(산술 논리 장치)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALU 칩은 컴퓨터에서 지원하는 모든 저수준 산술 및 논리 연산을 수행하는 장치입니다.&lt;br /&gt;일반적인 ALU는 주어진 두 값을 더하거나, 비트 단위 And를 계산하거나, 두 값이 동일한지 비교하는 일 등을 수행하며,&lt;br /&gt;설계에 따라 ALU가 지원하는 함수가 정해집니다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;레지스터&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CPU는 연산 도중에 중간 값을 임시로 저장해야 하는 경우가 많습니다.&lt;br /&gt;이론적으로는 이 값들을 메모리 레지스터에 저장할 수 있지만, CPU와 RAM은 별개의 칩이기에 때문에 신호가 이동하려면 거리가 멀다는 단점이 있습니다. 이때 발생하는 신호 지연을 기아상태(&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;stavation)&lt;/span&gt;라고 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기아 상태를 방지하고 성능을 향상시키기 위해 일반적으로 CPU에는 프로세서에서 곧바로 접근 가능한 메모리 역할을 하는&lt;br /&gt;고속 레지스터들을 적은 용량으로 탑재합니다. 이 레지스터들은 다양한 용도로 활용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 레지스터 : CPU의 단기 기억 메모리로 CPU계산의 중간값을 임시로 저장할 떄 사용됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;주소 지정 레지스터 (Addressing Register) : CPU가 메모리접근을 위해 사용하는 레지스터입니다. 명령어에 주소가 포함되지 않는 경우 사용합니다.&lt;/li&gt;
&lt;li&gt;프로그램 카운터 : 다&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;음에 불러와서 실행해야하는 명령어의 주소를 저장하는&lt;span&gt; 레지스터입니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;명령어 레지스터 : &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;현재 명령어를 저장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; 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;일반 컴퓨터에서는 레지스터가 많이 존재하지만 우리가 만들 HACK에는 어드레스 레지스터, 데이터 레지스터 그리고 프로그램 카운터 3개 입니다.&lt;/span&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;span style=&quot;color: #000000; text-align: left;&quot;&gt;메모리부터 프로그랭 명령을 순차적으로 꺼내&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;해독&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;하고, 해석에 따라서 명령어 실행에 필요한&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;제어 신호&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;를 기억장치, 연산장치, 입출력 장치 등으로 보내는 장치입니다. (&lt;/span&gt;프로그램 카운터(PC), 명령 해독기, 부호기, 명령 레지스터 등으로 구성됩니다.)&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: #212529; 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: #212529; text-align: start;&quot;&gt;마이크로코드&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;는 여러 장치에 해야 할 일을 알려 주는 1개 이상의 비트열입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;따라서 명령어는 실행되기 전에 마이크로코드로 디코딩되어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;그 후 각 마이크로 코드는 CPU 내에 지정된 하드웨어 장치(ALU, 레지스터, 메모리)로&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;전달되어, 전체 명령어가 실행되기 위해 장치가 수행해야 할 동작들을 알려줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인출-실행 (fetch-execute cycle)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로그램이 실행되는 각 주기마다 CPU는 명령어 메모리에서 2진 기계 명령어를 인출하고, 디코딩하고, 실행합니다.&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;CPU가 실행할 명령어는 메모리에 존재하고, CPU는&amp;nbsp;Program Counter(PC)를 통해서 다음번에 실행할 명령어를 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행할 명령어는 주소 버스를 통해 주소 지정 레지스터 (addressing register)에 copy됩니다. (fetch)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 명령어 레지스터가 해당 명령어를 읽고 디코딩하여 ALU, 데이터 레지스터 등을 이용하며 메모리와 상호작용 하며 적절하게 명령을 처리합니다. (execution)&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;하지만 fetch-execution 사이클에는 충돌 문제가 존재합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpdjLp/btsMjpsSHD3/ujtr7MEszDXhNPT7ko83V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpdjLp/btsMjpsSHD3/ujtr7MEszDXhNPT7ko83V0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpdjLp/btsMjpsSHD3/ujtr7MEszDXhNPT7ko83V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpdjLp%2FbtsMjpsSHD3%2Fujtr7MEszDXhNPT7ko83V0%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;577&quot; height=&quot;356&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch와 execution이 동시에 일어날 때, fetch 단계에서 pc의 값을 업데이트하는 동안, execution 단계에서 ALU가 연산을 진행하는 중 동일하게 pc에 값에 접근해야할 때 충돌이 일어납니다.&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;예를들어 execution 단계에서 조건부 분기처리나 jump와 같은 명령어가 있있다면 execution이 끝날 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pc값이 업데이트 되어야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 멀티플렉서(Mux)를 통해 충돌을 방지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mux를 통해 선택된 명령어는 IR로 이동하여 실행됩니다. (선택되지 않았던 명령어도 IR로 같이 가서 다음 주기 때 실행됩니다.)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;입력과 출력 (메모리 매핑 I/O&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수많은 I/O장치를 컴퓨터와 통신할 수 있도록 컴퓨터는 수많은 장치들을 동일하게 처리하는 기법이 필요합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;I/O장치와 컴퓨터가 연결되면 빈 메모리에 해당 장치가 사용할 공간이 할당되며, 일종의 '메모리 맵' 역할을 하는 전용 메모리 영역을 할당합니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이러한 작업은 보통 I/O장치의 installer 프로그램과 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;디바이스 드라이버(device driver)가&lt;/span&gt; 담당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;입력장치에 변동이 존재할 때 RAM에 해당 내용을 기록하고,&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CPU는 RAM에서 해당 데이터를 받아와 메모리 맵(RAM)에 저장하거나 연산하는 등 상황에 맞게 처리합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;I/O 장치는 RAM에서 해당 데이터를 받아와 화면에 출력하는 등 적절하게 동작합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이와 같은 방식으로 I/O장치의&lt;span&gt;&amp;nbsp;&lt;/span&gt;종류와 무관하게 사용이 가능합니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16bit 폰 노이만 기계로 CPU, 명령어메모리 및 데이터메모리 그리고 두개의 메모리 매핑 I/O 장치(외부장치)인 스크린과 키보드로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 핵 플랫폼에서 명령어 메모리는 프로그램이 미리 기록된 ROM 칩으로 물리적으로 구현됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CPU&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSTBsx/btsMjAunYQJ/vyxdSEyIbY5c3UKHY27LBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSTBsx/btsMjAunYQJ/vyxdSEyIbY5c3UKHY27LBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSTBsx/btsMjAunYQJ/vyxdSEyIbY5c3UKHY27LBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSTBsx%2FbtsMjAunYQJ%2FvyxdSEyIbY5c3UKHY27LBk%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;678&quot; height=&quot;480&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵 기계어로 작성된 명령들을 실행하도록 설계되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALU와 데이터 레지스터(D), 주소 레지스터(A), 프로그램 카운터(PC)로 구성되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 실행을 위한 명령어를 인출해 오는 명령어 메모리(RAM)와, 데이터 값을 읽거나 쓰는 데이터 메모리(ROM)에 연결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;D는 데이터를 저장하고, A는 데이터, RAM/ROM 주소 등을 저장합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;rom은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pc값에 따라 계속 해서 명령어를 출력합니다. 여기서 출력되는 명령어가 CPU의 현재명령어가 됩니다.&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;A명령어의 경우 16bit명령어는 A레지스터에 그대로 로드되는 2진값(&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;0--- ---- ---- ----&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;)으로 취급되며. C명령어는 CPU내의 다양한 칩들이 수행되는 여러 마이크로 연산을 가리키는 제어비트(&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;1xxa&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;cccc ccdd djjj)&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;추가로 C 명령어의 목적지가 M이면 writeM은 쓰기 명령을 위해 1이되고, 그렇지 않으면 0이되고, ALU의 결과는 outM으로 나갑니다.&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;M이 아니라면 writeM은 0으로 설정되고, outM은 아무 값이나 될 수 잇습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;reset이 1이면 pc를 0으로 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;fetch-execute cycle 을 통해 CPU가 하는 전반적인 일들을 설명 할 수 있습니다..&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;execute : pc를 통해 현재 명령어가 여러 칩에 전달됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주소명령어라면 A 레지스터에 값이 전달되고, 계산 명령어라면 ALU와 레지스터가 명령을 수행합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;fetch : ALU의 출력과 jump bit을 비교해 조건이 틀리면 PC++, 조건이 맞으면 PC = A (A로 jump) 를 수행합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음 CLK때는 PC가 가리키는 명령어가 ROM의 출력이 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; 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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beuoZ1/btsMi4gzejD/tfky3zoB3PQkel4z2DiHkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beuoZ1/btsMi4gzejD/tfky3zoB3PQkel4z2DiHkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beuoZ1/btsMi4gzejD/tfky3zoB3PQkel4z2DiHkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeuoZ1%2FbtsMi4gzejD%2Ftfky3zoB3PQkel4z2DiHkK%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;628&quot; height=&quot;393&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 칩은 기본적으로 세 개의 16비트 칩 (RAM16K, 스크린, 키보드) 부품으로 되어있습니다.&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;RAM = RAM16K + 스크린(RAM8K) + 키보드(레지스터, 읽기전용)&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;- RAM16K(16K 레지스터 RAM 칩, 범용 데이터 저장소)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Screen(내장형 8K RAM 칩, 스크린 메모리 맵, &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;0x4000-0x5FFF&lt;/span&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Keyboard(내장형 레지스터 칩, 읽기전용, 키보드 메모리 맵, &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;0x6000&lt;/span&gt;)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RAM16K&lt;/h4&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;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v7ahX/btsMjI7bSbn/x7A8W83yEKk3F191y00Tfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v7ahX/btsMjI7bSbn/x7A8W83yEKk3F191y00Tfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v7ahX/btsMjI7bSbn/x7A8W83yEKk3F191y00Tfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv7ahX%2FbtsMjI7bSbn%2Fx7A8W83yEKk3F191y00Tfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;311&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;address에 주소를 통해 out으로 데이터가 출력됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;in에 값(value), load Bit 1을 설정하면 해당 주소에 전달한 값이 저장됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터메모리는 기본적인 범용 데이터 저장공간, 메모리 맵을 통한 I/O 장치를 위한 영역이 존재합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;명령어 메모리&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sL7rv/btsMiYgrYKK/xk4SKotuLLxaKafKPXqI50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sL7rv/btsMiYgrYKK/xk4SKotuLLxaKafKPXqI50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sL7rv/btsMiYgrYKK/xk4SKotuLLxaKafKPXqI50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsL7rv%2FbtsMiYgrYKK%2Fxk4SKotuLLxaKafKPXqI50%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;607&quot; height=&quot;268&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&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;ROM32K라고도 불립니다.&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;0000 0000 0000 0000 ~ 0111 1111 1111 1111 2^15(32K)만큼 접근할수 있고,&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; 한 레지스터가 16비트이기에 출력은 16비트 크기를 가집니다.&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;쓰기 작업을 위한 in이나 load 단자는 존재하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입력/출력&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면, 키보드와 같은 I/O장치는 데이터 메모리 RAM에 매핑되고, 클럭마다 반영됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵 컴퓨터 플랫폼에서는 스크린 메모리 맵과 키보드 메모리 맵은 Screen과 Keyboard라는 2개의 내장형 칩으로 구현됩니다.&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: #212529; text-align: left;&quot;&gt;스크린&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uoCKO/btsMkJp3phZ/LdMFmJbOo5Qjv9ZSWolLv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uoCKO/btsMkJp3phZ/LdMFmJbOo5Qjv9ZSWolLv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uoCKO/btsMkJp3phZ/LdMFmJbOo5Qjv9ZSWolLv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuoCKO%2FbtsMkJp3phZ%2FLdMFmJbOo5Qjv9ZSWolLv1%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;566&quot; height=&quot;333&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;16비트 레지스터로 된 8K 메모리 칩으로 구현되며, &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;address와 load 입력을 받습니다.&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;스크린 공간이 8K이기에 주소가 13비트 입력으로 되어있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;칩에 기록한 Bit은 픽셀로 표시됩니다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;키보드&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2sx5Z/btsMiCfmO2b/xumzWAOqfhoXfoNk1epLQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2sx5Z/btsMiCfmO2b/xumzWAOqfhoXfoNk1epLQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2sx5Z/btsMiCfmO2b/xumzWAOqfhoXfoNk1epLQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2sx5Z%2FbtsMiCfmO2b%2FxumzWAOqfhoXfoNk1epLQk%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;684&quot; height=&quot;144&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&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;/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: #212529; text-align: start;&quot;&gt;키보드에서 키가 눌리면 Keyboard 칩의 output으로 키에 해당하는 문자의 16비트 코드를 출력하고, &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;아무것도 눌리지 않으면 칩은 0을 출력합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴퓨터&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcfn3H/btsMiHAMRq5/o96AeskaeYr4kAfszYWut1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcfn3H/btsMiHAMRq5/o96AeskaeYr4kAfszYWut1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcfn3H/btsMiHAMRq5/o96AeskaeYr4kAfszYWut1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdcfn3H%2FbtsMiHAMRq5%2Fo96AeskaeYr4kAfszYWut1%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;528&quot; height=&quot;308&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hack 시스템의 최상위 칩입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU, RAM, ROM, 스크린, 키보드로 구성되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램의 실행을 위해 rom에 미리 Load 해야하고, reset이 0이면 프로그램&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행, reset이 1이면 프로그램을 초기화합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CPU구현&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci5GqJ/btsMkBS5VFT/FfdPyXzx1Pi8kNYjojFhQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci5GqJ/btsMkBS5VFT/FfdPyXzx1Pi8kNYjojFhQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci5GqJ/btsMkBS5VFT/FfdPyXzx1Pi8kNYjojFhQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci5GqJ%2FbtsMkBS5VFT%2FFfdPyXzx1Pi8kNYjojFhQ0%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;747&quot; height=&quot;451&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 구현에 앞서 잠깐 CPU가 어떤일을 하는지 먼저 정리해보려합니다.&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;&lt;b&gt;1. 디코딩&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 명령어가 들어오는 부분부터 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어오는 명령어가 A명령어인지 C명령어인지 op-code를 통해 구분하는 디코딩작업을 수행해야합니다.&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;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;2. 명령어 실행&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A명령어의 경우 MSB를 제외한 15비트는 그대로 A 레지스터에 로드되고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 A레지스터의 출력은 M으로 전달됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;C명령어의 경우 111accccccccdddjjj 6개의 제어 비트 ccccccc와 a로 선택된 연산을 수행하고, ddd로 지정한 곳에 연산 결과를 저장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;a비트는 ALU 입력이 A 레지스터 값에서 올지,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;아니면 입력된 M 값에서 올지 결정하는 비트입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;cccccc 비트는 ALU에서 어떤 함수가 계산될지를 가리키고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;ddd 비트는 어떤 레지스터가 ALU 출력을 받아야 할지 지정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;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;jjj&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;비트는 다음에 인출할 명령어를 결정합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;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;000이 아닌 경우 연산 결과에 따라서 jjj의 조건(0과같거나, 크거나, 작거나 등)에 따라 어드레스 레지스터에 입력된 명령어 주소로 점프합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;3. 명령어 가져오기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC는 일반적으로 다음 순서의 명령어의 주소를 정하고 출력한다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;( PC(t)=PC(t-1)+1 )&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;하지만 goto 연산(분기문)의 경우 A 레지스터에 이동할 주소를 저장한 뒤 jump 명령어를 사용하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;( If jump(t) then PC(t)=A(t-1) else PC(t)=PC(t-1)+1 )&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;즉 레지스터의 출력을 PC의 입력으로 연결시키고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;jump를 해야할 때 PC의 load를 활성시키켜 PC에 다음명령어를 지정해주면 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가로 프로그램을 종료하려면 reset Bit을 set하여 PC의 값을 0으로 만들어야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1739800005492&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP CPU {
    IN inM[16],          // 데이터 메모리에서 읽은 값 (M=A인 경우)
        instruction[16], // 현재 실행할 명령어 (ROM에서 가져옴)
        reset;           // 프로그램을 처음부터 실행할지 여부

    OUT outM[16],        // 데이터 메모리에 저장할 값
        writeM,          // 데이터 메모리에 쓰기 여부
        addressM[15],    // 데이터 메모리 주소
        pc[15];          // Program Counter 값 (다음 명령어 주소)

    PARTS:
    
    // 명령어의 최상위 비트를 반전하여 A-명령어인지 C-명령어인지 판별 (A-명령어면 ni=1, C-명령어면 ni=0)
    Not(in=instruction[15],out=ni);
    
    // A-명령어이면 instruction 값을 선택하고, C-명령어이면 outtM 값을 선택하여 A 레지스터에 저장할 값 결정
    Mux16(a=outtM,b=instruction,sel=ni,out=i);
    
    // A 레지스터: A-명령어일 경우 값을 저장하고, 해당 값의 하위 15비트를 addressM에 출력 (메모리 주소로 사용됨)
    Or(a=ni,b=instruction[5],out=intoA);
    ARegister(in=i,load=intoA,out=A,out[0..14]=addressM);

    // C-명령어에서 A 레지스터 또는 메모리 값을 선택하여 ALU의 입력으로 전달
    And(a=instruction[15],b=instruction[12],out=AorM);
    Mux16(a=A,b=inM,sel=AorM,out=AM);
    
    // ALU 실행: 입력된 x(D 레지스터)와 y(A/M 레지스터)를 기반으로 연산 수행, 결과를 outM에 출력
    ALU(x=D,y=AM,zx=instruction[11],nx=instruction[10],zy=instruction[9],ny=instruction[8],f=instruction[7],no=instruction[6],out=outtM,out=outM,zr=zr,ng=ng);

    // C-명령어의 D 레지스터 저장 비트를 확인하여 D 레지스터 업데이트 여부 결정
    And(a=instruction[15],b=instruction[4],out=intoD);
    DRegister(in=outtM,load=intoD,out=D);

    // C-명령어의 메모리 쓰기 비트를 확인하여 writeM 결정 (1이면 메모리에 쓰기 수행)
    And(a=instruction[15],b=instruction[3],out=writeM);

    // 점프 로직: 조건을 판별하여 PC를 업데이트할지 결정
    Not(in=ng,out=pos); // 음수 여부 반전
    Not(in=zr,out=nzr); // 0 여부 반전
    And(a=instruction[15],b=instruction[0],out=jgt); // JGT 비트 체크
    And(a=pos,b=nzr,out=posnzr); // 양수이며 0이 아닐 경우
    And(a=jgt,b=posnzr,out=ld1); // 점프 조건 충족 시 ld1 활성화

    And(a=instruction[15],b=instruction[1],out=jeq); // JEQ 비트 체크
    And(a=jeq,b=zr,out=ld2); // 0일 경우 점프 조건 충족 시 ld2 활성화

    And(a=instruction[15],b=instruction[2],out=jlt); // JLT 비트 체크
    And(a=jlt,b=ng,out=ld3); // 음수일 경우 점프 조건 충족 시 ld3 활성화

    // ld1 (JGT) 또는 ld2 (JEQ) 활성화 여부 확인
    Or(a=ld1,b=ld2,out=ldt);
    // ld3 (JLT) 또는 이전 결과(ldt) 활성화 여부 확인
    Or(a=ld3,b=ldt,out=ld);

    // PC 업데이트: 점프 조건이 충족되면 A 레지스터 값으로 설정, 그렇지 않으면 1 증가
    PC(in=A,load=ld,inc=true,reset=reset,out[0..14]=pc);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;메모리 구현&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1739689192746&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Memory {
    IN in[16], load, address[15];
    OUT out[16];

    PARTS:
    DMux(in=load, sel=address[14], a=loadRAM, b=loadScreen);
    RAM16K(in=in, load=loadRAM, address=address[0..13], out=outRAM);
    Screen(in=in, load=loadScreen, address=address[0..12], out=outScreen);
    Keyboard(out=outKeyboard);
    Mux4Way16(a=outRAM, b=outRAM, c=outScreen, d=outKeyboard, sel=address[13..14], out=out);
}CHIP Memory {
    IN in[16], load, address[15];  // 16비트 데이터 입력, 쓰기 신호(load), 15비트 주소 입력
    OUT out[16];                   // 16비트 데이터 출력

    PARTS:
    // address[14] 값에 따라 load 신호를 RAM 또는 Screen으로 분배
    DMux(in=load, sel=address[14], a=loadRAM, b=loadScreen);
    
    // RAM (16K 메모리 블록)
    RAM16K(in=in, load=loadRAM, address=address[0..13], out=outRAM);
    
    // Screen 메모리
    Screen(in=in, load=loadScreen, address=address[0..12], out=outScreen);
    
    // 키보드 입력을 받아 outKeyboard에 출력
    Keyboard(out=outKeyboard);
    
    // address[13..14]에 따라 4개의 데이터 소스 중 하나를 선택하여 out으로 출력
    // a, b -&amp;gt; RAM, c -&amp;gt; Screen, d -&amp;gt; Keyboard
    Mux4Way16(a=outRAM, b=outRAM, c=outScreen, d=outKeyboard, sel=address[13..14], out=out);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;컴퓨터 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 Computer 칩은 CPU, MEMORY, ROM32K 칩으로 구현됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;- 사용자가 reset 입력을 활성화하면, CPU의 pc에서는 0이 출력되고 프로그램의 첫 번째 명령어를 출력합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- CPU는 출력된 명령어를 실행하며, 필요에 따라 적절한 레지스터를 읽고씁니다. 또 다음에 어떤 명령어를 실행할지 결정합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 다음 명령어가 정해지면 해당 명령어의 주소를 pc 출력으로 내보낸다. (&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;CPU의 PC 출력은 명령어 메모리의 address 입력으로 연결)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 메모리로 전달된 다음 명령어는 CPU의 instruction 입력으로 연결되어서 fetch-execute cycle을 마무리합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbXegP/btsMk1RxZn7/4vnDw6emLQ6ikDwfbqe8UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbXegP/btsMk1RxZn7/4vnDw6emLQ6ikDwfbqe8UK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbXegP/btsMk1RxZn7/4vnDw6emLQ6ikDwfbqe8UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbXegP%2FbtsMk1RxZn7%2F4vnDw6emLQ6ikDwfbqe8UK%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;647&quot; height=&quot;388&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1739689231213&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Computer {

    IN reset;  // 프로그램을 처음부터 실행할지 여부를 결정하는 입력 신호

    PARTS:
    
    // ROM : 프로그램 명령어를 저장하는 32K 크기의 읽기 전용 메모리, PC값을 주소로 명령어를 가져옴
    ROM32K(address=pc, out=instruction);

    // CPU : 명령어를 실행하고, 메모리 및 PC값을 업데이트
    CPU(inM=inM, instruction=instruction, reset=reset, outM=outM, writeM=writeM, addressM=addressM, pc=pc);

    // RAM : CPU 연산결과 저장 및 필요 데이터를 가져옴
    Memory(in=outM, load=writeM, address=addressM, out=inM);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>NandToTetris</category>
      <category>nandtotetris</category>
      <category>컴퓨터 아키텍쳐</category>
      <category>폰 노이만 구조</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/90</guid>
      <comments>https://jaykos96.tistory.com/90#entry90comment</comments>
      <pubDate>Thu, 13 Feb 2025 21:34:38 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris-Hardware simulator / 기계어-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/89</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;기계어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어(기계)를 제어하는 언어이며, 하드웨어와 소프트웨어 사이 가장 중요한 인터페이스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 명령을 처리 할 때 사용되는 언어로 2진수로 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 기계어는 기본적인 산술연산과, And, Or, Not같은 논리연산들을 위한 명령어들을 지원합니다.&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;아래 예시는 완전한 기계어(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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 메모리에 올라가있는 명령들을 순서대로 실행하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 명령어로 점프하거나, 반복해야하는 경우도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 분기동작을 위한 기능도 존재합니다.&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;(R1, R2는 레지스터를 의미합니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7BIOg/btsMaz8ZvlQ/rvVkg9JmQgKrj6PZhPZt7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7BIOg/btsMaz8ZvlQ/rvVkg9JmQgKrj6PZhPZt7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7BIOg/btsMaz8ZvlQ/rvVkg9JmQgKrj6PZhPZt7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7BIOg%2FbtsMaz8ZvlQ%2FrvVkg9JmQgKrj6PZhPZt7k%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;651&quot; height=&quot;330&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;핵 기계어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서 다루는 핵 컴퓨팅 시스템은 폰 노이만 구조를 따르며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16bit 컴퓨터입니다. 즉 CPU와 메모리 장치가 16bit값들을 처리하고 저장하도록 설계되었다는 의미입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mgZtc/btsL8NnFoNT/UPhfSeP3pc3tDHvMIToQy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mgZtc/btsL8NnFoNT/UPhfSeP3pc3tDHvMIToQy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mgZtc/btsL8NnFoNT/UPhfSeP3pc3tDHvMIToQy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmgZtc%2FbtsL8NnFoNT%2FUPhfSeP3pc3tDHvMIToQy1%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;671&quot; height=&quot;401&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메모리&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHwZH9/btsL93Qm1gq/zDik7xgE6QF61eKSxhe9P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHwZH9/btsL93Qm1gq/zDik7xgE6QF61eKSxhe9P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHwZH9/btsL93Qm1gq/zDik7xgE6QF61eKSxhe9P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHwZH9%2FbtsL93Qm1gq%2FzDik7xgE6QF61eKSxhe9P0%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;537&quot; height=&quot;281&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAM과 ROM 두개의 메모리를 사용하며 두 메모리 모두 폭(width)이 16bit , 주소공간은 15bit입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 메모리(RAM)는 프로그램이 조작하는 데이터(2진수)를 저장하고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;명령어 메모리(ROM)는 프로그램의 명령어들을 저장합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 주소공간 (2^15) 마다&amp;nbsp; width (16bit) 만큼의 데이터를 저장 할 수 있어 총&amp;nbsp; 64kb 의 데이터를 저장 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 주소는 0 ~ 32k -1까지 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 주소지정 가능한 16bit 레지스터가 일렬로 32k -1까지 나열되어있어 있다고 생각하면 편합니다.&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;&lt;b&gt;레지스터&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tC8z3/btsMaw5zuhp/eMLnkt36RYSOW4t5YCZdSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tC8z3/btsMaw5zuhp/eMLnkt36RYSOW4t5YCZdSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tC8z3/btsMaw5zuhp/eMLnkt36RYSOW4t5YCZdSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtC8z3%2FbtsMaw5zuhp%2FeMLnkt36RYSOW4t5YCZdSK%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;568&quot; height=&quot;329&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 16bit 레지스터 데이터 레지스터(D), 주소 레지스터(A), 선택된 데이터 메모리 레지스터(M)이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 레지스터(D)는 16bit값을 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 레지스터(A)는 RAM 혹은 ROM의 주소를 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;핵 기계어 문법&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;A-명령어 (op-code가 0일때)&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QKHxk/btsMaB1qiMm/C1sOTUbYfR8MBkKXKbKTv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QKHxk/btsMaB1qiMm/C1sOTUbYfR8MBkKXKbKTv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QKHxk/btsMaB1qiMm/C1sOTUbYfR8MBkKXKbKTv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQKHxk%2FbtsMaB1qiMm%2FC1sOTUbYfR8MBkKXKbKTv0%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;814&quot; height=&quot;123&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A레지스터에 15bit값을 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스터에 들어오는 16bit중 가장 왼쪽 비트는 연산코드(op-code)를 나타내고 나머지 음수가 아닌 이진수를 가리키는 15bit를 A레지스터에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A레지스터에 값을 저장하면 선택된 레지스터의 주소는 선택된 데이터 메모리 레지스터(M)에도 설정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 M을 참조하여 다음 C명령어에서 해당 레지스터를 조작 할 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;변수지정 : @xxx&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;핵 명령어 @xxx는 A 레지스터에 값 x를 지정합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;A레지스터에 지정한 값 xxx가 주소로 사용될지, 값으로 사용될지는 다음에 나오는 명령어에 따라 달라집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738847912965&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예시1 : 데이터 레지스터에 상수값 입력
@23 // A레지스터의 값을 23으로 설정
D=A // 레지스터(D)에 레지스터(A)값 저장 D=23

//예시2 : 주소값 사용
@17 // A레지스터의 값을 17로 설정
M=0 // RAM[17]을 0으로 설정, RAM[17] = 0

// 예시3 : RAM[100]= 17
@17
D=A
@100
M=D 

// 예시 4 : RAM[100] = RAM[200]
@100
D=M
@200
M=D&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;C-명령어 (&lt;b&gt;op-code가 1일때)&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCv6cW/btsMci0SQ82/NuQTV8OUMTGfYxs0ngxZWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCv6cW/btsMci0SQ82/NuQTV8OUMTGfYxs0ngxZWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCv6cW/btsMci0SQ82/NuQTV8OUMTGfYxs0ngxZWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCv6cW%2FbtsMci0SQ82%2FNuQTV8OUMTGfYxs0ngxZWk%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;647&quot; height=&quot;185&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;1456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/riXX7/btsMbKwjEbl/jDOmTnQoM46kFVQgKRjQJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/riXX7/btsMbKwjEbl/jDOmTnQoM46kFVQgKRjQJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/riXX7/btsMbKwjEbl/jDOmTnQoM46kFVQgKRjQJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FriXX7%2FbtsMbKwjEbl%2FjDOmTnQoM46kFVQgKRjQJ1%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;620&quot; height=&quot;587&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;1456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무었을 계산할지 (comp), 어디에 계산된 값을 저장할지(dest), 그 다음 무엇을 할지(jump)에 대한 명령어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16bit 중 맨 왼쪽은 연산코드(op-code)&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;다음 7개 비트는 comp&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 3개 비트는 dest&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨오른쪽 3개의 비트는 jump를 나타냅니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;comp (acccccc)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7비트로 ALU가 수행할 연산을 결정하는 comp 비트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 ALU입력은 D레지스터에서 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 ALU입력은 A레지스터(a비트가 0일때)나 M레지스터(a비트가 1일때)에서 입력을 받습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;comp에서는 a가 0일때와, 1일때 수행하는 연산이 따로 나눠져있습니다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;dest (ddd)&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3비트로 ALU 연산 결과를 담을 목적지입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저장하지 않을지(null), M에 담을지(RAM[A]), D 레지스터에 담을지, A레지스터 D레지스터 둘다에 연산 결과를 담을지 결정합니다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;jump (jjj)&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;jump 비트는 연산 결과에 따라 &lt;span&gt;A&lt;/span&gt;레지스터에 지정된 주소의&lt;span&gt;&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;000인경우 점프 없이 다음 명령어를 불러오고,&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;001 인경우 COMP의 연산 결과가 0보다 크면 점프합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;무조건 점프 JMP(111)는 관례상 0;JMP로 정의됩니다.&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;b&gt;(A 레지스터를 사용할 때 충돌 방지필요)&lt;/b&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;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;핵 컴퓨터는 RAM과 ROM의 주소를 지정할 때 주소 레지스터 하나만 사용됩니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;따라서 @n 명령어를 실행할 때 RAM[n], ROM[n]이 모두 선택됩니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;그래서 이어지는 C-명령어가 M을 조작하거나, 점프를 수행하게 된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;이때 충돌을 피하기 위해서는 &lt;/span&gt;&lt;b&gt;M에 대한 참조를 포함하는 C-명령어에서는 점프를 지정하지 않도록 하고,&lt;br /&gt;점프를 지정한 C-명령어에서는 M을 참조하지 말아야 합니다.&lt;/b&gt;&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 두 레지스터(R1, R2)에 값들을 더하고, 그 합에 17을 더한 후 그 결과를 세번째 RAM레지스터에 저장&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1846&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctXhvJ/btsMbyJ2dAf/LdEBll59MqQaDe0foMfN4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctXhvJ/btsMbyJ2dAf/LdEBll59MqQaDe0foMfN4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctXhvJ/btsMbyJ2dAf/LdEBll59MqQaDe0foMfN4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctXhvJ%2FbtsMbyJ2dAf%2FLdEBll59MqQaDe0foMfN4k%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;1846&quot; height=&quot;644&quot; data-origin-width=&quot;1846&quot; data-origin-height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 명령어의 의미는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. @0 // RAM [0] 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. D=M // 먼저 RAM[0]의 값을 D레지스터로 옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. @1 // RAM[1] 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. D=D+M // 이후 RAM[1]을 선택하여 D레지스터에 들어있는 값과 현재 선택된 메모리 레지스터의 값을 더해 D레지스터에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. @17 // A레지스터에 정수 17 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. D=D+A // D레지스터에 A레지스터의 값 더함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. @2 // RAM[2] 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. M=D // 선택된 레지스터 RAM[2]에 D레지스터의 값 (17)을 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 1+2+3+ ... + n을 계산하시오 (n은 첫번째 레지스터의 값, 두번째 RAM레지스터에 결과 출력)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739023974389&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// i, sum 변수 초기화
    @i
    M=1
    @sum
    M=0
// 반복문 시작
(LOOP)
    @R0 // 반복할 횟수 N이 들어있음
    D=M
    @i
    D=D-M // D = D - i (D는 R0 - i)
    @STOP
    D;JLT // i가 R0보다 크면 STOP으로 점프 (반복 종료)    
    @sum
    D=M
    @i
    D=D+M
    @sum
    M=D
    @i
    M=M+1
    @LOOP 
    0;JMP // 다시 LOOP로 돌아가서 계속 반복
(STOP)
    @sum
    D+M
    @R1
    M=D
(END)
    @END
    0;JMP&lt;/code&gt;&lt;/pre&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;b&gt;3.&amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;for i=0...n {do somthing with arr[i]}&lt;span&gt; 를 구현해보자&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739024992170&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @n
    M=0 // RAM[n] = 0
(LOOP)
    @n
    D=M // D = 0
    @R1 // 특정값
    D=D-M // R1과 D의 차 계산
    @END
    D;JEQ // R1과 D가 같은지 비교 -&amp;gt; D가 0일 경우 @END로 점프    
    @R0
    D=M // D = RAM[0] 
    @n
    A=M+D // RAM[n]과 D(R0) 값을 더한 결과 A에 지정
    M=-1  // RAM[A]에 -1 저장     
    @n
    M=M+1 // n값 1증가    
    @LOOP
    0;JMP
(END)
    @END
	0;JMP&lt;/code&gt;&lt;/pre&gt;</description>
      <category>NandToTetris</category>
      <category>nandtotetris</category>
      <category>기계어</category>
      <category>밑바닥부터 만드는 컴퓨팅 시스템</category>
      <category>핵기계어</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/89</guid>
      <comments>https://jaykos96.tistory.com/89#entry89comment</comments>
      <pubDate>Wed, 5 Feb 2025 23:00:50 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris-Hardware simulator / 레지스터, 메모리, PC 실습-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지 만들었던 칩들은 시간과 무관한 조합(combination)칩입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 실습할 칩들은 현재시점의 입력과 이전에 처리되었던 칩들의 결과에도 영향을 받는 순차(sequential)칩 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에서의 시간은 클럭(tick, tock)이라는 2개의 이산신호를 이용해서 구현 할 수 있습니다.&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;데이터 플립 플롯 (DFF)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에서의 시간 클럭(tick, tock)을 사용하는 논리게이트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DFF는 클럭 신호에 맞추어 데이터를 출력하며, 이전에 입력되었던 신호를 출력하는 간단한 동작을 구현합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(클럭 신호는 클록 전용 버스를 통해 DDL로 전달됩니다.)&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;예를들어 DFF의 in에 1이 들어간다면 DFF는 in으로 들어온 데이터 1이 저장되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 클럭 때 DFF에서 가지고 있던 데이터 1을 출력합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738242373958&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 저번 클럭(t-1) 때 들어온 데이터를 이번 클럭(t)에 출력합니다
out(t)=in(t-1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SgclV/btsL3ZVwcT5/zMoAvyNl9uGyt1yDv9v6S0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SgclV/btsL3ZVwcT5/zMoAvyNl9uGyt1yDv9v6S0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SgclV/btsL3ZVwcT5/zMoAvyNl9uGyt1yDv9v6S0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSgclV%2FbtsL3ZVwcT5%2FzMoAvyNl9uGyt1yDv9v6S0%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;646&quot; height=&quot;228&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt2IGt/btsL4IFsrs7/mu3QMWhC0z5J3iPeCdkpKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt2IGt/btsL4IFsrs7/mu3QMWhC0z5J3iPeCdkpKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt2IGt/btsL4IFsrs7/mu3QMWhC0z5J3iPeCdkpKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt2IGt%2FbtsL4IFsrs7%2Fmu3QMWhC0z5J3iPeCdkpKK%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;667&quot; height=&quot;308&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;레지스터 (Register)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFF은 이전에 들어온 입력데이터를 계속해서 출력만 하기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DFF 저장된 데이터를 변경하거나 출력하기 위한 장치, 레지스터가 필요합니다.&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;레지스터에 저장되는 비트 수는 width라고도 하며, 이 데이터를 word, 크기를 wordsize라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;32Bit 운영체제의 wordsize는 32Bit, 64Bit는 64Bit입니다.&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;데이터 비트를 전달하는 입력 in, 쓰기 기능을 설정하는 load입력, 레지스터의 현재상태를 출력하는 out이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;load가 1이면 레지스터의 출력은 in(t)의 값을 저장 후 출력하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt; load가 0이면 레지스터의 출력은 out(t-1), 즉 저번 클럭에 유지된 값을 값을  출력합니다.&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;1022&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIle0t/btsL3XpSwEB/HbwUBEgqWYXnt2ZpSCK0a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIle0t/btsL3XpSwEB/HbwUBEgqWYXnt2ZpSCK0a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIle0t/btsL3XpSwEB/HbwUBEgqWYXnt2ZpSCK0a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIle0t%2FbtsL3XpSwEB%2FHbwUBEgqWYXnt2ZpSCK0a0%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;494&quot; height=&quot;255&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1bit 레지스터&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스터에 입력이 들어오면 먼저 Mux에 의해 선택된 out이 DFF로 전달됩니다.&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;레지스터에 들어온 load가 0이라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;레지스터는 이전에 DFF에 존재하던 데이터를 출력해야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 DFF의 in에는 Mux에 따른 출력(out)이 전달되고 (load가 0이기에 DFF에 0이 전달됨),&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFF에 0이 들어오면 상태가 변하지 않으므로 다음 클럭에 맞추어 이전 출력을 그대로 출력합니다.&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;만약 레지스터의 load에 1이 전달되면.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in에 따른 MUX의 out이 DFF에 전달되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFF는 들어온 in(0 또는 1)에 따라 이전 데이터를 출력하거나 새로운 값을 DFF에 저장하고 다음 클럭에 출력합니다.&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;(16Bit 레지스터도 구현은 동일합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1738404881847&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Bit {
    IN in, load;
    OUT out;

    PARTS:
    Mux(a=muxin, b=in , sel=load, out=muxout);
    DFF(in=muxout, out=muxin, out=out); // MUX, 최종out 두 방향으로 전달
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;메모리 (Random-Access-Memory)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 (RAM)는 n개의 레지스터 칩으로 구성되며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAM은 각 레지스터마다 특정주소(0 ~ n-1)를 할당하고 이 주소를 이용해 특정 레지스터의 값을 읽거나 쓸 수 있는 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;어떤&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;레지스터의 주소든 같은 속도로 접근 가능하기에 Random Access Memory라고 합니다.&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;메모리는 in, load, address 세 개의 입력을 받아 address에 위치한 레지스터에서 읽기 또는 쓰기를 하여 결과를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기,쓰기가 일어나는 '한번의 동작'에서 address로 들어온 주소의 레지스터만 사용합니다.&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;load가 0과 address가 들어오면 선택한 레지스터의 값을 반환하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;load가 1, address, in이 들어오면 In에 들어온 값을 레지스터에 저장하고 다음 클럭에 그 값을 반환합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dt0ycS/btsL3xdZeJU/W4YofkOGBWzeTtBr3iOvY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dt0ycS/btsL3xdZeJU/W4YofkOGBWzeTtBr3iOvY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dt0ycS/btsL3xdZeJU/W4YofkOGBWzeTtBr3iOvY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdt0ycS%2FbtsL3xdZeJU%2FW4YofkOGBWzeTtBr3iOvY1%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;433&quot; height=&quot;415&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1738501704938&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP RAM8 {
    IN  in[16], load, address[3]; 
    OUT out[16];                 

    PARTS:
    Register(in=in, load=sel0, out=r0);
    Register(in=in, load=sel1, out=r1);
    Register(in=in, load=sel2, out=r2);
    Register(in=in, load=sel3, out=r3);
    Register(in=in, load=sel4, out=r4);
    Register(in=in, load=sel5, out=r5);
    Register(in=in, load=sel6, out=r6);
    Register(in=in, load=sel7, out=r7);

    // 주소(address)에 따라 8개의 레지스터 중 하나만 load 활성화
    DMux8Way(in=load, sel=address, a=sel0, b=sel1, c=sel2, d=sel3, 
             e=sel4, f=sel5, g=sel6, h=sel7);

    // 주소(address)에 따라 선택된 레지스터의 데이터를 출력
    Mux8Way16(a=r0, b=r1, c=r2, d=r3, e=r4, f=r5, g=r6, h=r7, 
              sel=address, out=out);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;프로그램 카운터 (Program Counter)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 카운터는 CPU가 다음에 실행할 명령어의 주소를 레지스터에 전달해주는 기능을 합니다.&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;load, inc, reset, in 4개의 제어비트가 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in, load에 데이터가 들어오면 in에 해당하는 값으로 PC의 값을 설정하거나 매 클럭마다 값을 1씩 증가시키는 동작을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 카운터를 관리하는 inc와 reset 두가지의 제어비트가 추가로 존재하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;inc가 활성화(1) 되어있으면 매 클록 주기마다 상태값을 1씩 증가시키며 (pc++) reset 비트를 활성화(1)하면 카운터(PC)의 상태값이 초기화됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 load, inc, reset모두 0이라면 현재 PC값을 유지합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blUB8g/btsL484UF3N/wnfh0kEDUwxs3g2PCFBndk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blUB8g/btsL484UF3N/wnfh0kEDUwxs3g2PCFBndk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blUB8g/btsL484UF3N/wnfh0kEDUwxs3g2PCFBndk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblUB8g%2FbtsL484UF3N%2Fwnfh0kEDUwxs3g2PCFBndk%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;501&quot; height=&quot;180&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1738502924585&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP PC {
    IN in[16], reset, load, inc;
    OUT out[16];
    
    PARTS:
    // 현재 PC 값을 저장할 16비트 레지스터
    Register(in=pcNext, load=true, out=pcCurrent, out=out);

    // 현재 PC + 1 계산 (자동 증가)
    Inc16(in=pcCurrent, out=pcPlusOne);

    // Mux로 동작 선택
    Mux16(a=pcCurrent, b=pcPlusOne, sel=inc, out=pcInc);   // inc=1이면 증가

    Mux16(a=pcInc, b=in, sel=load, out=pcLoad);            // load=1이면 in 값으로 점프

    Mux16(a=pcLoad, b=false, sel=reset, out=pcNext);       // reset=1이면 0으로 리셋    
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>NandToTetris</category>
      <category>dff</category>
      <category>nandtotetris</category>
      <category>pc</category>
      <category>레지스터</category>
      <category>메모리</category>
      <category>밑바닥부터 만드는 컴퓨팅 시스템</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/88</guid>
      <comments>https://jaykos96.tistory.com/88#entry88comment</comments>
      <pubDate>Thu, 30 Jan 2025 22:45:08 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris-Hardware simulator / Adder, Inc ,ALU 실습-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 내부에서 모든것은 2진코드로 표현되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스터의 wordsize cpu가 한번에 처리 할 수 있는 단위)에 따라 처리할 수 있는 데이터의 단위가 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cpu아키텍쳐에 따라 wordsize가 32bit라면 4바이트, 64bit라면 8바이트의 단위를 한번에 처리 할 수 있습니다.&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;예를들어 어떤 컴퓨터가 8bit wordsize를 가졌다면 총 255까지의 숫자를 표현 할 수 있지만, 음수는 표현할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 컴퓨팅 시스템에서 제한된 비트 안에서 어떻게 음수를 표현하는지, 그리고 단순한 가산기부터 ALU를 만드는 실습까지 진행해보려 합니다.&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;컴퓨터에서의 2진수 처리&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;덧셈&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽에서 끝 비트인 최하위 비트(least significant bit, LSB)부터 최상위 비트 (most significant bit, MSB)까지 각각 숫자를 더해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더해 2가되면 자리올림비트 (CarryBit)를 만들어 다음자리수에 올려 계산해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 최상위 비트(MSB)에서 자리올림(오버플로)이 발생한다면 사용하는 bit수에 맞게 버림 처리를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d86xwW/btsL1jTE6ai/b6SKuC9TTNPpMBqhjaT9Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d86xwW/btsL1jTE6ai/b6SKuC9TTNPpMBqhjaT9Ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d86xwW/btsL1jTE6ai/b6SKuC9TTNPpMBqhjaT9Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd86xwW%2FbtsL1jTE6ai%2Fb6SKuC9TTNPpMBqhjaT9Ek%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;299&quot; height=&quot;340&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2진수의 음수표현과 2의 보수법&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2의 보수법&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;376&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Jo5i/btsL22wotJR/YgT088XDCOp7PmhEiSo0yK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Jo5i/btsL22wotJR/YgT088XDCOp7PmhEiSo0yK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Jo5i/btsL22wotJR/YgT088XDCOp7PmhEiSo0yK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Jo5i%2FbtsL22wotJR%2FYgT088XDCOp7PmhEiSo0yK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;284&quot; height=&quot;699&quot; data-origin-width=&quot;376&quot; data-origin-height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 컴퓨터에서는 이진수의 음수를 표현하기 위해 2의 보수법을 활용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보수를 구한다는건 각 양수에 대응하는 음수를 구한다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1의 보수로 음수 표기를 하게되면 음의 0, 양의 0이 존재하기에 컴퓨터에서는 2의 보수법을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 &lt;b&gt;4bit 2진법 쳬계에서 음수 x를 표현하는 이진코드는 &quot;2^4 - x&quot; 로 나타내게됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-7을 이진수로 나타낸다면 &quot;2^4-7&quot;이고 십진법으로 계산했을때는 &quot;9&quot;, 이진법으로는 1001입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7은 이진법으로 0111이기에 &quot;1001 + 0111 = 0000&quot;이 되어 2의 보수임을 확인 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 2의 보수를 찾으려면, 모든 bit에 대해서 1의보수(0을 1로, 1을 0으로)를 취한 다음 1을 더해주면 됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;부호비트 (sign bit)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zzyk2/btsL1ZUGbNa/oJ0cinvcRgoxna75jamcbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zzyk2/btsL1ZUGbNa/oJ0cinvcRgoxna75jamcbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zzyk2/btsL1ZUGbNa/oJ0cinvcRgoxna75jamcbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzzyk2%2FbtsL1ZUGbNa%2FoJ0cinvcRgoxna75jamcbK%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;386&quot; height=&quot;372&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴퓨터에서는 음수를 표현하기 위해 비트의 가장 왼쪽 비트(MSB)를 부호비트(sign bit)로 사용합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 부호비트가 1이라면 음수, 0이라면 양수가 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;양수만 표현한다면 4비트는 0부터 15까지의 16개의 수를 표현 할 수 있고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부호비트를 사용한다면 16개의 절반을 음수를 위해 할당합니다.&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉 4비트 안에서 음수까지 표현해야한다면 위의 표와 같이 -8부터 7까지 표현 할 수 있게됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;두 비트를 더해서 결과 값으로 합(Sum)과 자리올림(Carry)를 bit를 산출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xor 과 And 연산을 이용해서 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mqFqv/btsL0A9KE4T/kEK5XifzLDKfbnKJHh1okk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mqFqv/btsL0A9KE4T/kEK5XifzLDKfbnKJHh1okk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mqFqv/btsL0A9KE4T/kEK5XifzLDKfbnKJHh1okk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmqFqv%2FbtsL0A9KE4T%2FkEK5XifzLDKfbnKJHh1okk%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;585&quot; height=&quot;359&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1737957979445&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP HalfAdder {
    IN a, b;    // 1-bit inputs
    OUT sum,    // Right bit of a + b 
        carry;  // Left bit of a + b

    PARTS:
    Xor(a =a, b =b, out = sum);
    And(a =a, b =b, out = carry);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;전가산기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 비트를 계산하는 가산기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력은 반가산기와 동일하게 carry, sum을 산출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HalfAdder와 Or를 통해서 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTKjdT/btsL0XjzEpG/en9qRTk7ca51G2FxJeL9Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTKjdT/btsL0XjzEpG/en9qRTk7ca51G2FxJeL9Xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTKjdT/btsL0XjzEpG/en9qRTk7ca51G2FxJeL9Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTKjdT%2FbtsL0XjzEpG%2Fen9qRTk7ca51G2FxJeL9Xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;406&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1737958694909&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP FullAdder {
    IN a, b, c;  // 1-bit inputs
    OUT sum,     // Right bit of a + b + c
        carry;   // Left bit of a + b + c

    PARTS:
    HalfAdder(a=a , b=b , sum=sum1, carry=carry1);
    HalfAdder(a=sum1, b=c, sum=sum, carry=carry2);
    Or(a=carry1 , b=carry2 , out=carry );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;16Bit 가산기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16bit 가산기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 16bit수를 계산할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 책에서는 오버플로우는 무시합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1737959042074&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Add16 {
    IN a[16], b[16];
    OUT out[16];

    PARTS:
    FullAdder(a=a[0], b=b[0], c=false, sum=out[0], carry=carry0);
    FullAdder(a=a[1], b=b[1], c=carry0, sum=out[1], carry=carry1);
    FullAdder(a=a[2], b=b[2], c=carry1, sum=out[2], carry=carry2);
    FullAdder(a=a[3], b=b[3], c=carry2, sum=out[3], carry=carry3);
    FullAdder(a=a[4], b=b[4], c=carry3, sum=out[4], carry=carry4);
    FullAdder(a=a[5], b=b[5], c=carry4, sum=out[5], carry=carry5);
    FullAdder(a=a[6], b=b[6], c=carry5, sum=out[6], carry=carry6);
    FullAdder(a=a[7], b=b[7], c=carry6, sum=out[7], carry=carry7);
    FullAdder(a=a[8], b=b[8], c=carry7, sum=out[8], carry=carry8);
    FullAdder(a=a[9], b=b[9], c=carry8, sum=out[9], carry=carry9);
    FullAdder(a=a[10], b=b[10], c=carry9, sum=out[10], carry=carry10);
    FullAdder(a=a[11], b=b[11], c=carry10, sum=out[11], carry=carry11);
    FullAdder(a=a[12], b=b[12], c=carry11, sum=out[12], carry=carry12);
    FullAdder(a=a[13], b=b[13], c=carry12, sum=out[13], carry=carry13);
    FullAdder(a=a[14], b=b[14], c=carry13, sum=out[14], carry=carry14);
    FullAdder(a=a[15], b=b[15], c=carry14, sum=out[15], carry=carry15);
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;증분기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진수에 1을 더합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 컴퓨터 아키텍쳐를 설정 할 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 숫자에 1을 더하는 칩이 있는게 편리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 b에 값을 설정하지 않으면 모두 '0'인 bit입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 b[0], b의 가장 오른쪽 비트 MSB를 1로 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1737960142883&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Inc16 {
    IN in[16];
    OUT out[16];

    PARTS:
    Add16(a=in, b[0]=true, out=out);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;ALU (산술논리장치)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cpu의 계산을 담당하는 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 수의 덧샘, 뺄셈, 불 연산 등등 연산을 할 수 있는 칩입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력은 x,y 두개와 6개의 제어비트 zx, nx, zy, ny, f, no가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;zx가 1이면 x를 0으로 만들고, nx가 1이면 x를 반전시킵니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;zy, ny는 y에 대해서 1이면 y를 0으로 만들고, ny가 1이면 y를 반전시킵니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;f가 1이면 변환된 x와 y를 ADD하고, 0이면 AND 연산을 합니다. no가 1이면 반환 값을 반전시킨다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;zr은 위에서 계산한 결과가 0이면 True, ng는 위에서 계산한 결과가 음수이면 True입니다.&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;(해당 책에서 만드는 cpu는 곱셈연산이 없습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYtYFW/btsL3gImf6B/kGAKc0YQ8tmkcL8U74xr1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYtYFW/btsL3gImf6B/kGAKc0YQ8tmkcL8U74xr1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYtYFW/btsL3gImf6B/kGAKc0YQ8tmkcL8U74xr1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYtYFW%2FbtsL3gImf6B%2FkGAKc0YQ8tmkcL8U74xr1k%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;684&quot; height=&quot;318&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zAgYQ/btsL2RordOK/oK22aLv1kkaA6nBn35cqok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zAgYQ/btsL2RordOK/oK22aLv1kkaA6nBn35cqok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zAgYQ/btsL2RordOK/oK22aLv1kkaA6nBn35cqok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzAgYQ%2FbtsL2RordOK%2FoK22aLv1kkaA6nBn35cqok%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;761&quot; height=&quot;618&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1737964458584&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP ALU {
    IN  
        x[16], y[16],         
        zx, 
        nx, 
        zy, 
        ny, 
        f, 
        no;
    OUT 
        out[16], 
        zr,      
        ng;      

    PARTS:
    Not16(in=x, out=notX);
    Not16(in=y, out=notY);

    Mux4Way16(a=x, b=notX, c=false, d=true, sel[0]=nx, sel[1]=zx, out=newX);
    Mux4Way16(a=y, b=notY, c=false, d=true, sel[0]=ny, sel[1]=zy, out=newY);
    
    Add16(a=newX, b=newY, out=addXY);
    And16(a=newX, b=newY, out=andXY);

    Mux16(a=andXY, b=addXY, sel=f, out=MuxOut);
    Not16(in=MuxOut, out=notMuxOut);
    
    Mux16(a=MuxOut, b=notMuxOut, sel=no, out=out, out[15]=ng, out[0..7]=left, out[8..15]=right);

    Or8Way(in=left, out=orWayOut1);
    Or8Way(in=right, out=orWayOut2);
    Or(a=orWayOut1, b=orWayOut2, out=orOut);
    Not(in=orOut, out=zr);
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>NandToTetris</category>
      <category>nandtotetris</category>
      <category>밑바닥부터 만드는 운영체제</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/87</guid>
      <comments>https://jaykos96.tistory.com/87#entry87comment</comments>
      <pubDate>Fri, 24 Jan 2025 22:41:07 +0900</pubDate>
    </item>
    <item>
      <title>운영시 유용한 Performance, Sys 쿼리 모음</title>
      <link>https://jaykos96.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DB에서 뭔가 Performance나 sys테이블 봐야할거같은데&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;(Real MySQL을 보고 공부한 내용을 다룹니다.)&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; MySQL에 접속했던 host들의 전체 목록을 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CURRENT_CONNECTIONS는 현재 연결된 커넥션 수, TOTAL_CONNECTIONS는 연결되었던 총 커넥션 수 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1736839712625&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM performance_schema.hosts&lt;/code&gt;&lt;/pre&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;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYHEFO/btsLMsxbzos/5IydCec5zGvkX082KRZRX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYHEFO/btsLMsxbzos/5IydCec5zGvkX082KRZRX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYHEFO/btsLMsxbzos/5IydCec5zGvkX082KRZRX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYHEFO%2FbtsLMsxbzos%2F5IydCec5zGvkX082KRZRX0%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;206&quot; data-origin-width=&quot;2126&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;MySQL 총 메모리 사용량 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736840035297&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM sys.memory_global_total&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lRZ63/btsLMZOHWpN/hDVa3r97WmKZhXKDe4DNVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lRZ63/btsLMZOHWpN/hDVa3r97WmKZhXKDe4DNVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lRZ63/btsLMZOHWpN/hDVa3r97WmKZhXKDe4DNVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlRZ63%2FbtsLMZOHWpN%2FhDVa3r97WmKZhXKDe4DNVk%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;433&quot; height=&quot;113&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;스레드별 메모리 사용량 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736840145811&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT thread_id, user, current_allocated FROM sys.memory_by_thread_by_current_bytes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mViPI/btsLN58cQak/RlGfqgX9uX3OP0TaqjiSpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mViPI/btsLN58cQak/RlGfqgX9uX3OP0TaqjiSpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mViPI/btsLN58cQak/RlGfqgX9uX3OP0TaqjiSpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmViPI%2FbtsLN58cQak%2FRlGfqgX9uX3OP0TaqjiSpK%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;1530&quot; height=&quot;526&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;특정 프로세스 ID의 메모리 사용내역 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736840306516&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT thread_id, event_name,
       sys.format_bytes(CURRENT_NUMBER_OF_BYTES_USED)
FROM
performance_schema.memory_summary_by_thread_by_event_name
WHERE thread_id = (SELECT sys.ps_thread_id(&amp;lt;프로세스ID&amp;gt;))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2074&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5zfcx/btsLN4VL0bR/C5QZXXfAX1HNKmMHYtRNGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5zfcx/btsLN4VL0bR/C5QZXXfAX1HNKmMHYtRNGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5zfcx/btsLN4VL0bR/C5QZXXfAX1HNKmMHYtRNGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5zfcx%2FbtsLN4VL0bR%2FC5QZXXfAX1HNKmMHYtRNGK%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;2074&quot; height=&quot;568&quot; data-origin-width=&quot;2074&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;미사용 인덱스 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736840512701&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM sys.schema_unused_indexes&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;중복된 인덱스 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736840587922&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM sys.schema_redundant_indexes&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;변경이 없는 테이블 확인 (구동된 시점부터 현재까지 쓰기가 발생하지 않은 테이블)&lt;/h3&gt;
&lt;pre id=&quot;code_1736840831976&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM information_schema.tables AS t
JOIN performance_schema.table_io_waits_summary_by_table AS tio
ON tio.OBJECT_SCHEMA = t.TABLE_SCHEMA AND tio.OBJECT_NAME = t.TABLE_NAME
WHERE t.TABLE_SCHEMA NOT IN ('mysql', 'performance_schema','sys')
AND tio.COUNT_WRITE = 0&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;I/O 요청이 많은 테이블 목록 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736840937007&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM sys.io_global_by_file_by_bytes WHERE file LIKE '%idb'&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;테이블별&amp;nbsp; 작업량 통계 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736841071301&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT table_schema,table_name, rows_updated, rows_inserted, rows_deleted, rows_inserted
        ,io_read, io_write
FROM
sys.schema_table_statistics
WHERE TABLE_SCHEMA NOT IN ('mysql', 'performance_schema','sys')&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;풀 테이블 스캔 쿼리 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736841224202&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT db, query, exec_count, sys.format_time(total_latency),
       rows_examined_avg, rows_sent_avg, last_seen
FROM sys.x$statements_with_full_table_scans&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;자주 실행되는 쿼리 목록 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736841300789&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT db, exec_count, query
FROM sys.statement_analysis
ORDER BY exec_count DESC&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;실행시간이 긴 쿼리 목록&lt;/h3&gt;
&lt;pre id=&quot;code_1736841422740&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT query, exec_count, sys.format_time(avg_latency)
     ,rows_sent_avg, rows_examined_avg, last_seen
FROM sys.x$statement_analysis
ORDER BY avg_latency DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;최근에 정렬 작업을 수행한 쿼리 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736841606456&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM
sys.statements_with_sorting
ORDER BY last_seen DESC LIMIT 1&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;최근에 임시테이블을 생성한 쿼리 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736841664053&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM
    sys.statements_with_temp_tables
LIMIT 10&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;트랜잭션이 활성 상태인 커넥션에서 실행한 쿼리 내역 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736842292540&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ps_t.PROCESSLIST_ID,
       ps_esh.THREAD_ID,
       CONCAT(ps_t.PROCESSLIST_USER,'@',ps_t.PROCESSLIST_HOST) as 'account',
       ps_esh.EVENT_NAME,
       ps_esh.SQL_TEXT,
       sys.format_time(ps_esh.TIMER_WAIT) AS 'duration',
       DATE_SUB(NOW(), INTERVAL (SELECT VARIABLE_VALUE FROM performance_schema.global_status
                                                       WHERE VARIABLE_NAME='UPTIME') - ps_esh.TIMER_START*10e-13 second) AS 'start_time',
       DATE_SUB(NOW(), INTERVAL (SELECT VARIABLE_VALUE FROM performance_schema.global_status
                                                       WHERE VARIABLE_NAME='UPTIME') - ps_esh.TIMER_END*10e-13 second) AS 'end_time'
FROM performance_schema.threads ps_t
INNER JOIN performance_schema.events_transactions_current ps_etc
ON ps_etc.THREAD_ID = ps_t.THREAD_ID
INNER JOIN performance_schema.events_statements_history ps_esh
ON ps_esh.NESTING_EVENT_ID = ps_etc.NESTING_EVENT_ID
WHERE ps_etc.STATE = 'ACTIVE'
AND ps_esh.MYSQL_ERRNO = 0
ORDER BY ps_t.PROCESSLIST_ID, ps_esh.TIMER_START&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;특정 세션에서 실행된 전체 쿼리 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1736945404404&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ps_t.PROCESSLIST_ID,
       ps_esh.THREAD_ID,
       CONCAT(ps_t.PROCESSLIST_USER,'@',ps_t.PROCESSLIST_HOST) as 'account',
       ps_esh.EVENT_NAME,
       ps_esh.SQL_TEXT,
       DATE_SUB(NOW(), INTERVAL (SELECT VARIABLE_VALUE FROM performance_schema.global_status
                                                       WHERE VARIABLE_NAME='UPTIME') - ps_esh.TIMER_START*10e-13 second) AS 'start_time',
        DATE_SUB(NOW(), INTERVAL (SELECT VARIABLE_VALUE FROM performance_schema.global_status
                                                       WHERE VARIABLE_NAME='UPTIME') - ps_esh.TIMER_END*10e-13 second) AS 'end_time',
       sys.FORMAT_TIME(ps_esh.TIMER_WAIT) AS 'duration'
FROM
performance_schema.events_statements_history ps_esh
INNER JOIN performance_schema.threads ps_t
ON ps_t.THREAD_ID = ps_esh.THREAD_ID
WHERE ps_t.PROCESSLIST_ID = &amp;lt;PROCESS_ID&amp;gt;
AND ps_esh.SQL_TEXT IS NOT NULL
AND ps_esh.MYSQL_ERRNO = 0
ORDER BY ps_esh.TIMER_START DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DB</category>
      <category>MySQL</category>
      <category>performance스키마</category>
      <category>운용 시 유용한 쿼리</category>
      <category>필수 스크립트</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/86</guid>
      <comments>https://jaykos96.tistory.com/86#entry86comment</comments>
      <pubDate>Tue, 14 Jan 2025 16:07:01 +0900</pubDate>
    </item>
    <item>
      <title>서비스중인 테이블을 안전하게 변경 ALTER 하는 방법</title>
      <link>https://jaykos96.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 운영하다보면 테이블을 변경해야 할 때가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호기롭게 DDL문을 실행했지만 변경하는 시간이 오래 걸리게되면 급격하게 불안해지는 저의 모습을 여러번 보았습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 스키마 변경 중에도 다른 커넥션에서 데이터를 정상적으로 조회 할 수 있도록 도와주는 온라인 DDL알고리즘과 해당 알고리즘을 적용해 테이블을 변경하는 방법들을 정리해보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Real MySQL 8.0을 보고 공부한 내용을 다룹니다.)&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;온라인 DDL 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마 변경 중에도 다른 커넥션에서 데이터를 정상적으로 조회 할 수 있도록 도와주는 MySQL의 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3가지의 알고리즘이 존재하며, ALTER TABLE 실행하면 MySQL은 INSTANT, INPLACE, COPY 순서대로 가능한 알고리즘을 선택하여 적용시킵니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;INSTANT&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테이블의 데이터는 변경하지 않고, 메타데이터만 변경하고 작업을 완료시킵니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마 변경 중 테이블의 읽기, 쓰기는 대기하지만 작업시간은 매우 짧아 다른 커넥션의 쿼리 처리는 거의 영향을 끼치지 않습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;INPLACE&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;임시테이블로 데이터를 복사하지 않고, 스키마 변경을 실행합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내부적으로 테이블 리빌드가 실행 될 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레코드의 복사작업은 없지만 테이블의 모든 데이터를 리빌드해야하기에 테이블 크기에 따라 많은 시간이 걸릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 작업중에도 테이블의 읽기 쓰기가 모두 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(최초 시작 시점과 종료시점에는 테이블 읽고 쓰기가 불가능 하지만 시간이 매우 짧아 다른 커넥션에서 큰 영향은 없습니다.)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;COPY&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경된 스키마를 적용한 임시테이블을 생성하고, 테이블의 레코드를 모두 임시 테이블로 복사 한 후 최종적으로 임시 테이블을 RENAME해서 스키마 변경을 완료합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 읽기만 가능하고 DML(INSERT, UPDATE, DELETE)는 불가능합니다.&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;DDL을 적용 할 떄 위에 3개의 알고리즘과 함께 잠금수준도 명시해서 실행하면 원하는 알고리즘으로 테이블 변경을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠금 수준도 동일하게 3가지 방법이 있습니다&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;NONE&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무런 잠금을 걸지 않습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SHARED&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 잠금을 걸고 스키마 변경을 실행합니다. (테이블 읽기는 가능, 쓰기작업 불가)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;EXCLUSIVE&lt;/h4&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;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDL아래 알고리즘과 LOCK을 명시해서 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 명시하지 않으면 MySQL이 적절한 알고리즘과 잠금수준을 적용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1736397014304&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE salaries CHANGE to_date, end_date DATE NOT NULL,
ALGORITHM=INPLACE, LOCK=NONE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;INPLACE 알고리즘의 테이블 리빌드란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INPLACE알고리즘에는 테이블 리빌딩이 일어날 수 있습니다.&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;(컬럼이름 변경작업 같은 경우에는 리빌드 X)&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;하지만 INPLACE알고리즘에서도 작업 시작, 끝 두 시점에는 X-Lock이 걸리게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;X-Lock이 걸렸던 시점에서 발생했던 DML들은 메모리의 온라인 변경 로그 (Oneline alter log)에 쌓기게 되는데&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;만약 온라인 변경 로그의 값이 너무 커지게 되면 온라인 DDL작업은 실패하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( innodb_online_alter_log_max_size 변수로 크기 조절 가능, 세션단위의 동적 변수임)&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;안전하게 테이블 변경 DDL 실행하는 방법&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. ALGORITHM=INSTANT 방식으로 실행&lt;/p&gt;
&lt;pre id=&quot;code_1736397917031&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE salaries CHANGE to_date, end_date DATE NOT NULL,
ALGORITHM=INSTANT&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ALGORITHM=INPLACE, LOCK=NONE 방식으로 실행&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736397899899&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE salaries CHANGE to_date, end_date DATE NOT NULL,
ALGORITHM=INPLACE, LOCK=NONE&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;각 작업별 온라인 DDL 알고리즘 처리 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서 테이블 변경시 처리되는 알고리즘 처리표입니다. (8.0.21 버전 기중)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRbqMx/btsLIo1EP7P/y1cxYxgP2rKqniYkNkatC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRbqMx/btsLIo1EP7P/y1cxYxgP2rKqniYkNkatC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRbqMx/btsLIo1EP7P/y1cxYxgP2rKqniYkNkatC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRbqMx%2FbtsLIo1EP7P%2Fy1cxYxgP2rKqniYkNkatC1%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;1590&quot; height=&quot;812&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhjLfi/btsLI5gf1XS/SWUWowjRuYLZGtmAaR7lTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhjLfi/btsLI5gf1XS/SWUWowjRuYLZGtmAaR7lTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhjLfi/btsLI5gf1XS/SWUWowjRuYLZGtmAaR7lTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhjLfi%2FbtsLI5gf1XS%2FSWUWowjRuYLZGtmAaR7lTK%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;1618&quot; height=&quot;694&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsJxE0/btsLHsDKOZM/gnw3IHtwjDKzZMPTZATAm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsJxE0/btsLHsDKOZM/gnw3IHtwjDKzZMPTZATAm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsJxE0/btsLHsDKOZM/gnw3IHtwjDKzZMPTZATAm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsJxE0%2FbtsLHsDKOZM%2Fgnw3IHtwjDKzZMPTZATAm1%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;1616&quot; height=&quot;488&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b88I0E/btsLIXidoxs/RTkkGyVx1oP0CEsG56QwK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b88I0E/btsLIXidoxs/RTkkGyVx1oP0CEsG56QwK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b88I0E/btsLIXidoxs/RTkkGyVx1oP0CEsG56QwK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb88I0E%2FbtsLIXidoxs%2FRTkkGyVx1oP0CEsG56QwK1%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;1620&quot; height=&quot;612&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u0y1b/btsLI4hlJRf/v6jqm9zvuLkKypsOKyZgu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u0y1b/btsLI4hlJRf/v6jqm9zvuLkKypsOKyZgu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u0y1b/btsLI4hlJRf/v6jqm9zvuLkKypsOKyZgu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu0y1b%2FbtsLI4hlJRf%2Fv6jqm9zvuLkKypsOKyZgu1%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;1612&quot; height=&quot;334&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYSrL/btsLIao8fmZ/7oOaxYqx4Gu74JIQAQr4Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYSrL/btsLIao8fmZ/7oOaxYqx4Gu74JIQAQr4Hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYSrL/btsLIao8fmZ/7oOaxYqx4Gu74JIQAQr4Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYSrL%2FbtsLIao8fmZ%2F7oOaxYqx4Gu74JIQAQr4Hk%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;1624&quot; height=&quot;754&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;1254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KnjRW/btsLH8ktVty/wHoX4pt4mMhFe4mDfcDov0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KnjRW/btsLH8ktVty/wHoX4pt4mMhFe4mDfcDov0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KnjRW/btsLH8ktVty/wHoX4pt4mMhFe4mDfcDov0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKnjRW%2FbtsLH8ktVty%2FwHoX4pt4mMhFe4mDfcDov0%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;1640&quot; height=&quot;1254&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;1254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DB</category>
      <category>MySQL</category>
      <category>안전하게 테이블 변경</category>
      <category>온라인 ddl</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/85</guid>
      <comments>https://jaykos96.tistory.com/85#entry85comment</comments>
      <pubDate>Thu, 9 Jan 2025 13:10:16 +0900</pubDate>
    </item>
    <item>
      <title>MySQL 실행계획 보는 방법</title>
      <link>https://jaykos96.tistory.com/84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DB에서 쿼리를 작성하면 EXPLAIN 또는 DESC 명령어를 통해 실행계획을 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 보다보면 가끔 잘 모르겠는 부분이 있어서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Real MySQL을 보며 공부한 부분을 정리하려합니다.&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;두 정보를 기반으로 어떤 인덱스를 선택할지 JOIN순서를 어떻게 정할지 결정하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통계정보는 디스크에 저장되며 테이블이 생성되거나 데이터가 변경될 때 통계정보가 추가 및 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;히스토그램은 자동으로 수집되지 않으며, 데이터 분포에 대한 통계정보를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;히스토그램은 아래와 같은 명령어로 생성 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1707308769688&quot; class=&quot;sql&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;ANALYZE TABLE 테이블명 UPDATE HISTOGRAM ON 칼럼명;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출력 포멧&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행계획은 테이블, TREE, JSON 3가지 형태로 출력 할 수 있습니다.&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;&lt;b&gt;테이블 출력&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735733166302&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXPLAIN SELECT 쿼리&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNDHEh/btsLAydt3RZ/XKkVvAwkMBNwdBXIvYRMek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNDHEh/btsLAydt3RZ/XKkVvAwkMBNwdBXIvYRMek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNDHEh/btsLAydt3RZ/XKkVvAwkMBNwdBXIvYRMek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNDHEh%2FbtsLAydt3RZ%2FXKkVvAwkMBNwdBXIvYRMek%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;1308&quot; height=&quot;852&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;852&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;size18&quot;&gt;&lt;b&gt;TREE 출력&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735733238052&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXPLAIN FORMAT=TREE SELECT 쿼리&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TQyRV/btsLCDLoDfE/ctGIGyBbtsjJXkDkY0EEEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TQyRV/btsLCDLoDfE/ctGIGyBbtsjJXkDkY0EEEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TQyRV/btsLCDLoDfE/ctGIGyBbtsjJXkDkY0EEEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTQyRV%2FbtsLCDLoDfE%2FctGIGyBbtsjJXkDkY0EEEK%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;1920&quot; height=&quot;1188&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;JSON 출력&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735733260970&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXPLAIN FORMAT=JSON SELECT 쿼리&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;1188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TNeUu/btsLDfJ5ehc/aB5FnUsDL1jn3kCK7xwpBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TNeUu/btsLDfJ5ehc/aB5FnUsDL1jn3kCK7xwpBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TNeUu/btsLDfJ5ehc/aB5FnUsDL1jn3kCK7xwpBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTNeUu%2FbtsLDfJ5ehc%2FaB5FnUsDL1jn3kCK7xwpBk%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;1918&quot; height=&quot;1188&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;1188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TREE FORMAT에서 쿼리의 단계별 실행시간 및 실행계획 읽는법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EXPALIN ANALYZE 명령어로 단계별 쿼리 시간을 확인 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 실제 실행계획은 아니며 MySQL이 가지고 있는 통계정보들을 가지고 예측한 결과를 보여줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1735733337301&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXPLAIN ANALYZE FORMAT=TREE SELECT o.order_id, u.name, p.name AS product_name, oi.quantity
FROM orders o
JOIN users u ON o.user_id = u.user_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE u.name = 'Alice';&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1735733400585&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-&amp;gt; Nested loop inner join  (cost=1.8 rows=1) (actual time=0.0732..0.0857 rows=2 loops=1)
    -&amp;gt; Nested loop inner join  (cost=1.45 rows=1) (actual time=0.0648..0.0736 rows=2 loops=1)
        -&amp;gt; Nested loop inner join  (cost=1.1 rows=1) (actual time=0.0495..0.055 rows=1 loops=1)
            -&amp;gt; Filter: (u.`name` = 'Alice')  (cost=0.75 rows=1) (actual time=0.0336..0.0378 rows=1 loops=1)
                -&amp;gt; Table scan on u  (cost=0.75 rows=5) (actual time=0.0306..0.0334 rows=5 loops=1)
            -&amp;gt; Covering index lookup on o using user_id (user_id=u.user_id)  (cost=0.35 rows=1) (actual time=0.0142..0.0153 rows=1 loops=1)
        -&amp;gt; Filter: (oi.product_id is not null)  (cost=0.35 rows=1) (actual time=0.0146..0.0175 rows=2 loops=1)
            -&amp;gt; Index lookup on oi using order_id (order_id=o.order_id)  (cost=0.35 rows=1) (actual time=0.0142..0.0167 rows=2 loops=1)
    -&amp;gt; Single-row index lookup on p using PRIMARY (product_id=oi.product_id)  (cost=0.35 rows=1) (actual time=0.00508..0.00517 rows=1 loops=2)&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;TREE 형태의 실행계획에서 들여쓰기는 아래와 같은 의미를 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;들여쓰기가 같은 레벨에서는 상단에 위치한 라인이 먼저 실행된다.&lt;/li&gt;
&lt;li&gt;들여쓰기가 다른 레벨에서는 가장 안쪽에 위치한 라인이 먼저 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 위와 같은 상황에서는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Filter:&amp;nbsp;(u.`name`&amp;nbsp;=&amp;nbsp;'Alice')&amp;nbsp;&amp;nbsp;(cost=0.75&amp;nbsp;rows=1)&amp;nbsp;(actual&amp;nbsp;time=0.0336..0.0378&amp;nbsp;rows=1&amp;nbsp;loops=1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- actual time은 테이블에서 레코드를 가져오는데 걸리는  시간이며, 첫번째 숫자는 첫번째 레코드, 두번째는 마지막 레코드를 가져오는데 걸린 평균시간입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- users 테이블에서 조건에 맞는 행을 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 5개의 행을 스캔했고 조건에 맞는 actual rows=1 조건을 만족하는 행은 1개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.Covering&amp;nbsp;index&amp;nbsp;lookup&amp;nbsp;on&amp;nbsp;o&amp;nbsp;using&amp;nbsp;user_id&amp;nbsp;(user_id=u.user_id)&amp;nbsp;&amp;nbsp;(cost=0.35&amp;nbsp;rows=1)&amp;nbsp;(actual&amp;nbsp;time=0.0142..0.0153&amp;nbsp;rows=1&amp;nbsp;loops=1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- u.user_id를 기반으로 orders 테이블에서 조건에 맞는(u.name=Alice)데이터를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- rows=1 매칭된 행 1개, 커버링 인덱스 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- loops=1 상위노드(users)에서 하위노드(orders)의 데이터를 읽어오는 작업이 한번 진행되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.Index&amp;nbsp;lookup&amp;nbsp;on&amp;nbsp;oi&amp;nbsp;using&amp;nbsp;order_id&amp;nbsp;(order_id=o.order_id)&amp;nbsp;&amp;nbsp;(cost=0.35&amp;nbsp;rows=1)&amp;nbsp;(actual&amp;nbsp;time=0.0142..0.0167&amp;nbsp;rows=2&amp;nbsp;loops=1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- o.order_id를 이용해 order_items 테이블에서 데이터를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- rows=2 매칭된 행 1개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- loops=1 상위노드(order_items)에서 하위노드(orders)의 데이터를 읽어오는 작업이 한번 진행되었습니다.&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;4.Single-row index lookup on p using PRIMARY (product_id=oi.product_id)&amp;nbsp;&amp;nbsp;(cost=0.35 rows=1) (actual time=0.00508..0.00517 rows=1 loops=2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- oi.product_id를 이용해 products 테이블에서 데이터를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- roops=2 상위노드(product)에서 하위노드(order_items)의 데이터를 읽어오는 작업이 한번 진행되었습니다.&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;EXPLAIN 컬럼들&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;id (SELECT의 단위)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT 쿼리별로 부여되는 식별자값 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 SELECT 문장은 하나의 ID값을 가지며, JOIN된 테이블들은 하나의 ID값을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 아래와 같이 서브쿼리를 사용하면 다른 ID값을 가진 실행계획을 찾을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2034&quot; data-origin-height=&quot;1082&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdK9ib/btsLBsjOLDh/xFH754J2baadoRb17gOoo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdK9ib/btsLBsjOLDh/xFH754J2baadoRb17gOoo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdK9ib/btsLBsjOLDh/xFH754J2baadoRb17gOoo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdK9ib%2FbtsLBsjOLDh%2FxFH754J2baadoRb17gOoo0%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;2034&quot; height=&quot;1082&quot; data-origin-width=&quot;2034&quot; data-origin-height=&quot;1082&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;select_type&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT 쿼리에 대한 타입입니다.&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;&lt;b&gt;SIMPLE&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNION이나 서브쿼리를 사용하지 않는 단순한 SELECT쿼리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 가장 바깥의 문장이 SIMPLE입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;PRIMARY&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNION이나 서브쿼리를 가지는 실행계획에서 가장 바깥쪽 문장의 타입입니다.&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;&lt;b&gt;DERIVED&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쿼리의 실행결과로 메모리와 디스크에 임시테이블을 만드는 작업을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;UNION&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNION으로 결합되는 쿼리 중 첫번 째 이후 쿼리에 대한 타입입니다. (첫번째는 DERIVED)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째 테이블은 4개의 서브쿼리로 조회된 결과를 UNION ALL로 결합해 임시테이블을 이용해 사용하고 있기에 DERIVED 타입을 가지게됩니다.&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;추가로 UNION이나 UNION ALL로 결합된 쿼리가 외부쿼리에 의해 영향을 받으면 DEPENDENT 타입(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;DEPENDENT SUBQUERY,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;DEPENDENT UNION)이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DEPENDENT의 경우에는 외부쿼리가 먼저 실행되고 내부쿼리가 실행되어야 하기에 일반 서브쿼리보다는 느립니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b255cI/btsLDIzeb5u/5RoTCU6rNUPKlKyczhZUG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b255cI/btsLDIzeb5u/5RoTCU6rNUPKlKyczhZUG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b255cI/btsLDIzeb5u/5RoTCU6rNUPKlKyczhZUG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb255cI%2FbtsLDIzeb5u%2F5RoTCU6rNUPKlKyczhZUG1%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;1388&quot; height=&quot;1014&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;1014&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;size18&quot;&gt;&lt;b&gt;SUBQUERY&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT TYPE에서 서브쿼리는 FROM절 이외에서 사용되는 서브쿼리만을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FROM절에서 사용되는 서브쿼리는 DERIVED로 표시됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3EJlk/btsLCqsN56e/iklsI6iHRodA3Or7kjiKu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3EJlk/btsLCqsN56e/iklsI6iHRodA3Or7kjiKu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3EJlk/btsLCqsN56e/iklsI6iHRodA3Or7kjiKu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3EJlk%2FbtsLCqsN56e%2FiklsI6iHRodA3Or7kjiKu0%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;1220&quot; height=&quot;654&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;654&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;MATERIALZED&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 FROM절이나 IN형태의 서브쿼리에 사용된 서브쿼리의 최적화를 위해 사용합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브쿼리의 내용을 임시로 구체화 한 후 임시테이블을 다른 테이블과 조인하는 형태로 최적화합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;TABLE 컬럼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용된 TABLE을 표시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;derived숫자&amp;gt;, &amp;lt;union M,N&amp;gt;과 같은 컬럼들은 임시테이블을 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시와 같은 쿼리에서 &amp;lt;derived3&amp;gt;이라고 되어있는데 여기서 3은 SELECT쿼리의 id값이 3인 실행계획으로 부터 만들어진 파상태이블임을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 id:1 번은 실행되기 위해 id:3번인 users 테이블을 읽어서 만들어진 임시테이블이라는 의미입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEfLyH/btsLC6USYQ3/KFTk4Hn6v9Cr1QKW1Ts0E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEfLyH/btsLC6USYQ3/KFTk4Hn6v9Cr1QKW1Ts0E1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEfLyH/btsLC6USYQ3/KFTk4Hn6v9Cr1QKW1Ts0E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEfLyH%2FbtsLC6USYQ3%2FKFTk4Hn6v9Cr1QKW1Ts0E1%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;868&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PARTITIONS 컬럼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT에서 조회할 때 파티션을 통해 조회했다면 해당 파티션을 확인 할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TYPE 컬럼 (JOIN TYPE)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 테이블의 레코드를 어떤 방식으로 읽었는지 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CONST&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pk나 유니크 키를 사용하는 WHERE 조건절을 가지고있으며, 반드시 1건을 반환하는 방식입니다.&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;그래서 const타입이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pk조건 중 일부만 사용할때는 const가 아닌 ref방식으로 읽습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI8gW4/btsLEXWAIl2/fzjKU2a7MDqDlKnijWkOj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI8gW4/btsLEXWAIl2/fzjKU2a7MDqDlKnijWkOj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI8gW4/btsLEXWAIl2/fzjKU2a7MDqDlKnijWkOj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI8gW4%2FbtsLEXWAIl2%2FfzjKU2a7MDqDlKnijWkOj0%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;1090&quot; height=&quot;564&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;564&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;size18&quot;&gt;&lt;b&gt;eq_ref&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;join에서 처음 읽은 테이블의 컬럼값을 다른 테이블의 pk나 유니크 키 조건에 사용 할 때를 eq_ref라고합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 처음 읽은 테이블이 아닌 두번째 테이블의 type에 eq_ref라고 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 테이블의 조건을 통해 읽는 두번째 테이블의 데이터가 반드시 유니크해야 eq_ref방식을 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFo1oj/btsLCJliRzp/pmI5jOTf4LkRcmget43Kxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFo1oj/btsLCJliRzp/pmI5jOTf4LkRcmget43Kxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFo1oj/btsLCJliRzp/pmI5jOTf4LkRcmget43Kxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFo1oj%2FbtsLCJliRzp%2FpmI5jOTf4LkRcmget43Kxk%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;1340&quot; height=&quot;756&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;756&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;size18&quot;&gt;&lt;b&gt;ref, ref_or_null&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스의 종류와 상관없이 동등(Equal)조건으로 검색할 때에는 ref방식이 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eq_ref와 다르게 유니크한 값일 필요가 없으므로 const나 eq_ref보다는 느리지만 여전히 빠른 접근방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null조건도 함께 검색한다면 ref_or_null 방식이 사용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brFh7j/btsLEgvyHCF/jvek2cFkRpmzhazl726Cwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brFh7j/btsLEgvyHCF/jvek2cFkRpmzhazl726Cwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brFh7j/btsLEgvyHCF/jvek2cFkRpmzhazl726Cwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrFh7j%2FbtsLEgvyHCF%2Fjvek2cFkRpmzhazl726Cwk%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;1094&quot; height=&quot;566&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;566&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;size18&quot;&gt;&lt;b&gt;unique_subquery, &lt;b&gt;index_subquery&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 where조건절에서 사용되는 IN 형태의 쿼리를 위한 접근방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;unique_subquery는 서브쿼리에서 중복되지 않는 유니크한 값만 반환 할 때 사용되는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index_subquery는 서브쿼리에서 중복된 결과를 인덱스를 통해 제거 할 수 있을 떄 사용되는 방법입니다.&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;&lt;b&gt;range&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 레인지 스캔을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 어플리케이션 쿼리가 가장 많이 사용하게되는 방법입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbQnl2/btsLErjBqa0/KqdADll0jax6MAPJu1KOh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbQnl2/btsLErjBqa0/KqdADll0jax6MAPJu1KOh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbQnl2/btsLErjBqa0/KqdADll0jax6MAPJu1KOh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbQnl2%2FbtsLErjBqa0%2FKqdADll0jax6MAPJu1KOh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;578&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;578&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;size18&quot;&gt;&lt;b&gt;index_merge&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개 이상의 인덱스를 이용하여 각각 검색결과를 만들어낸 후, 그 결과를 병합해서 처리하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pk컬럼과 인덱스 컬럼을 or 조건처럼 각각 조회하게되는 쿼리일때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL은 pk를 통해 데이터를 찾고, 인덱스를 통해 데이터를 찾은 후 두 결과를 병합하는 형태로 처리합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eF4QGv/btsLDMV5m6b/cV5KiVjToVAJf6qLW3may0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eF4QGv/btsLDMV5m6b/cV5KiVjToVAJf6qLW3may0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eF4QGv/btsLDMV5m6b/cV5KiVjToVAJf6qLW3may0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeF4QGv%2FbtsLDMV5m6b%2FcV5KiVjToVAJf6qLW3may0%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;1092&quot; height=&quot;560&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;560&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;size18&quot;&gt;&lt;b&gt;index&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;아래 방법 중 1+2, 1+3과 같은 상황일 때 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. range나 const, ref와 같은 접근방법으로 인덱스를 사용하지 못하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 인덱스에 포함된 컬럼만으로 처리 할 수 있는 컬럼일 경우&amp;nbsp;&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;1024&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ELASL/btsLDmJ6NMl/TyucGcqF9o73BkhmwqeZyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ELASL/btsLDmJ6NMl/TyucGcqF9o73BkhmwqeZyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ELASL/btsLDmJ6NMl/TyucGcqF9o73BkhmwqeZyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FELASL%2FbtsLDmJ6NMl%2FTyucGcqF9o73BkhmwqeZyk%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;1024&quot; height=&quot;500&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;500&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;size18&quot;&gt;&lt;b&gt;ALL&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 풀 스캔을 의미합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xli70/btsLEfDtwTf/wOg31KjgWaTk0Gz1eRb1qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xli70/btsLEfDtwTf/wOg31KjgWaTk0Gz1eRb1qK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xli70/btsLEfDtwTf/wOg31KjgWaTk0Gz1eRb1qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXli70%2FbtsLEfDtwTf%2FwOg31KjgWaTk0Gz1eRb1qK%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;960&quot; height=&quot;506&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;possible_keys 컬럼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저가 실행계획을 만들 때 후보로 선정했던 즉 실제 사용되지 않았던 인덱스를 의미합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;key 컬럼&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;옵티마이저가 실제 사용했던 인덱스를 의미합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;key의 값이 primary라면 pk를 사용한다는 의미이고, 이 외의 값은 테이블이나 인덱스를 만들었을 때의 그 고유이름이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실행계획이 ALL이라면 key컬럼은 NULL로 표시됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bggH8J/btsLCDyY0qF/bKedNnaddxACgr0Mr29cV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bggH8J/btsLCDyY0qF/bKedNnaddxACgr0Mr29cV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bggH8J/btsLCDyY0qF/bKedNnaddxACgr0Mr29cV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbggH8J%2FbtsLCDyY0qF%2FbKedNnaddxACgr0Mr29cV0%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;980&quot; height=&quot;526&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;key_lens 컬럼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 처리하기 위해 사용했던 인덱스에서 몇개의 인덱스까지 사용되었는지 byte의 길이로 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name은 varchar(50)이기에 한 문자당 3바이트, 즉 200바이트에 문자열의 길이를 저장하기 위한 1바이트, NULL 허용하기에 1바이트,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key_lens컬럼에서 바이트를 정수로 표현하기 위한 1바이트 총 203바이트가 표시됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qwPig/btsLC4XfDcE/1tjKMaZHmkomNqfVRWgySk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qwPig/btsLC4XfDcE/1tjKMaZHmkomNqfVRWgySk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qwPig/btsLC4XfDcE/1tjKMaZHmkomNqfVRWgySk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqwPig%2FbtsLC4XfDcE%2F1tjKMaZHmkomNqfVRWgySk%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;1208&quot; height=&quot;512&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;ref 컬럼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조조건으로 어떤값이 제공되었는지 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수값이라면 const, 다른 테이블의 컬럼값이라면 그 테이블과 컬럼명이 표시됩니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;rows 컬럼&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt; filtered 컬럼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 엔진에서 가져온 데이터 중 filterd% 만큼이 쿼리조건에 만족한다는 의미입니다. (MySQL엔진에서 처리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 상황에서는 ix_firstname 인덱스 조건에 일치하는 레코드는 233건이고 그 중 쿼리조건에 맞는 데이터는 16.03%라는 의미입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xe5A7/btsLEeEKBUd/g7ihyuT5St047tjjGRHg20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xe5A7/btsLEeEKBUd/g7ihyuT5St047tjjGRHg20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xe5A7/btsLEeEKBUd/g7ihyuT5St047tjjGRHg20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXe5A7%2FbtsLEeEKBUd%2Fg7ihyuT5St047tjjGRHg20%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;754&quot; height=&quot;236&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EXTRA 컬럼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 관련하여 중요한 내용들이 표시되는 컬럼입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;const row not found&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const조건으로 읽었지만 해당 레코드가 1건도 없을 때 나타납니다.&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;&lt;b&gt;Distinct&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;distinct, union등이 사용된 쿼리에서 나타납니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 두 테이블 전체에서 유니크한 user_id를 가져려는 쿼리입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;EXTRA 컬럼에서 users테이블에서는 distinct가 표시되었는데 해당 테이블에서 중복이 제거되어 필요한 유니크한 값들만을 찾았다는 의미입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1988&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nA0WP/btsLEMgSvg5/46zAFGmUJzQ5xZ2yMxwBQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nA0WP/btsLEMgSvg5/46zAFGmUJzQ5xZ2yMxwBQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nA0WP/btsLEMgSvg5/46zAFGmUJzQ5xZ2yMxwBQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnA0WP%2FbtsLEMgSvg5%2F46zAFGmUJzQ5xZ2yMxwBQ0%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;1988&quot; height=&quot;654&quot; data-origin-width=&quot;1988&quot; data-origin-height=&quot;654&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;size18&quot;&gt;&lt;b&gt;Full scan on NULL key&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리에서 컬럼의 값이 NULL인 경우를 찾을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어  col1 in (SELECT name FROM USER)와 같은 상황에서 나타날 수 있는 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 col1이 NULL이게 된다면 그때는 서브쿼리테이블에 대해서 풀 테이블 스캔을 한다는 내용입니다.&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;&lt;b&gt; impossible having, impossible where&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성한 having, where조건이 무조건 FALSE일 때 나타나는 내용입니다.&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;&lt;b&gt; impossible having, impossible where&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;작성한 having, where조건이 무조건 FALSE일 때 나타나는 내용입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; No matching min/max row&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;집합함수에 일치하는 레코드가 없다는 내용입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; No matching  row in const table&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;조인에 사용된 테이블에서 const방법으로 접근할때 일치하는 레코드가 없을 때 나타납니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Using filesort&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Order by로 처리되지 못하고 filesort방식으로 정렬했을때 나타납니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Using index (커버링인덱스)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인덱스만 읽어서 모든 쿼리를 처리 할 수 있을 때 사용됩니다. (SELECT에서 사용하는 컬럼이 모두 인덱스의 컬럼일 때)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Using temporary&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;임시테이블을 사용했을때 나타납니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Using  where&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스토리지에서 가져온 데이터를 MySQL 엔진에서 별도의 작업을 했을 때 나타납니다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스토리지 엔진에서 100건의 데이터를 가져왔다면 filtere처리로 데이터가 MySQL엔진에서 필터링되었다면 Using where조건이 나타납니다.&lt;/p&gt;</description>
      <category>DB</category>
      <category>explain</category>
      <category>mysql 실행계획</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/84</guid>
      <comments>https://jaykos96.tistory.com/84#entry84comment</comments>
      <pubDate>Mon, 30 Dec 2024 22:06:40 +0900</pubDate>
    </item>
    <item>
      <title>MySQL 옵티마이저가 하는 일</title>
      <link>https://jaykos96.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;웹서비스를 개발하다 보면 실제 운영서버에 반영하기 전에 만들어둔 쿼리에 대한 테스트 및 성능확인이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 EXPLAIN 명령어를 통해 쿼리의 성능을 평가했는데, 그냥 인덱스를 잘 타는구나~ 이 정도만 확인했던 기억이 있습니다.&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;MySQL 옵티마이저에 대해 공부 및 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Real MySQL을 보고 공부한 내용을 다룹니다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼리의 실행 절차&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. SQL 파싱&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자로 부터 쿼리요청이 들어오면 'SQL 파서 모듈'에서 문장을 적절하게 쪼개어 MySQL서버가 이해할 수 있는 수준으로 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(파싱 트리 작업)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 최적화 및 실행계획 수립 (옵티마이저 일하는 단계)&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;임시테이블 필요유무도 이 단계에서 결정됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 레코드 읽기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정된 인덱스를 사용해 스토리지 엔진으로 부터 데이터를 읽어옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;옵티마이저의 기본적인 데이터 처리 방식&lt;/h2&gt;
&lt;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;b&gt;풀 테이블 스캔과 풀 인덱스 스캔&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블의 레코드 수가 적어서 인덱스보다 풀 테이블 스캔이 더 빠를 때 ( 보통 테이블이 페이지 1개로 구성되어 있는 경우 )&lt;/li&gt;
&lt;li&gt;WHERE나 ON 조건에서 인덱스를 이용할 수 없을 때&lt;/li&gt;
&lt;li&gt;인덱스 레인지 스캔을 하더라도 조건 일치 레코드가 너무 많을 때&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;백그라운드 스레드의 작업 시점부터는 점진적으로 읽어오는 페이지 수를 4개 8개.. 점점 증가시켜 최대 64개 까지 버퍼풀에 한번에 저장해둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 InnoDB엔진은 특정 테이블의 데이터가 연속해서 읽히면 백그라운드 스레드에서 앞으로 필요할 것 같은 데이터들을 예상해서 미리 디스크에서 버퍼풀로 데이터를 가져다 두고 이를 리드 어헤드 작업이라고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;임시테이블 (Internal Temporary Table)에 대해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL엔진이 스토리지 엔진으로 부터 데이터를 정렬(ORDER BY)하거나 그룹핑(GROUP BY)할 때 내부적인 임시 테이블을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;임시테이블은 최초 메모리에 생성되었다가 테이블의 크기가 커지면 디스크로 옮겨지며, 쿼리의 처리가 완료되면 자동으로 삭제됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 생성되는 임시 테이블은 TempTable (기본값 1GB)이라는 스토리지 엔진을 사용하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에 저장되는 임시 테이블은 InnoDB스토리지 엔진을 사용합니다.&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;Created_tmp_disk_tables : 디스크에 존재하는 임시 테이블 수&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Created_tmp_files : 임시 테이블 생성 횟수&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Created_tmp_tables : 디스크 + 메모리에 존재하는 임시 테이블 수&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734872838677&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SHOW SESSION STATUS LIKE 'Created_tmp%';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JuTit/btsLrdUmkuK/DlJcrVVaJTLrTRXQbZ34wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JuTit/btsLrdUmkuK/DlJcrVVaJTLrTRXQbZ34wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JuTit/btsLrdUmkuK/DlJcrVVaJTLrTRXQbZ34wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJuTit%2FbtsLrdUmkuK%2FDlJcrVVaJTLrTRXQbZ34wk%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;261&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;쿼리에서 임시테이블이 사용되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ORDER BY와 GROUP BY에 명시된 컬럼이 다른 쿼리 (임시 유니크 인덱스 사용)&lt;/li&gt;
&lt;li&gt;ORDER BY나 GROUP BY에 명시된 컬럼이 JOIN의 순서 상 첫번째 테이블이 아닌 쿼리 (임시 유니크 인덱스 사용)&lt;/li&gt;
&lt;li&gt;DINSTINCT나 ORDER BY가 동시에 있거나  DISTINCT가 인덱스로 처리되지 못하는 쿼리 (임시 유니크 인덱스 사용)&lt;/li&gt;
&lt;li&gt;UNION, UNION DISTINCT가 사용된 쿼리 (select_type 컬럼이 UNION RESULT인 경우) (임시 유니크 인덱스 사용)&lt;/li&gt;
&lt;li&gt;쿼리의 실행 계획에서 select_type이 DERIVED인 쿼리 (임시 유니크 인덱스 사용 X)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;임시 테이블은 메모리에 먼저 생성되지만 예외적으로 바로 디스크에 생성되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UNION이나 UNION ALL에서 SELECT되는 컬럼 중 길이가 512바이트 이상인 컬럼이 있는 경우&lt;/li&gt;
&lt;li&gt;GROUP BY, DISTINCT 컬럼 중 길이가 512바이트 이상인 컬럼이 있는 경우&lt;/li&gt;
&lt;li&gt;메모리 임시 테이블의 크기가 temporable_max_ram 변수보다 큰 경우&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;&lt;b&gt;옵티마이저와 ORDER BY 처리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EXPLAIN 명령어로 실행 계획을 확인하다 보면 EXTRA 컬럼에&amp;nbsp; using filesort라는 문구를 종종 확인 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 filesort 방식으로 정렬처리가 되었다는 뜻인데, 즉 인덱스를 타지 않는 방식으로 정렬된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저는 크게 3가지 방법으로 정렬을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 ORDER BY에서 인덱스를 사용하게 되면 MySQL에서 '스트리밍 방식'으로 데이터를 처리하여 빠른 응답이 가능하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 사용하지 않는 경우에는 '버퍼 방식'으로 데이터를 전달하기에 속도에 차이가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(하지만 JDBC는 기본적으로 '버퍼 방식'을 사용하기에 MySQL에서 스트리밍 방식으로 전달해주어도 JDBC를 통해 응답받게 되면 결국 '버퍼 방식'입니다.)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 인덱스를 이용한 처리&lt;/b&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;EXTRA 컬럼에 별도 내용이 표기되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인덱스를 통해 읽는 경우에는 실제 인덱스의 값이 정렬되어 있기에 순서대로 데이터를 읽어오면 됩니다. (정렬을 위한 별도 작업 X)&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;B-Tree인덱스에서만 유효하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ORDER BY에 명시된 컬럼이 드라이빙 테이블에 속하고, 인덱스가 생성된 순서대로 ORDER BY의 정렬조건에 있어야합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;또한 WHERE조건의 첫번째 컬럼과 ORDER BY는 같은 인덱스를 탈 수 있어야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 테이블이 조인되는 상황에서는 Nested-Loop 방식에서만 유효합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Using filesort를 이용한 처리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filesort 방식은 정렬 작업이 쿼리 실행 시 실행되므로 레코드수가 적으면 인덱스 정렬보다는 빠르지만&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;filesort 방식을 사용하는 시점은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정렬 기준이 많아서 모든 요건별 인덱스 생성이 불가능한 경우&lt;/li&gt;
&lt;li&gt;GROUP BY의 결과나 DISTINCT 처리결과를 정렬해야하는 경우&lt;/li&gt;
&lt;li&gt;임시테이블의 결과를 정렬해야하는 경우 (union등)&lt;/li&gt;
&lt;li&gt;랜덤하게 레코드를 가져와야하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL은 정렬 시 정렬 버퍼(sort buffer)라는 메모리 공간을 사용하는데,&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;(이 작업을 Multi Merge라고 하며 이 횟수는 Sort_merge_passes 변수에 기록되며 SHOW STATUS VARIABLE 명령어로 확인 가능합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;filesort 시 옵티마이저가 사용하는 정렬 방식&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. sort_key, row_id (투 패스 방식)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정렬하는 key와 row_id만 가져와서 정렬하는 방식입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라이언트 쿼리에서 정렬key와 row_id 외 다른 컬럼이 있다면 정렬 후 추가로 필요한 컬럼을 다시 디스크에서 가져와야하기에 추가 작업이 필요합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;BLOB이나 TEXT 컬럼이 있다면 MySQL은 반드시 해당 방식으로 정렬합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정렬할 레코드가 적다면 효율적인 방식입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. sort_key, additional_fields (싱글 패스 방식)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정렬하는 key와 레코드 전체를 가져와서 정렬하는 방식입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;레코드의 컬럼들은 고정사이즈로 메모리에 저장됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정렬할 레코드가 많다면 효율적인 방식입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. sort_key, packed_additional_fields (싱글 패스 방식)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정렬하는 key와 레코드 전체를 가져와서 정렬하는 방식입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;레코드의 컬럼들은 가변사이즈로 메모리에 저장됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;옵티마이저에서 EXTRA 컬럼에 나타나는 file sort 방식은 크게 2가지로 구분됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-1. Using filesort (조인에서 드라이빙 테이블만 정렬)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;드라이빙 테이블의 컬럼만으로 ORDER BY 절의 조건 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이빙 테이블만으로 ORDER BY에 조건을 주게되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 드라이빙 테이블에서 데이터를 가져온 뒤 검색결과를 ORDER BY의 조건을 기준으로 filesort합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-2. Using temporary; Using filesort (조인에서 조인결과를 임시테이블로 저장 후 정렬)&lt;/b&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리에 따라서 join된 테이블들이 임시 테이블에 저장될 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에 쿼리 결과를 보여주기 위해 정렬 할 때 그 임시테이블을 정렬해야하기에 가장 느린 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시테이블에 저정된 데이터를 정렬하게 될 때 Using tempoary가 사용됩니다.&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;마지막으로 아래와 같은 명령어를 통해 정렬 관련한 MySQL의 상태를 확인 할 수 있습니다&lt;/p&gt;
&lt;pre id=&quot;code_1734869284877&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SHOW STATUS LIKE 'Sort%';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;옵티마이저와 GROUP BY 처리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY또한 ORDER BY와 같이 인덱스를 사용할수도 사용하지 못할수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하지 못한다면 임시 테이블을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(ORDER BY와 동일하게 인덱스를 사용하면 스트리밍 방식, 사용하지 않으면 버퍼 방식입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 HAVING 에 사용된 조건절은 인덱스를 탈 수 없습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인덱스 스캔을 이용하는 GROUP BY (타이트 인덱스 스캔)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조인의 드라이빙 테이블에 속한 컬럼만을 조건으로 GROUP BY 조건을 주었을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY가 인덱스를 통해 처리되어도 그룹 함수 등의 그룹값을 처리해야 할 때에는 임시테이블이 필요할때도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY와 동일하게 인덱스를 통해 처리되면 EXTRA 컬럼에 별도의 문구가 뜨지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;루스 인덱스 스캔(Loose index scan)을 이용하는 GROUP BY&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 테이블에서만 수행되는 GROUP BY에서만 적용됩니다.&lt;/li&gt;
&lt;li&gt;Prefix Index (컬럼 앞쪽 일부만 사용하는 인덱스)에서는 사용이 불가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loose index scan은 인덱스의 레코드를 건너뛰며 필요한 레코드들을 가져오는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분포도가 좋지 않을수록 좋은 성능을 낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 EXTRA컬럼에 Using index for group-by 라고 표시됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;임시 테이블을 이용하는 GROUP BY&lt;/b&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EXTRA 컬럼에 Using Temporary라는 문구가 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서는 GROUP BY가 필요할 때 GROUP BY절의 컬럼들로 구성된 유니크 인덱스를 가진 임시 테이블을 만들어 중복제거와 집합함수 연산을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 join의 결과를 가져와 한건씩 임시테이블에서 중복체크를 하며 별도의 정렬작업 없이 INSERT, UPDATE 작업을 진행 후 반환합니다.&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;DINSTINCT 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DISTINCT는 임시테이블과 함께 사용될 때에도 Using Temporary 문구가 나오지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 SELECT에서 유니크한 레코드만 가져오려고 할 때 DISTINCT를 사용하면 GROUP BY와 동일한 방식을 통해 MySQL에서 데이터를 가져옵니다.&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;아래와 같이 DISTINCT를 사용하면 user_id만 DISTINCT되서 나오지 않고 user_id, user_name SELECT절의 모든 컬럼에 대해서 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DISTINCT는 함수가 아니기에 뒤에 괄호를 자체적으로 제거합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734871245751&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT DISTINCT(user_id), user_name FROM EMP&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아래와 같이 집합함수 내에서 사용한다면 해당 컬럼에 대해서만 적용됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734871459069&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(DISTINCT user_id) FROM EMP&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;조인 버퍼 (Join Buffer)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;옵티마이저에서 Join을 사용할 때 어떠한 방식으로도 Full Table Scan이나 Index Full Scan을 피할 수 없다면&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;드라이빙 테이블에서 읽은 레코드를 메모리에 캐시한 후 드리븐 테이블과 캐시한 메모리를 Join하는 형태로 처리합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 사용하는 메모리 캐시를 조인 버퍼(Join Buffer)라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인 버퍼는 사용후 바로 해제됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 MySQL구조에서 조인처리는 MySQL엔진에서 처리하지만 실제 데이터를 읽는곳은 스토리지 엔진입니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Nested Loop Join (조인 버퍼 사용 X)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nested Loop Join은 드라이빙 테이블의 레코드를 한건 읽어서 드리븐 테이블의 일치하는 레코드들을 찾아서 조인을 수행하는 방식입니다. (일반적인 2중 for문 방식이라고 생각하시면 편할 것 같습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Join을 위한 인덱스나 정렬과 같은 부가적인 기능을 따로 사용하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Block Nested Loop Join (조인 버퍼 사용 O)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서 사용되는 대부분의 Join방식은 Block Nested Loop Join입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Join의 연결이 되는 조건 컬럼에 인덱스가 있는 경우 사용되는 방식입니다.&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;MySQL은 조인 대상 테이블 중 하나(드라이빙 테이블 혹은 레코드가 작은 테이블)로부터 레코드를 읽어서 그대로 조인버퍼에 버퍼링합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉 드라이빙 테이블의 레코드를 읽어서 바로 join하지 않고 조인버퍼에 조인 대상을 버퍼링을 해둡니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;조인버퍼가 가득 차면 그 때 MySQL엔진은 버퍼링된 레코드를 스토리지엔진으로 한번에 요청합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;EXTRA 컬럼을 통해 Join방식을 확인 할 수 있습니다 (8.0.20 버전 부터는 Block Nested Loop Join이 사용되지 않고 Hash Join 방식이 사용되어 block nested loop 방식 및 EXTRA 컬럼에 해당 방식이 나오지 않을 수 있습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1502&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uijwv/btsLAinp54G/6d0QvMqy2jskQOqs5B9g9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uijwv/btsLAinp54G/6d0QvMqy2jskQOqs5B9g9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uijwv/btsLAinp54G/6d0QvMqy2jskQOqs5B9g9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuijwv%2FbtsLAinp54G%2F6d0QvMqy2jskQOqs5B9g9K%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;1502&quot; height=&quot;344&quot; data-origin-width=&quot;1502&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 머지 (index_merge)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 옵티마이저는 테이블당 하나의 인덱스만 사용하도록 실행계획을 수립합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 종종 하나의 테이블에 대해 2개 이상의 인덱스를 사용해서 쿼리를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리에 사용된 각 조건이 서로다른 인덱스를 사용 할 수 있고, 그 조건을 만족하는 레코드가 많을것으로 예상될 때 MySQL서버는 인덱스 머지를 사용합니다.&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;Index_merge_intersection (교집합)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EXTRA 컬럼에 Using intersect 가 표시된다면 '인덱스 머지 교집합'이 사용된겁니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF3gpk/btsLyt4nUdo/WC4tqjLeCIyyNuW8FGkztK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF3gpk/btsLyt4nUdo/WC4tqjLeCIyyNuW8FGkztK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF3gpk/btsLyt4nUdo/WC4tqjLeCIyyNuW8FGkztK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF3gpk%2FbtsLyt4nUdo%2FWC4tqjLeCIyyNuW8FGkztK%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;1564&quot; height=&quot;274&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Index_merge_union (합집합)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE절에 사용된 2개 이상의 조건이 각각의 인덱스를 사용하되 OR연산자로 연결된 경우에 사용하는 최적화 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 조건에서 사용할 수 있는 인덱스의 각각의 검색결과를 Union 알고리즘으로 병합했다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 인덱스에서 중복된 레코드가 가져와 질 수 있는데 테이블의 PK와 우선순위큐를 이용해서 데이터를 병합하기에 별도의 중복 제거 작업은 하지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WwIJY/btsLBMuajwk/j7A3sU7OUvOwpNTo6fLLv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WwIJY/btsLBMuajwk/j7A3sU7OUvOwpNTo6fLLv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WwIJY/btsLBMuajwk/j7A3sU7OUvOwpNTo6fLLv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWwIJY%2FbtsLBMuajwk%2Fj7A3sU7OUvOwpNTo6fLLv1%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;1566&quot; height=&quot;312&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Index_merge_sort_union (정렬 후 합집합)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 머지 작업 중 정렬이 필요한 경우는 반드시 존재합니다. (pk로 필터링 불가 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 경우에는 중복 제거를 위해 정렬을 해야하는데 Index_merge_sort_union 방식의 sort_union 알고리즘을 사용하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EXTRA 컬럼에 sort_union 방식이 사용되었다고 표기됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWCiPZ/btsLAVSSn9I/0jYmuddfLDQRPfPGuZR2Y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWCiPZ/btsLAVSSn9I/0jYmuddfLDQRPfPGuZR2Y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWCiPZ/btsLAVSSn9I/0jYmuddfLDQRPfPGuZR2Y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWCiPZ%2FbtsLAVSSn9I%2F0jYmuddfLDQRPfPGuZR2Y0%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;1540&quot; height=&quot;294&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세미 조인 (semijoin)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 join을 수행하지 않고, 다른 테이블에서 조건에 일치하는 레코드의 유무를 체크하는 형태의 쿼리를 의미합니다.&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;아래와 같은 쿼리에서 원래 MySQL은 employees 테이블을 조회하고 그 후 조건에 있는 IN절의 결과와 비교해서 데이터를 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로 데이터를 조회 할 때에는 semijoin 최적화 기능으로 인하여 IN 절의 조건 dept_emp 테이블을 먼저 조회하고 employees 테이블과 비교합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gcaxu/btsLAcHxUWM/8O4xSYwZ7TDVqszvW9SnEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gcaxu/btsLAcHxUWM/8O4xSYwZ7TDVqszvW9SnEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gcaxu/btsLAcHxUWM/8O4xSYwZ7TDVqszvW9SnEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGcaxu%2FbtsLAcHxUWM%2F8O4xSYwZ7TDVqszvW9SnEk%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;1330&quot; height=&quot;252&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테이블 풀-아웃 최적화(Table Pull-out)&lt;/h3&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;서브쿼리 부분이 유니크 인덱스, 프라이머리 키 룩업으로 결과가 1건 일 때 사용 가능합니다.&lt;/li&gt;
&lt;li&gt;서브 쿼리의 테이블을 아우터 쿼리로 가져와서 최적화를 수행하기에, 모든 서브쿼리 테이블이 아우터 쿼리로 가져와 질 수 있다면, 서브쿼리 자체는 사라집니다.&lt;/li&gt;
&lt;li&gt;'서브쿼리를 최대한 풀어서 사용하라'는 튜닝 가이드가 많은데, Table Pull-out최적화는 해당 가이드를 그대로 실행합니다.&lt;/li&gt;
&lt;li&gt;EXTRA 컬럼에 별도로 표기되지 않습니다. 실행계획의 id값으로 해당 최적화 사용 여부를 확인 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세미조인 상황에서 서브쿼리에 사용된 테이블을 아우터 쿼리로 꺼낸 후 쿼리를 join쿼리로 재작성하는 최적화 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 세미조인 형태의 쿼리의 실행계획을 확인해 보았을 때 실행계획의 id값이 동일하다면 서브쿼리 방식이 아닌 join방식으로 처리되었다는 뜻이기에 결국 테이블 풀-아웃 최적화 방식이 사용되었다는 의미로 이해할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l4c4T/btsLB0Tk7oP/ivgqwrlT3JeemmNBw7Mkk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l4c4T/btsLB0Tk7oP/ivgqwrlT3JeemmNBw7Mkk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l4c4T/btsLB0Tk7oP/ivgqwrlT3JeemmNBw7Mkk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl4c4T%2FbtsLB0Tk7oP%2FivgqwrlT3JeemmNBw7Mkk0%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;1438&quot; height=&quot;188&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;188&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;보다 정확한 확인을 위해서는 SHOW WARNINGS 명령어로 옵티마이저가 재작성한 쿼리를 직접 확인하는 방법도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhLKmF/btsLAeMkPN9/avmaShh3kfmcwQ7TRlCgfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhLKmF/btsLAeMkPN9/avmaShh3kfmcwQ7TRlCgfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhLKmF/btsLAeMkPN9/avmaShh3kfmcwQ7TRlCgfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhLKmF%2FbtsLAeMkPN9%2FavmaShh3kfmcwQ7TRlCgfk%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;499&quot; height=&quot;397&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;퍼스트 매치 (FirstMatch)&lt;/h3&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;EXTRA 컬럼에는 FirstMatch(table-N)이 표시됩니다.&lt;/li&gt;
&lt;li&gt;최적화는 상관 서브쿼리에도 적용 가능합니다.&lt;/li&gt;
&lt;li&gt;GROUP BY나 집합함수가 사용된 최적화에서는 사용 할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;In형태의 세미조인을 EXIST 형태로 튜닝한 것과 비슷한 방식으로 최적화를 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 테이블의 각 행에 대해 서브쿼리의 테이블과 비교해가며 조건이 맞는 행을 찾으면 더 이상 서브쿼리의 테이블을 조회하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 테이블의 다른 행을 기준으로 다시 서브쿼리의 테이블과 비교하는 최적화 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;루스 스캔(Loose scan)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXTRA 컬럼에서 LooseScan으로 표시됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loose Index scan으로 서브쿼리 테이블을 읽고, 그 다음 아우터 테이블을 드리븐 테이블로 사용해서 조인을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 쿼리에서 dept_no로 그룹핑해서 서브쿼리의 테이블의 데이터를 읽어온 다음 departments 테이블과 join을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xOOW1/btsLAdTSxPz/Hk1JY3xIKNz1ZvLo0ajweK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xOOW1/btsLAdTSxPz/Hk1JY3xIKNz1ZvLo0ajweK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xOOW1/btsLAdTSxPz/Hk1JY3xIKNz1ZvLo0ajweK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxOOW1%2FbtsLAdTSxPz%2FHk1JY3xIKNz1ZvLo0ajweK%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;946&quot; height=&quot;190&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구체화 (Materialization)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용된 임시테이블의 select_type에는 Materlized 라고 표시됩니다.&lt;/li&gt;
&lt;li&gt;다른 서브쿼리 최적화와 다르게, 서브쿼리 내에 GROUP BY절이 있어도 사용 가능합니다.&lt;/li&gt;
&lt;li&gt;In 절에서 서브쿼리는 상관 서브쿼리가 아니어야합니다.&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;사용된 임시테이블의 select_type에는 Materlized 라고 표시됩니다.&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;중복제거(Duplicated Weed-out)&lt;/h3&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;서브쿼리가 GROUP BY, 집합함수가 사용되었으면 사용 할 수 없습니다.&lt;/li&gt;
&lt;li&gt;서브쿼리의 테이블을 JOIN으로 처리하기에 최적화 할 수 있는 방법이 많습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세미조인 서브쿼리를 INNER JOIN 쿼리로 바꾸어 실행하고 마지막에 중복된 레코드를 제거하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INNER JOIN + GROUP BY를 실행하는것과 동일한 작업으로 쿼리를 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시테이블을 사용하기에 start tempoary , end tempoary 문구가 사용되며 그 두 구간 사이에서 중복제거 최적화가 일어났다고 생각하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqRyQ6/btsLzvgrf5k/CmcrGEG9dC41nGEEBcKUr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqRyQ6/btsLzvgrf5k/CmcrGEG9dC41nGEEBcKUr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqRyQ6/btsLzvgrf5k/CmcrGEG9dC41nGEEBcKUr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqRyQ6%2FbtsLzvgrf5k%2FCmcrGEG9dC41nGEEBcKUr1%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;1564&quot; height=&quot;352&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨디션 팬아웃 (condition_fanout_filter)&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;condition_fanout_filter 최적화 옵션이 활성화 되어있다면 아래 두 경우에 대해서 보다 정확한 예측을 할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WHERE조건에 사용된 컬럼에 인덱스가 있는 경우&lt;/li&gt;
&lt;li&gt;WHERE조건에 사용된 컬럼에 히스토그램이 존재하는 경우&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파생 테이블 머지 (derived_merge)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 FROM절에 사용되는 서브쿼리를 외부쿼리와 병합하는 최적화입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SHOW WARNINGS 명령어로 최적화된 쿼리를 확인 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zjgDE/btsLBspfPQl/ogIOKHEpUtNdZpa7hiT0y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zjgDE/btsLBspfPQl/ogIOKHEpUtNdZpa7hiT0y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zjgDE/btsLBspfPQl/ogIOKHEpUtNdZpa7hiT0y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzjgDE%2FbtsLBspfPQl%2FogIOKHEpUtNdZpa7hiT0y1%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;784&quot; height=&quot;246&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 몇몇 상황에 대해서는 derived_merge가 사용될 수 없기에 아래와 같은 경우에는 수동으로 병합하는것이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;집계함수 및 윈도우 함수가 사용된 쿼리&lt;/li&gt;
&lt;li&gt;DISTINCT가 사용된 서브쿼리&lt;/li&gt;
&lt;li&gt;GROUP BY, HAVING이 사용된 서브쿼리&lt;/li&gt;
&lt;li&gt;LIMIT, UNION, UNION ALL을 포함하는 사용된 서브쿼리&lt;/li&gt;
&lt;li&gt;SELECT 절에 사용된 서브쿼리&lt;/li&gt;
&lt;li&gt;값이 변경되는 사용자 변수가 사용된 서브 쿼리&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;스킵 스캔 (skip scan)&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;즉 인덱스의 후행컬럼만을 조건으로 사용해도 인덱스를 사용하여 쿼리 개선을 할 수 있습니다.&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;1238&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p9YW4/btsLzbCKHb2/sn6OnwhjI9OwvlQpDmnhFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p9YW4/btsLzbCKHb2/sn6OnwhjI9OwvlQpDmnhFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p9YW4/btsLzbCKHb2/sn6OnwhjI9OwvlQpDmnhFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp9YW4%2FbtsLzbCKHb2%2Fsn6OnwhjI9OwvlQpDmnhFK%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;736&quot; height=&quot;498&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해시 조인 (Hash Join)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시조인은 Best Throughput 전략에 적합하고 네스티드 루프 조인은 Best Response-time 전략에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 일반적인 웹서비스에서는 네스티드 루프 조인이 더 좋은 방식일때가 많지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분석과 같은 서비스에서는 해시조인이 더 좋은 방식일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서는 조인 컬럼이 인덱스가 없거나, 조인 대상 테이블 중 일부의 레코드건수가 매우 적을 때에만 해시조인 알고리즘을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시조인은 빌드(build)단계와 프로브(probe)단계로 나뉘어집니다.&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;&lt;b&gt;빌드 단계 (build phase)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인 대상 테이블 중 레코드 건수가 적어 해시테이블로 만들기 적합한 테이블을 골라서 메모리에 해시테이블을 생성하는 작업입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들어진 해시 테이블은 조인버퍼에 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;프로브 단계 (probe phase)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 테이블의 레코드를 읽어 만들어둔 해시테이블과 일치하는 레코드를 찾는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 나머지 테이블을 프로브 테이블이라 합니다.&lt;/p&gt;</description>
      <category>DB</category>
      <category>InnoDB</category>
      <category>MySQL</category>
      <category>옵티마이저</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/83</guid>
      <comments>https://jaykos96.tistory.com/83#entry83comment</comments>
      <pubDate>Sat, 21 Dec 2024 19:06:28 +0900</pubDate>
    </item>
    <item>
      <title>NandToTetris-Hardware simulator / 조합 게이트 실습-밑바닥부터 만드는 컴퓨팅 시스템</title>
      <link>https://jaykos96.tistory.com/82</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;친구의 추천을 받아 '밑바닥부터 구현하는 컴퓨팅 시스템'책을 읽고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 구현해보는 내용들이 좀 있이서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부 내용 정리 및 학습 목적으로 책에서 진행한 실습들을 포스팅해보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습은 공식 사이트에서 Web IDE를 통해 진행하였습니다.&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://www.nand2tetris.org/software&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.nand2tetris.org/software&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734615531608&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Software | nand2tetris&quot; data-og-description=&quot;Two OS implementations are supplied: (i) a collection of eight&amp;nbsp;.vm&amp;nbsp;class files, written originally in Jack (just like Unix is written in C), and (ii) a faster implementation of all the OS services, embedded in the supplied&amp;nbsp;VM Emulator.&quot; data-og-host=&quot;www.nand2tetris.org&quot; data-og-source-url=&quot;https://www.nand2tetris.org/software&quot; data-og-url=&quot;https://www.nand2tetris.org/software&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.nand2tetris.org/software&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.nand2tetris.org/software&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Software | nand2tetris&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Two OS implementations are supplied: (i) a collection of eight&amp;nbsp;.vm&amp;nbsp;class files, written originally in Jack (just like Unix is written in C), and (ii) a faster implementation of all the OS services, embedded in the supplied&amp;nbsp;VM Emulator.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.nand2tetris.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 IDE 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nand2tetris.github.io/web-ide/chip/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nand2tetris.github.io/web-ide/chip/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734615558851&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;NAND2Tetris&quot; data-og-description=&quot;&quot; data-og-host=&quot;nand2tetris.github.io&quot; data-og-source-url=&quot;https://nand2tetris.github.io/web-ide/chip/&quot; data-og-url=&quot;https://nand2tetris.github.io/web-ide/chip/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://nand2tetris.github.io/web-ide/chip/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nand2tetris.github.io/web-ide/chip/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;NAND2Tetris&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nand2tetris.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NandGate&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NandToTetris에서 기초가 되는 NandGate의 진리표입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5G8YK/btsLpPUb8pc/p5Wo1XSJ7jkewf8PjZurLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5G8YK/btsLpPUb8pc/p5Wo1XSJ7jkewf8PjZurLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5G8YK/btsLpPUb8pc/p5Wo1XSJ7jkewf8PjZurLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5G8YK%2FbtsLpPUb8pc%2Fp5Wo1XSJ7jkewf8PjZurLK%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;373&quot; height=&quot;305&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NotGate&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0이 들어오면 1, 1이 들어오면 0 을 출력하는 게이트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Not의 경우 NandGate를 그대로 활용하면 원하는 출력을 얻을 수 있습니다.&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;HDL의 PARTS 섹션에서는 하위 칩들을 사용해 논리를 구현하며, 핀에 신호를 전달하는 과정을 표현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nand칩을 사용해 두 입력핀 a,b로부터 out이라는 신호를 생성 후 외부로 전달합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734693464142&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Not {
    IN in;
    OUT out;

    PARTS:
    Nand(a= in, b= in, out=out);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AndGate&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHySFi/btsLsrcW0Rn/cRM4uAQpkEK3DfRCUKIlfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHySFi/btsLsrcW0Rn/cRM4uAQpkEK3DfRCUKIlfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHySFi/btsLsrcW0Rn/cRM4uAQpkEK3DfRCUKIlfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHySFi%2FbtsLsrcW0Rn%2FcRM4uAQpkEK3DfRCUKIlfK%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;492&quot; height=&quot;266&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1과1이 들어올때는 1, 나머지는 0을 출력하는 AndGate입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nand과 앞서 만들어둔 Not을 활용하여 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nand칩에서 aNandb라는 임시 내부 신호를 만들고 Nand의 출력핀 out이 신호를 Not칩으로 전달합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734693423091&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP And {
    IN a, b;
    OUT out;
    
    PARTS:
    Nand(a= a, b= b, out= NandAB); 
    Not(in= NandAB, out= out);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OrGate&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNMAaQ/btsLrzij9sU/yQhjad85gqmtV2qQK9DWGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNMAaQ/btsLrzij9sU/yQhjad85gqmtV2qQK9DWGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNMAaQ/btsLrzij9sU/yQhjad85gqmtV2qQK9DWGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNMAaQ%2FbtsLrzij9sU%2FyQhjad85gqmtV2qQK9DWGK%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;512&quot; height=&quot;244&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a,b 둘중 하나라도 1이면 1을 출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 신호를 Not하고 각각 변경된 신호를 Nand 게이트에 전달합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734694157315&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Or {
    IN a, b;
    OUT out;

    PARTS:
    Not(in=a, out=notA);
    Not(in=b, out=notB);
    Nand(a= notA, b=notB, out=out );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;XorGate&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A2EwX/btsLsfX270J/Hf08iQ6XDbfr8wHRO1aFVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A2EwX/btsLsfX270J/Hf08iQ6XDbfr8wHRO1aFVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A2EwX/btsLsfX270J/Hf08iQ6XDbfr8wHRO1aFVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA2EwX%2FbtsLsfX270J%2FHf08iQ6XDbfr8wHRO1aFVk%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;524&quot; height=&quot;266&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 입력값 중 하나만 1일 때 1을 나머지는 0을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Not, And, Or을 활용해서 만들 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734696381488&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Xor {
    IN a, b;
    OUT out;

    PARTS:
    Not(in=b, out=notB);
    Not(in=a, out=notA);
    And(a=a, b=notB, out=aAndNotB);
    And(a=notA, b=b, out=notAAndB);
    Or(a=aAndNotB, b=notAAndB, out=out);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티플렉서 (MUL, Selector)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a,b의 데이터 비트와 sel 선택비트 3가지 input을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a,b 중 sel에 의해 선택된 값을 out으로 내보냅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chQONH/btsLpLEbvTv/lVltiXqOunjZC2d4U1CUkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chQONH/btsLpLEbvTv/lVltiXqOunjZC2d4U1CUkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chQONH/btsLpLEbvTv/lVltiXqOunjZC2d4U1CUkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchQONH%2FbtsLpLEbvTv%2FlVltiXqOunjZC2d4U1CUkk%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;420&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1737115481635&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Mux {
    IN a, b, sel;
    OUT out;

    PARTS:
    Not(in=sel, out=NotSel);
    And(a=a, b=NotSel, out= aAndNotSel);
    And(a=b, b=sel, out= bAndSel);
    Or(a= aAndNotSel, b=bAndSel, out=out);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;디멀티플렉서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플렉서와 반대의 기능을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in과 입력신호(sel)를 각각 하나씩 받아서 2개의 출력(a,b)으로 전달하는데, 입력신호(sel)가 0이면 a에 in을 1이면 b에 in을 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in이 전달되지 않는 신호에는 0을 전달합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LEVaL/btsLRintO8h/IU3MveMn5luuBdjdtsNqPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LEVaL/btsLRintO8h/IU3MveMn5luuBdjdtsNqPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LEVaL/btsLRintO8h/IU3MveMn5luuBdjdtsNqPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLEVaL%2FbtsLRintO8h%2FIU3MveMn5luuBdjdtsNqPK%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;698&quot; height=&quot;231&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1737117494909&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP DMux {
    IN in, sel;
    OUT a, b;

    PARTS:
    Not(in= sel, out=NotSel);
    And(a=in, b=NotSel , out=a);
    And(a=in , b=sel , out=b );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;16BIT And, Or, Not, Mux&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16비트 게이트들은 단순하게 앞에서 작업했던 And, Not, Or, Mux들을 각 비트에 맞게 16번씩 반복해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Or8Way (M입력 Or)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다입력되는 m개의 비트 중 적어도 하나가 1이면 1을 출력하는 게이트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어오는 모든 bit를 순서대로 OR게이트로 비교하면 원하는 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1737119237923&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Or8Way {
    IN in[8];
    OUT out;

    PARTS:
    Or(a=in[0] , b=in[1] , out=out1 );
    Or(a=out1 , b=in[2] , out=out2 );
    Or(a=out2 , b=in[3] , out=out3 );
    Or(a=out3 , b=in[4] , out=out4 );
    Or(a=out4 , b=in[5] , out=out5 );
    Or(a=out5 , b=in[6] , out=out6 );
    Or(a=out6 , b=in[7] , out=out );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Mux4Way16 (M입력 N비트 멀티플렉서)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;m개의 n비트 입력 중 하나를 선택해서 n비트 출력으로 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;00이면 a, 01이면 b, 10이면 c, 11이면 d를 출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 2진수에서는 오른쪽이 하위 비트이기에 sel이 01이라면 sel[0]은 1, sel[1]은 0입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUs2Zd/btsLTauI4KT/RpT3CpcdkoFP0e9iOFIKZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUs2Zd/btsLTauI4KT/RpT3CpcdkoFP0e9iOFIKZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUs2Zd/btsLTauI4KT/RpT3CpcdkoFP0e9iOFIKZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUs2Zd%2FbtsLTauI4KT%2FRpT3CpcdkoFP0e9iOFIKZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;338&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1737120613988&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Mux4Way16 {
    IN a[16], b[16], c[16], d[16], sel[2];
    OUT out[16];
    
    PARTS:
    Mux16(a=a, b=b, sel=sel[0] , out=aMuxb);
    Mux16(a=c, b=d, sel=sel[0] , out=cMuxd);
    Mux16(a=aMuxb , b=cMuxd , sel=sel[1] , out=out );    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Mux8Way16 (M입력 N비트 멀티플렉서)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mux8Way16은 앞서 만들었던 Mux4Way16을 이용해서 구현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1737162509951&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP Mux8Way16 {
    IN a[16], b[16], c[16], d[16],
       e[16], f[16], g[16], h[16],
       sel[3];
    OUT out[16];

    PARTS:
    Mux4Way16(a=a , b=b , c=c , d=d , sel=sel[0..1] , out=abMuxCd);
    Mux4Way16(a=e , b=f , c=g , d=h , sel=sel[0..1] , out=efMuxGh);
    Mux16(a=abMuxCd , b=efMuxGh , sel=sel[2] , out=out);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;DMux4Way (M입력 N비트 디멀티플렉서)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플렉서와 반대의 기능을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어온 sel에 따라 a,b,c,d로 출력을 전달합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4DjMH/btsLSkLHkud/tmkXJkYOXnGT2EUAUZCc7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4DjMH/btsLSkLHkud/tmkXJkYOXnGT2EUAUZCc7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4DjMH/btsLSkLHkud/tmkXJkYOXnGT2EUAUZCc7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4DjMH%2FbtsLSkLHkud%2FtmkXJkYOXnGT2EUAUZCc7K%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;1260&quot; height=&quot;356&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1737164286513&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP DMux4Way {
    IN in, sel[2];
    OUT a, b, c, d;

    PARTS:
    DMux(in=in, sel=sel[1], a=in1, b=in2);
    DMux(in=in1, sel=sel[0], a=a, b=b);
    DMux(in=in2, sel=sel[0], a=c, b=d);  
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;DMux8Way (M입력 N비트 디멀티플렉서)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 만들었던 DMux4Way를 활용해서 만들 수 있습니다&lt;/p&gt;
&lt;pre id=&quot;code_1737164647968&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CHIP DMux8Way {
    IN in, sel[3];
    OUT a, b, c, d, e, f, g, h;

    PARTS:
    DMux(in=in, sel=sel[2], a=in1, b=in2);
    DMux4Way(in=in1, sel=sel[0..1], a=a, b=b, c=c, d=d);
    DMux4Way(in=in2, sel=sel[0..1], a=e, b=f, c=g, d=h);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고용 부록 진리표&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqkpmy/btsMadSKA41/2WszzzXtzOutswSpa7PI5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqkpmy/btsMadSKA41/2WszzzXtzOutswSpa7PI5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqkpmy/btsMadSKA41/2WszzzXtzOutswSpa7PI5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqkpmy%2FbtsMadSKA41%2F2WszzzXtzOutswSpa7PI5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;469&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;866&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRfqJE/btsMaDKmEbj/KYO1AyARI8tp0VFsAOKJKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRfqJE/btsMaDKmEbj/KYO1AyARI8tp0VFsAOKJKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRfqJE/btsMaDKmEbj/KYO1AyARI8tp0VFsAOKJKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRfqJE%2FbtsMaDKmEbj%2FKYO1AyARI8tp0VFsAOKJKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;705&quot; height=&quot;281&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;446&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;</description>
      <category>NandToTetris</category>
      <category>nandtotetris</category>
      <category>밑바닥부터 구현하는 컴퓨팅 시스템</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/82</guid>
      <comments>https://jaykos96.tistory.com/82#entry82comment</comments>
      <pubDate>Thu, 19 Dec 2024 22:39:24 +0900</pubDate>
    </item>
    <item>
      <title>InnoDB 아키텍쳐를 알아보자</title>
      <link>https://jaykos96.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 APM을 만드려고 하다보니 오픈소스의 아키텍쳐에 대한 관심이 높아져서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Real MySQL을 보고 공부한 InnoDB의 아키텍쳐를  정리해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서 언급하는 8.0이하에서 발생한 이슈들이나 정보들은 정리하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;InnoDB 아키텍쳐&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C2XcQ/btsLgGPEAJh/h83cyePI4l3R1zZdBK08xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C2XcQ/btsLgGPEAJh/h83cyePI4l3R1zZdBK08xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C2XcQ/btsLgGPEAJh/h83cyePI4l3R1zZdBK08xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC2XcQ%2FbtsLgGPEAJh%2Fh83cyePI4l3R1zZdBK08xk%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;1036&quot; height=&quot;928&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;928&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레코드 기반 Lock을 제공하기에 높은 동시성 처리가 가능하며 안정적인 엔진입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서 기본적으로 사용하는 스토리지 엔진입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;InnoDB의 특징&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;primary key에 의한 클러스터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;InnoDB에 저장되는 테이블 레코드들은 기본적으로 pk에 의해 클러스터링 되어 pk값 순서대로 저장됩니다.&lt;/b&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;또한 세컨더리 인덱스는 레코드의 실제 주소가 아닌 pk의 값을 논리적인 주소로 사용해서 InnoDB의 데이터를 가져옵니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;foreign key 지원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB스토리지 엔진 레벨에서 지원하는 기능으로 MyISAM이나 MEMORY테이블에서는 사용할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB에서 fk를 사용하기 위해서는 부모, 자식 테이블 모두 해당 컬럼에 인덱스 생성이 필요하고, 변경시 부모,자식 테이블 모두에게서 데이터가 있는지, 데드락이 걸릴 수 있는 상황 등 고려할 여러 상황들이 많기에 주의해서 사용할 필요가 있습니다.&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;foreign_key_checks 시스템 변수를 OFF로 설정하면 fk체크 작업을 일시적으로 멈출 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734172734433&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SET foreign_key_checks=OFF;
SET SESSION foreign_key_checks=OFF;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;MVCC (Multi Version Concurrency Control)&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;MVCC의 MV(Multi Version)는 하나의 레코드에 대해 여러개의 버전이 동시에 관리된다는 의미입니다.&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;InnoDB는 Undo Log를 이용하여 MVCC를 구현합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwhsZ9/btsLfOgziTH/3NHHHAVLupix1z7kVFLJL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwhsZ9/btsLfOgziTH/3NHHHAVLupix1z7kVFLJL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwhsZ9/btsLfOgziTH/3NHHHAVLupix1z7kVFLJL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwhsZ9%2FbtsLfOgziTH%2F3NHHHAVLupix1z7kVFLJL1%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;1014&quot; height=&quot;344&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;344&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;위와 같은 쿼리들이 실행되면 InnoDB에서는 아래 그림과 같은 상황이 됩니다.&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;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDusUw/btsLho8v9ri/jiZlzh0PV4BC6TzYcJMCI0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDusUw/btsLho8v9ri/jiZlzh0PV4BC6TzYcJMCI0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDusUw/btsLho8v9ri/jiZlzh0PV4BC6TzYcJMCI0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDusUw%2FbtsLho8v9ri%2FjiZlzh0PV4BC6TzYcJMCI0%2Fimg.jpg&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;814&quot; height=&quot;458&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 아래와 같은 UPDATE문을 실행하면 아래 그림과 같은 상태가 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BzaP4/btsLhqL465i/iCoSjFLJ1rriWoGQcCxiM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BzaP4/btsLhqL465i/iCoSjFLJ1rriWoGQcCxiM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BzaP4/btsLhqL465i/iCoSjFLJ1rriWoGQcCxiM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBzaP4%2FbtsLhqL465i%2FiCoSjFLJ1rriWoGQcCxiM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;90&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNZtjQ/btsLfXLfJ6C/n8WSB1dknyLAVW7BUUNZPk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNZtjQ/btsLfXLfJ6C/n8WSB1dknyLAVW7BUUNZPk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNZtjQ/btsLfXLfJ6C/n8WSB1dknyLAVW7BUUNZPk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNZtjQ%2FbtsLfXLfJ6C%2Fn8WSB1dknyLAVW7BUUNZPk%2Fimg.jpg&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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UPDATE 내용을 COMMIT여부와 관계없이 버퍼풀에 반영합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 변경되기 전 내용을 언두로그에 보관합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에 변경을 기록하는건 InnoDB의 백그라운드 스레드 (Write Thread)가 작업하기에 UPDATE명령 후 바로 디스크에 업데이트 될 수도 있고 아닐 수도 있습니다.&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;UPDATE문 이후 COMMIT을 하면 버퍼풀의 내용을 디스크에 영구적으로 저장하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROLLBACK을 하면 언두영역의 데이터를 버퍼풀로 복구하고 언두영역의 데이터를 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언두 영역의 데이터는 COMMIT이 되었을때 사라지는건 아니고 언두영역을 필요로하는 트랜잭션이 없을때 사라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 구조로 InnoDB가 동작하기에 두가지 버전 (Multi Version)을 관리한다고 하는것이며 어디에 있는 데이터를 읽어올지는 MySQL의 격리수준(Isolation level)에 따라 다를 수 있습니다.&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;READ_UNCOMMITTED의 경우에는 버퍼풀의 데이터를,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;READ_COMMITTED나 그 이상의 격리수준(REPEATABLE_READ, SERIALIZABLE)의 경우에는 아직 COMMIT되지 않았기에 버퍼풀이나, 언두영역의 데이터를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 작업과 과정을 MVCC라고 표현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;잠금 없는 읽관된 읽기 (Non-Locking Consistent Read)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지엔진은 MVCC를 이용하기에 읽기 작업 시 잠금을 걸지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 격리 수준 SERIALIZABLE이 아니라면 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;즉 다른 사용자가 특정 레코드를 변경하고 COMMIT하지 않았다고 해도 이 트랜잭션이 다른 사용자들의 SELECT를 방해하지 않습니다. (다른 사용자들은 대부분 언두로그를 통해 데이터를 읽음, 격리수준에 따라 다를 수 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 '잠금 없는 읽관된 읽기' Non-Locking Consistent Read라고 합니다.&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;종종 오랜시간 활성된 트랜잭션으로 인해 MySQL서버가 느려진다면 Consistent Read를 위해 InnoDB가 언두로그를 삭제하지 못하고 계속 유지하고 있어서 그런 것일 수 있으니, 트랜잭션은 시작되었다면 가능한 빨리 종료되어야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;자동 데드락 감지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진은 내부적으로 잠금이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;교착상태에 빠지지 않았는지 잠금 대기 목록을 그래프 형태(Wait-for List)로 관리&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 엔진은&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;데드락 감지 스레드를 가지고 있어 주기적으로 잠금 대기 그래프를 검사해 교착상태의 트랜잭션을 찾아 그중 하나를 강제 종료합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;종료되는 트랜잭션의 기준은 언두 로그의 양&lt;/b&gt;이며, 언두로그를 더 적게 가진 레코드가 일반적으로 롤백 대상이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB엔진은 상위 레이어인 MySQL엔진에서 관리되는 테이블 잠금(LOCK TABLES 명령으로 잠긴 테이블)은 확인 할 수 없어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드락 감지가 불확실하기에 innodb_table_locks 시스템 변수를 활성화하여 InnoDB 스토리지 엔진 내부 잠금뿐 아니라 테이블 레벨의 잠금까지 감지하도록 활성하하는게 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시 처리되는 스레드나 각 트랜잭션의 잠금의 개수가 많아지면 데드락 감지 스레드가 느려집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드락 감지 스레드는 잠금 목록을 검사하기에 잠금 상태가 변경되지 않도록 잠금 목록이 저장된 리스트(잠금 테이블)에 새로운 잠금을 걸고 데드락 스레드를 찾습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드락 감지 스레드가 느려지면 서비스 쿼리를 처리중인 스레드는 더는 작업을 하지 못하고 대기하게 되므로 서비스에 악영항을 끼칠 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 상황에 대비하여 innddb_deadlock_detect 변수를 통해 데드락 감지 스레드를 OFF할 수 있으나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드락 감지 스레드가 작동하지 않으면 InnoDB스토리지 엔진에서 2개 이상의 트랜잭션이 데드락이 걸리게 된다면 무한정 대기하게 될 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 innodb_lock_wait_timeout 변수(기본값 50초)를 활성화하여 데드락 상황이 지정한 시간만큼 지나게 되면 에러를 반환하게 할 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;InnoDB 버퍼풀&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해두는 InnoDB의 가장 핵심적인 기능입니다.&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;INSERT, UPDATE, DELETE처럼 데이터를 변경하는 쿼리는 레코드 데이터에 직접 접근해야하기에 매번 랜덤I/O를 발생시킬 수 밖에 없습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;버퍼풀 크기(Memory) 설정&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;b&gt;MySQL서버에서 메모리를 필요로하는 부분은 크게 없지만 가끔 클라이언트 세션에서 테이블의 레코드를 읽고 쓸 때 버퍼로 사용되는 '레코드 버퍼'가 상당한 메모리를 사용하는 경우도 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 커넥션과 테이블 모두 많은 서비스라면 메모리공간이 꽤 많이 필요할수도 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레코드 버퍼의 크기는 전체 커넥션수와 각 커넥션에서 읽고 쓰는 테이블 개수에 따라 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 버퍼풀의 크기 이야기로 넘어와서,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;운영체제의 공간이 8GB 미만이라면 50%정도만 InnoDB버퍼풀로 설정하는게 좋습니다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;더 여유가 있는 운영체제를 사용한다면 전체 메모리의 50%에서 시작해서 조금씩 올려가며 최적점을 찾습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 메모리 공간이 50G이상인 운영체제를 사용한다면 15GB ~ 30GB정도를 다른 프로그램을 위해 남겨두고 나머지를 InnoDB버퍼풀로 설정하는게 권장됩니다.&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;innodb_buffer_pool_size 시스템 변수로 크기를 설정 할 수 있으며 동적인 크기 확장도 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼풀은 내부적으로 128MB 청크 단위로 쪼개어져 관리되는데, 이는 크리를 늘리는 단위 크기로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 버퍼풀은 반드시 128MB단위로 처리됩니다.&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;버퍼풀은 innodb_buffer_pool_instances 변수를 통해 여러개로 분리하여 관리 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 8개로 초기화됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매모리가 크다면 버퍼 풀 인스턴스당 5GB정도가 되도록 개수를 설정하는게 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;버퍼풀의 구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB스토리지 엔진은 버퍼 풀이라는 메모리 공간을 페이지 크기(innodb_page_size 변수에 설정된)로 쪼개어 InnoDB 스토리지 엔진이 데이터를 필요로 할 때 해당 페이지를 읽어서 각 조각에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;버퍼풀의 페이지를 관리하기 위해 LRU(Least Recently Used)리스트와 플러시(Flush)리스트, 그리고 프리(Free)리스트라는 3개의 자료구조를 관리합니다.&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;프리 리스트는 LRU(Least Recently Used)와 MRU(Most Recently Used)리스트의 결합입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kGQwu/btsLiHHNH0J/mCKQMTDJxsco9lsnXA0Cn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kGQwu/btsLiHHNH0J/mCKQMTDJxsco9lsnXA0Cn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kGQwu/btsLiHHNH0J/mCKQMTDJxsco9lsnXA0Cn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkGQwu%2FbtsLiHHNH0J%2FmCKQMTDJxsco9lsnXA0Cn0%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;420&quot; height=&quot;663&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LRU리스트를 관리하는 목적은 디스크로부터 읽어온 페이지를 최대한 오랫동안 InnoDB버퍼풀의 메모리에 유지해서 디스크 읽기를 최소화 하기 위함입니다.&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;InnoDB 스토리지 엔진에서 디스크로부터 버퍼풀로 데이터를 가져오는 과정은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 필요한 레코드가 저장된 페이지가 버퍼풀에 있는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - A. InnoDB 어댑티브 해시 인덱스를 이용해 페이지 검색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - B. 해당 테이블의 인덱스를 이용해 버퍼풀에서 검색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - C. 버퍼풀에 이미 페이지가 있다면 해당 페이지의 포인터를 MRU 방향으로 승급&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;2. 디스크에서 필요한 페이지를 버퍼풀에 적재하고, 적재된 페이지에 대한 포인터를 LRU 헤더 부분에 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 버퍼풀의 LRU 헤더 부분에 적재된 데이터 페이지가 읽히면 (Read되면) MRU헤더 부분으로 이동&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 버퍼풀에 상주하는 페이지는 클라이언트 쿼리가 얼마나 최근에 접근했었는지에 따라 Age가 부여되며, 풀에 상주하는 동안 쿼리에서 오랫동안 사용되지 않으면 결국 페이지는 버퍼풀에서 제거됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼풀의 데이터 페이지가 쿼리에 의해 사용되면 Age가 초기화 되고 MRU의 헤더 부분으로 옮겨집니다.&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;5. 필요한 데이터가 자주 접근됐다면 해당 페이지의 인덱스 키를 어댑티브 해시 인덱스에 추가합니다.&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;그렇기에 한번 버퍼풀에 들어간 페이지가 꾸준하게 Read된다면 MRU영역에 계속 존재하게 되고, 사용되지 않는 페이지들은 LRU의 끝으로 밀려나며 결국 버퍼풀에서 제거됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;플러시(Flush)리스트는 디스크와 동기화 되지 않은 페이지 (더티 페이지)의 변경 시점 기준의 페이지 목록을 관리합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에서 읽은 상태 그대로 존재한다면 플러시 리스트에 관리되지 않지만, 변경이 가해진 페이지는 플러시 리스트에 관리되고 특정 시점에 디스크로 기록됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 변경되면 InnoDB는 변경 내용을 리두로그, 버퍼풀의 데이터 페이지에 내용을 반영합니다.&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;InnoDB 스토리지 엔진은 체크포인트를 발생시켜 디스크의 리두로그와 버퍼풀의 페이지의 상태를 동기화 시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;버퍼풀과 리두로그&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;InnoDB 버퍼풀의 더티 페이지는 특정 리두로그의 엔트리와 관계를 가지고,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;체크포인트가 발생하면 체크포인트 LSN보다 낮은 리두로그 엔트리와 관련된 더티 페이지를 모두 디스크로 동기화 시킵니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(장애복구 및 데이터 무결성을 위한 디스크 동기화)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 리두로그, LSN, 체크포인트에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리두로그는 서버가 비정상적으로 종료되었을 때 데이터파일에 기록되지 못한 데이터를 잃지 않게 도와줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리두로그는 1개 이상의 고정 크기 파일을 사용하며 '순환 버퍼 방식'으로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 변경이 발생하면 로그 엔트리(리두 로그에 기록되는 단위)가 하나의 리두로그 파일에 계속 쓰여집니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 쓰여지다가 어느 순간 새로운 파일에 쓰여지고 모든 파일에 다 쓰여지면 다시 첫번째 파일에 로그 엔트리가 덮어 쓰여집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB엔진은 로그 버퍼를 사용하여 리두로그 파일에 버퍼링하여 데이터를 씁니다.&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;리두 로그 파일의 '공간'들에는 새로운 엔트리가 계속해서 덮혀쓰여지기에 로그의 고유ID값 LSN(Log Sequence Number)는 계속해서 증가하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB엔진은 주기적으로 체크포인트 이벤트를 발생시켜 리두로그와 버퍼풀의 더티페이지를 디스크로 동기화 시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 발생한 체크포인트 지점의 로그 엔트리의 LSN 보다 낮은 LSN을 가지는 데이터들을 디스크로 동기화 시킵니다.&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;이 체크 포인트 지점의 LNS을 기점으로 더 큰 LSN을 가지는 데이터가 있는 공간을 활성 리두 로그 (Active Redo Log)공간,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크포인트 지점의 LSN보다 낮은 LSN을 가지는 공간 즉 디스크로 동기화된 공간은 재사용 가능 공간(Reuseable Space)으로 구분합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734172734435&quot; class=&quot;gherkin&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;r&quot;&gt;&lt;code&gt;# 체크포인트 발생 시 LSN : 2000
# 2000 이상 LSN을 가지고 있는 공간은 재사용 불가능한 Active Log
# 디스크로 동기화된 2000 이하 LSN을 가지고 있는 공간은 재사용 가능

|-- Reusable Space --|-------- Active Redo Log -------|
| LSN: 1500 ---------| LSN: 2000 ------------ 2500    |&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;활성 리두 공간 (Active Redo Log)공간의 데이터들은 아직 디스크에 동기화 되지 않았기에 덮어쓰여질 수 없습니다.&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;추가로 가장 최근 체크포인트의 LSN과 마지막 리두 로그 엔트리의 LSN의 차이를 체크포인트 에이지 (CheckPoint Age)라고 하며 이는 활성 리두 공간의 크기를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;버퍼 풀 flush (Buffer Pool Flush) (플러시 리스트 flush, LRU 리스트 flush)&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쓰기 버퍼링 기능을 구현하기 위해서 '플러시 리스트' flush, 'LRU 리스트' flush&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp;2개의 기능을 백그라운드로 실행시킵니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이떄 동기화에 사용하는 스레드를 클리너 스레드(Cleaner Thread) 라고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(성능 및 클라이언트 데이터 무결성을 위한 디스크 동기화)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 플러시 리스트 플러시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB엔진은 버퍼풀에 더티페이지가 생성되면 리두로그에도 기록되지만 '플러시 리스트'에도 추가됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;주기적으로 플러시 리스트(Flush_list)의 flush 함수를 호출해서 플러시 리스트에서 오래전에 변경된 데이터 페이지 순서대로 디스크에 동기화하는 작업을 수행합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 더티페이지 일괄 처리 작업(flush)이 효율적일수록 클라이언트의 쿼리 처리가 악영향을 받지 않고 부드럽게 처리 될 수 있습니다.&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;버퍼풀에 더티페이지의 양이 많을 수록 Disk IO Burst 현상이 발생할 가능성이 높아지는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼풀의 더티페이지 비중이 90%가 넘어가게 되면 InnoDB엔진은 즉시 디스크 기록을 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Disk IO Burst현상이 발생할 가능성이 매우 높기에 innodb_max_dirty_pages_pct_lwn 변수를 이용해 일정 수준이상의 더티페이지가 발생하면 조금씩 디스크로 기록하도록 하는 설정값을 조정해서 Dist IO Burst현상을 예방 할 수도 있습니다 (기본값10%)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. LRU 리스트 플러시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 엔진은 LRU리스트에서 사용빈도가 낮은 데이터 페이지들을 제거해서 새로운 페이지들을 읽어들일 공간을 만드는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 LRU리스트에서 flush함수가 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LRU리스트의 끝부분부터 시작해서 최대 innodb_lru_scan_depth 시스템 변수에 설정된 개수만큼 페이지들을 스캔합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스캔하면 디스크에 동기화 작업을 진행하고 클린 페이지는 즉시 Free 리스트로 페이지를 옮깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LRU 리스트를 flush 함으로서 공간을 확보해 클라이언트의 요청 처리 속도를 개선합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;버퍼풀 상태 백업 및 조회방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼풀은 쿼리 성능에 매우 밀접한 관계가 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;버퍼풀에 사용할 데이터들이 미리 적재되어 있으면 디스크 읽기 없이 데이터를 클라이언트에 반환 할 수 있기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 상태를 Warning Up상태라고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;버퍼풀에 대한 백업은 데이터 디렉토리에 ib_buffer_pool이라는 이름의 파일로 생성되는데,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 버퍼풀의 크기에 비해 매우 작습니다. (몇십MB 이하)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 이유는 LRU리스트의 데이터 페이지의 메타 정보만 가져와 저장하기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;버퍼풀에 있는 데이터를 조회하기 위해서는 information_schema 데이터베이스에 innddb_cached_indexs 테이블에서&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;테이블의 인덱스별로 데이터 페이지가 얼마나 버퍼풀에 적재되어 있는지 확인 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734184541083&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT it.name table_name
      ,ii.name index_name
      ,ici.n_cached_pages n_cached_pages
FROM information_schema.INNODB_TABLES it
INNER JOIN information_schema.INNODB_INDEXES ii on  ii.TABLE_ID = it.TABLE_ID
INNER JOIN information_schema.INNODB_CACHED_INDEXES ici on ici.INDEX_ID = ii.INDEX_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qtqwJ/btsLijHFe3R/A7OvgARkY58qHM3Mm3uT71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qtqwJ/btsLijHFe3R/A7OvgARkY58qHM3Mm3uT71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qtqwJ/btsLijHFe3R/A7OvgARkY58qHM3Mm3uT71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqtqwJ%2FbtsLijHFe3R%2FA7OvgARkY58qHM3Mm3uT71%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;1164&quot; height=&quot;346&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;346&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;테이블 전체 페이지중 어느정도 비율이 InnoDB 버퍼풀에 있는지 확인할수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734185083061&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
    t.TABLE_NAME
    ,(SELECT SUM(ici.N_CACHED_PAGES) AS n_cached_pages
     FROM information_schema.INNODB_TABLES it
              INNER JOIN information_schema.INNODB_INDEXES ii ON ii.TABLE_ID = it.TABLE_ID
              INNER JOIN information_schema.INNODB_CACHED_INDEXES ici ON ici.INDEX_ID = ii.INDEX_ID
     WHERE it.name = CONCAT(t.table_schema, '/', t.table_name)
    ) AS total_cached_pages,
    ((t.data_length + t.index_length - t.data_free) / @@innodb_page_size) AS total_pages
FROM
    information_schema.tables t
WHERE
    t.table_schema = 'EMP' and t.TABLE_NAME = 'TB_USER'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LPQAZ/btsLj0GkieU/EsTzKFqclVA6Axaf0KND00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LPQAZ/btsLj0GkieU/EsTzKFqclVA6Axaf0KND00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LPQAZ/btsLj0GkieU/EsTzKFqclVA6Axaf0KND00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLPQAZ%2FbtsLj0GkieU%2FEsTzKFqclVA6Axaf0KND00%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;1226&quot; height=&quot;194&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Double Write Buffer&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파셜 페이지(Partial-page), 톤 페이지(Torn-page)라는 용어가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어의 오작동이나 시스템의 비정상종료 시 페이지중 일부만 기록되는 현상인데 이는 InnoDB 스토리지 엔진에서 디터 페이지를 디스크 파일로 플러시 할때 일부만 기록되기 떄문에 발생합니다.&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;InnoDB엔진에서는 Double-Write 기법을 사용하니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;버퍼풀애서 더티페이지를 디스크에 쓰기 전에 데이터페이지들을 적당한 위치에 랜덤I/O 방식으로 먼저 DoubleWrite 버퍼에 기록합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 버퍼풀에서 디스크에 쓰기가 성공하면 DoubleWrite 버퍼에 저장해두었던 내용은 사라집니다.&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;재시작 시 DoubleWrite 버퍼에 있는 내용과 데이터 파일의 페이지 내용을 비교해서 다른 내용이 있으면 DoubleWrite 버퍼의 내용을 데이터 파일의 페이지로 복사합니다.&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;하지만 SSD와 같이 랜덤I/O가 일어나는 저장시스템에서는 해당 기능의 비용이 다소 들 수도 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;언두로그 (Undo Log)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진은 트래잭션과 격리수준을 보장하기 위해 DML로 변경되기 이전 데이터를 별도로 백업해둡니다.&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;언두로그는 트랜잭션 보장과 서비스의 격리수준을 보장하기 위해 사용됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xWpcv/btsLiVlWmYM/ak1zMTgAvwJT7R5YAzSVD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xWpcv/btsLiVlWmYM/ak1zMTgAvwJT7R5YAzSVD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xWpcv/btsLiVlWmYM/ak1zMTgAvwJT7R5YAzSVD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxWpcv%2FbtsLiVlWmYM%2Fak1zMTgAvwJT7R5YAzSVD0%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;1022&quot; height=&quot;114&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 DML이 실행되면 트랜잭션을 커밋하지 않아도 실제 데이터와 인덱스 버퍼에는 UPDATE-MySQL-USER라는 이름으로 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 변경되기 전 데이터(MySQL-USER)는 언두영역에 백업됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태로 COMMIT이 일어나면 상태가 유지되고,&amp;nbsp; (언두로그가 바로 삭제되지는 않고 삭제예정 처리(Prune) 됨, 백그라운드 작업으로 삭제)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROLLBACK되면 언두영역의 백업 데이터를 원래대로 되돌립니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DML 쿼리를 통해 트랜잭션에 적용되는 레코드들이 많으면 언두로그에 그만큼의 레코드들이 쌓이게 되어 사용량이 늘어나게됩니다.&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;MySQL에서 활성상태의 트랜잭션이 장기간 유지되는건 좋지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MySQL 서버의 언두레코드를 모니터링하는것은 반드시 필요합니다.&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;pre id=&quot;code_1734228621481&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT count
FROM information_schema.innodb_metrics
WHERE SUBSYSTEM='transaction' AND NAME = 'trx_rseg_history_len';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;언두 테이블스페이스 관리&lt;/h4&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; 언두 테이블 스페이스는 1 ~ 128개의 롤백 세그먼트를 가지며, 롤백 세그먼트는 1개 이상의 언두슬롯을 가집니다.&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;하나의 롤백 세그먼트는 InnoDB 페이지 크기를 16바이트로 나눈 값의 개수만큼 언두 슬롯을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 페이지 크기가 16KB라면 롤백 세그먼트는 1024개의 언두슬롯을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 트랜잭션이 필요로하는 언두슬롯 개수는 최대 4개입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 대략적으로 2개정도로 계산해서 최대 통시 처리 가능 트랜잭션 수를 예측 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734234130590&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;최대 동시 트랜잭션 수 = (InnoDB 페이지 크기) / 16 * (롤백 세그먼트 개수) * (언두 테이블스페이스 개수)&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;기본 세팅에서는 대략 131072(16*1024/16*128/2)개의 트랜잭션이 동시에 처리 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 명령어로 언두 테이블 스페이스를 동적으로 생성 및 조절할수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734234382569&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 언두 테이블 스페이스 생성
CREATE UNDO TABLESAPCE
-- 언두 테이블 스페이스 삭제
DROP UNDO TABLESPACE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;체인지 버퍼&lt;/h4&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;그렇기에&lt;b&gt; InnoDB엔진은 변경해야할 인덱스 페이지가 버퍼풀에 있으면 즉시 업데이트를 수행하지만&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇지 않고 디스크에서부터 읽어서 변경해야한다면 즉시 실행하지 않고 임시공간에 저장해둔뒤 먼저 사용자에게 결과를 반환하고 추후 작업을 하게됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 사용하는 임시공간을 체인지버퍼라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 체인지 버퍼는 InnoDB 버퍼풀로 설정된 공간에 25%까지 사용 가능합니다. (innodb_change_buffer_max_size 변수를 통해 50%까지 조정 가능)&lt;/p&gt;
&lt;pre id=&quot;code_1734239527108&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED
FROM performance_schema.memory_summary_global_by_event_name
WHERE EVENT_NAME = 'memory/innodb/ibuf0ibuf'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lQWea/btsLj9wvP5u/t5DUH6UAYBuVOtuhxaEdKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lQWea/btsLj9wvP5u/t5DUH6UAYBuVOtuhxaEdKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lQWea/btsLj9wvP5u/t5DUH6UAYBuVOtuhxaEdKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlQWea%2FbtsLj9wvP5u%2Ft5DUH6UAYBuVOtuhxaEdKk%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;1300&quot; height=&quot;538&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어뎁티브 해시 인덱스 (Adaptive Hash Index)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자가 직접 생성하는 B-Tree 인덱스가 아닌 InnoDB스토리지 엔진에서 사용자가 자주 요청하는 데이터에 대해서 자동으로 생성하는 메모리 기반 해시 인덱스입니다.&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;어뎁티브 해시 인덱스가 생성된 후 어뎁티브 해시 인덱스를 사용할 수 있는 요청이 발생하면 B-Tree인덱스 대신 어뎁티브 해시 인덱스를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 B-Tree의 루트 노드에서 리프노드까지 찾아가는 비용이 줄어들고 CPU의 역할이 줄어들어 쿼리의 성능을 향상시킵니다.&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;어뎁티브 해시 인덱스는 InnoDB엔진에서 하나만 존재하며 (파티션은 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'인덱스 키 값'과 해당 인덱스 키 값이 저장된 '데이터 페이지 주소'의 쌍으로 관리되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 키 값은 'B-Tree 인덱스의 고유번호(Id)'와 'B-Tree 인덱스의 실제 키 값'의 조합으로 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 키에 B-Tree 인덱스의 고유번호(Id)를 가지고 있는 이유는 'B-Tree 인덱스의 실제 키 값'이 어떤 B-Tree인덱스에 있는지 구분하기 위함입니다.&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;어뎁티브 해시 인덱스의 value값인 해당 인덱스 키 값이 저장된 '데이터 페이지 주소는 실제 키 값이 저장된 데이터 페이지의 메모리 주소를 가지는데, 이는 InnoDB 버퍼풀에 로딩된 페이지의 주소를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 어뎁티브 해시 인덱스는 InnoDB 버퍼풀에 로딩된 페이지에 대하서만 관리됩니다.&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;어뎁티브 해시 인덱스는 innodb_adaptive_hash_index 변수를 통해 비활성화가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어뎁티브 해시 인덱스가 도움이 되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 디스크의 데이터가 InnoDB 버퍼풀 크기와 비슷한 경우 (디스크 읽기가 많지 않은 경우)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 동등 조건 검색이 많은 경우 (동등비교나 IN)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 쿼리가 데이터 중 일부 데이터에 집중되는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어뎁티브 해시 인덱스가 도움이 되지 않는 경우&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 디스크 읽기가 많은 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 특정 패턴의 쿼리가 많은 경우 (join이나 like)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 매우 큰 데이터를 가진 테이블의 레코드를 폭 넓게 읽는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 통해 InnoDB 상태를 확인해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INSERT BUFFER AND ADAPTIVE HASH INDEX 항목을 볼 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 hash searches/s + non-hash searches/s 초당 번의 검색이 있었는데&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;hash searches/s&lt;span&gt; 만큼 어뎁티브 해시 인덱스가 실행되었다는 내용입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1734246169309&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;show engine INNODB status\G&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1734246258859&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 2 buffer(s)
Hash table size 34679, node heap has 2 buffer(s)
Hash table size 34679, node heap has 3 buffer(s)
Hash table size 34679, node heap has 1 buffer(s)
Hash table size 34679, node heap has 1 buffer(s)
Hash table size 34679, node heap has 1 buffer(s)
Hash table size 34679, node heap has 1 buffer(s)
5.03 hash searches/s, 15.24 non-hash searches/s&lt;/code&gt;&lt;/pre&gt;</description>
      <category>DB</category>
      <category>InnoDB</category>
      <category>innodb 버퍼풀</category>
      <category>innodb구조</category>
      <category>MySQL</category>
      <category>어뎁티브 해시 인덱스</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/81</guid>
      <comments>https://jaykos96.tistory.com/81#entry81comment</comments>
      <pubDate>Thu, 12 Dec 2024 21:39:26 +0900</pubDate>
    </item>
    <item>
      <title>MySQL엔진의 아키텍쳐를 알아보자</title>
      <link>https://jaykos96.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어를 만들때 아키텍쳐가 정말 중요한 부분이라는 생각이 점점 커지는 요즘입니다!&lt;br&gt;그래서 Real MySQL 을 읽고 공부한 MySQL과 InnoDB의 엔진 아키텍쳐를 정리해보고자 합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;MySQL 아키텍쳐&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLdDY/btsLdy5rmNu/Vjucly6AfwwvyuiDVBWzJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLdDY/btsLdy5rmNu/Vjucly6AfwwvyuiDVBWzJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLdDY/btsLdy5rmNu/Vjucly6AfwwvyuiDVBWzJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLdDY%2FbtsLdy5rmNu%2FVjucly6AfwwvyuiDVBWzJ0%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;721&quot; height=&quot;724&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;MySQL은 MySQL 엔진 + 스토리지 엔진 두가지 엔진으로 이루어져 있습니다.&lt;/b&gt;&lt;br&gt;이 둘을 합하여 MySQL, MySQL서버라고 부릅니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;MySQL 엔진&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트로 부터 접속 및 쿼리 요청을 처리하는 커넥션 핸들러와 SQL 파서 및 전처리기, 쿼리 최적화 실행을 위한 옵티마이저가 존재합니다.&lt;br&gt;데이터를 디스크에 저장하는 작업 외 전반적인 MySQL기능들은 대부분 MySQL엔진에서 이루어집니다.&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;요청된 SQL을 분석하거나 최적화 하는 중 DBMS의 핵심입니다.&lt;br&gt;실제 데이터를 디스크 스토리지에 저장하거나 디스크 스토리지로 부터 데이터를 읽어옵니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- ENGIND 파라미터로 스토리지 엔진을 지정 할 수 있습니다.
CREATE TABLE test_table (fd1 INT, fd2 INT) ENGINE=INNODB;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;핸들러API&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL엔진은 스토리지 엔진에 읽기 또는 쓰기를 요청하게되는데 이를 핸들로 요청이라 합니다.&lt;br&gt;이때 사용되는 API를 핸들러 API라고 합니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 핸들러 API 확인 명령어
SHOW GLOBAL STATUS LIKE 'Handler%';&lt;/code&gt;&lt;/pre&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;1230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c67NAs/btsLetWnMRu/zsJKT6cn9xq43P2r3uzdok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c67NAs/btsLetWnMRu/zsJKT6cn9xq43P2r3uzdok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c67NAs/btsLetWnMRu/zsJKT6cn9xq43P2r3uzdok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc67NAs%2FbtsLetWnMRu%2FzsJKT6cn9xq43P2r3uzdok%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;548&quot; height=&quot;766&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;1230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #383A42;&quot;&gt;MySQL 스레딩 구조&lt;/span&gt;&lt;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cb68Q/btsLecnccQq/rxBiH6SDjwgeXRokceD821/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cb68Q/btsLecnccQq/rxBiH6SDjwgeXRokceD821/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cb68Q/btsLecnccQq/rxBiH6SDjwgeXRokceD821/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCb68Q%2FbtsLecnccQq%2FrxBiH6SDjwgeXRokceD821%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;456&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;MySQL은 스레드 기반으로 동작하며 포그라운드 스레드 백그라운드 스레드로 구분됩니다.&lt;/b&gt;&lt;br&gt;아래 명령어로 실행중인 스레드 목록을 확인 할 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;48번 스레드, thread/sql/one_connection 스레드만 실제 사용자의 요청을 처리하는 포그라운드 스레드입니다.&lt;br&gt;동일한 이름의 스레드가 있는 이유는 동일한 작업을 병렬로 처리하기 떄문입니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT thread_id, name, type, processlist_user, processlist_host
FROM performance_schema.threads ORDER BY type, thread_id;&lt;/code&gt;&lt;/pre&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;1202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BlV2f/btsLde0yrlS/mZ6mN5azOYEr1WOm9723Ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BlV2f/btsLde0yrlS/mZ6mN5azOYEr1WOm9723Ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BlV2f/btsLde0yrlS/mZ6mN5azOYEr1WOm9723Ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBlV2f%2FbtsLde0yrlS%2FmZ6mN5azOYEr1WOm9723Ck%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;820&quot; height=&quot;493&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;1202&quot;/&gt;&lt;/span&gt;&lt;/figure&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;b&gt;MySQL서버에 접속된 클라이언트 수만큼 존재하며 클라이언트가 요청하는 쿼리 문장을 처리합니다.&lt;/b&gt;&lt;br&gt;클라이언트와의 커넥션이 종료되면 해당 커넥션을 담당하는 스레드는 다시 스레드 캐시(Thread cache)로 돌아갑니다.&lt;br&gt;만약 스레드 캐시가 가득 차있다면 스레드는 종료됩니다. (thread_cache_size로 캐시 수를 설정합니다.)&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;InnoDB에서는 아래와 같은 일들이 백그라운드 스레드로 처리됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;- 인서트 버퍼(Insert Buffer) 병합&lt;br&gt;&lt;b&gt;- 디스크에 로그 기록 (Log Thread 사용)&lt;/b&gt;&lt;br&gt;&lt;b&gt;- 디스크에 InnoDB 버퍼 풀의 데이터 기록 (Write Thread)&lt;/b&gt;&lt;br&gt;- 데이터를 버퍼로 읽기&lt;br&gt;- Lock, DeadLock 모니터링&lt;br&gt;&amp;nbsp;&lt;br&gt;InnoDB에서도 데이터를 읽는 작업은 주로 클라이언트 스레드에서 처리하지만 버퍼 풀의 데이터를 디스크에 기록하는 Write Thread는 아주 많고 중요한 작업을 처리하기 때문에 충분히 설정해야합니다. (내장 디스크 2 ~ 4 , DAS나 SAN의 경우 디스크 최적량으로)&lt;br&gt;&amp;nbsp;&lt;br&gt;InnoDB에서 쓰기 작업은 버퍼링하여 디스크에 일괄적으로 저장되도록 처리됩니다.&lt;br&gt;&lt;b&gt;만약 INSERT, UPDATE, DELETE 쿼리로 데이터가 변경되는 경우 InnoDB 버퍼풀에 존재하였다가 Write Thread를 통해 주기적으로 또는 특정 시점에 데이터가 디스크 파일로 완전히 저장 됩니다.&lt;/b&gt;&lt;br&gt;디스크에는 존재하지 않고 버퍼풀에만 데이터가&amp;nbsp; 존재할 때 'Dirty Page'로 표시되며 읽기 요청이 들어오면 버퍼풀에서 즉시 제공됩니다.&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;866&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9pbft/btsLdwT4lqH/4c2P6neyRKKDKrl4Gs9OmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9pbft/btsLdwT4lqH/4c2P6neyRKKDKrl4Gs9OmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9pbft/btsLdwT4lqH/4c2P6neyRKKDKrl4Gs9OmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9pbft%2FbtsLdwT4lqH%2F4c2P6neyRKKDKrl4Gs9OmK%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;778&quot; height=&quot;467&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 메모리 영역, 로컬 커넥션 영역으로 구분됩니다.&lt;br&gt;글로벌 메모리 영역은 MySQL 서버가 시작되면서 OS로 부터 할당됩니다. (MySQL 시스템 변수로 설정해둔 만큼 할당받습니다.)&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;b&gt;하나의 메모리 공간만 할당됩니다.&lt;/b&gt;&lt;br&gt;필요에 따라 2개 이상도 가능하지만 생성된 N개의 글로벌 메모리 영역은 모든 스레드(포그라운드 + 백그라운드 스레드)에 의해 공유됩니다.&lt;br&gt;버퍼링, 캐싱, 로그기록, 백그라운드 작업에 사용되는 영역입니다.&lt;br&gt;아래는 대표적인 글로벌 메모리 영역입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;- 테이블 캐시&lt;br&gt;- InnoDB 버퍼 풀 (InnoDB의 주요 캐싱 메커니즘으로, 데이터와 인덱스 페이지를 디스크에서 읽어와 메모리에 캐싱)&lt;br&gt;- InnoDB 어댑티브 해시 인덱스 (InnoDB 버퍼 풀에 저장된 데이터에서 자주 사용되는 인덱스를 해시 구조로 관리)&lt;br&gt;- InnoDB 리두 로그 버퍼 (데이터 변경 작업에 대한 로그를 기록)&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;클라이언트 스레드가 쿼리를 처리하는데 사용하는 메모리 영역입니다.&amp;nbsp;&lt;br&gt;클라이언트 메모리 영역, 세션 메모리 영역이라고도 말합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;클라이언트 별로 독립적으로 메모리가 할당되며 공유되지 않습니다.&lt;br&gt;&lt;b&gt;쿼리의 용도별로 필요한 공간이 할당되고 필요하지 않은 경우에는 메모리 공간을 할당하지 않을수도 있습니다.&lt;/b&gt;&lt;br&gt;커넥션이 열려있는 동안에만 계속 할당되어 있는 공간 (커넥션 버퍼, 결과 버퍼), 쿼리를 실행하는 순간에만 할당했다가 다시 해제하는 공간 (소트 버퍼, 조인 버퍼)도 있습니다.&lt;br&gt;커넥션이 종료되면 할당된 모든 로컬 메모리는 해제됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;대표적으로 아래와 같은 곳에서 사용됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;- 커넥션 버퍼 (클라이언트와 서버 간의 데이터 통신을 위한 기본 네트워크 버퍼)&lt;br&gt;- 정렬 버퍼(Sort Buffer)&lt;br&gt;- 바이너리 로그 캐시 (트랜잭션이 커밋되기 전에 로그를 디스크에 기록하기 전 임시로 저장)&lt;br&gt;- 네트워크 버퍼 (쿼리를 처리할 때 클라이언트에서 받은 데이터가 먼저 네트워크 버퍼에 저장)&lt;br&gt;&amp;nbsp;&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;사용자들의 요구를 만족시키기 위해 MySQL서버에 기능을 확장하기 위한 모델&lt;br&gt;컴포넌트는 플러그인 아키텍쳐의 단점 (플러그인간 통신 불가, 캡슐화 안됨 등)을 보안하고 나온 차세대 아키텍쳐입니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcpB4E/btsLcLqVm96/pHrrqvnkE3KOtmVeKz2mo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcpB4E/btsLcLqVm96/pHrrqvnkE3KOtmVeKz2mo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcpB4E/btsLcLqVm96/pHrrqvnkE3KOtmVeKz2mo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcpB4E%2FbtsLcLqVm96%2FpHrrqvnkE3KOtmVeKz2mo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;334&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;1026&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwbJVl/btsLerjW4CL/DShKjqlQnVFVTTqCvoK5g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwbJVl/btsLerjW4CL/DShKjqlQnVFVTTqCvoK5g1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwbJVl/btsLerjW4CL/DShKjqlQnVFVTTqCvoK5g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwbJVl%2FbtsLerjW4CL%2FDShKjqlQnVFVTTqCvoK5g1%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;678&quot; height=&quot;456&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿼리 파서&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 요청으로 들어온 &lt;b&gt;쿼리 문장을 토큰으로 분리해 트리형태의 구조로 만들어냅니다.&lt;/b&gt;&lt;br&gt;기본 문법 오류를 필터링하고 오류 메세지를 전달합니다.&lt;br&gt;&lt;br&gt;&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;b&gt;각 토큰을 테이블, 컬럼 이름 또는 내장 함수와 같은 객체를 매핑하여 각 존재여부와 객체의 접근 권한을 확인합니다.&lt;/b&gt;&lt;br&gt;존재하지 않는 테이블이나 컬럼, 함수에 접근한다면 전처리기에서 걸러집니다.&lt;br&gt;&lt;br&gt;&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;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 엔진&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;옵티마이저가 계획한 방법을 실행하기 위해 각 핸들러(스토리지 엔진)에게 필요한 작업들을 요청 후 최종 결과를 사용자나 모듈로 전달합니다.&amp;nbsp;&lt;/b&gt;&lt;br&gt;실제 DB내의 데이터를 얻기 위한 인터페이스라고 생각하시면 좋을 것 같습니다.&lt;br&gt;예를 들어 임시테이블 생성, WHERE조건에 일치하는 레코드 읽기, 읽어온 데이터를 임시테이블 저장, 최종결과 반환 과 같은 작업들을 각 핸들러에게 요청후 각 결과들을 취합하여 반환합니다.&lt;br&gt;&lt;br&gt;&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;br&gt;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;복제 (Replication)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;복제(Replication)는 1개 이상의 레플리카(replica) 저장소가 소스 저장소와 동기화를 자동으로 유지하는 과정입니다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;source-replica구조로 데이터의 백업이나 보호, 분산저장 등의 목적으로 사용되는 기능입니다.&lt;br&gt;&lt;/span&gt;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;스레드 풀&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;(엔터프라이즈 버전은 지원하지만 커뮤니티 버전 지원 X, 플러그인 형태로 추가하여 사용은 가능)&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;사용자의 요청을 처리하는 스레드 수를 줄여서 동시 처리하는 요청이 많아지더라고 MySQL서버의 CPU가 제한된 개수의 스레드 처리에만 집중 할 수 있게 서버의 자원을 줄이는 것이 목적입니다.&lt;/b&gt;&lt;br&gt;기본적으로 코어의 수만큼 스레드를 생성합니다. (thread_pool_size 시스템 변수로 관리)&lt;br&gt;thread_pool_size의 스레드가 모두 사용중일때에는 thread_pool_oversubscribe에 지정된 수(기본값3) 만큼 풀에 스레드를 추가로 받아들여 처리합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;스레드 풀의 타이머 스레드는 주기적으로 스레드 그룹의 상태를 체크하여 thread_pool_stall_limit 변수에 지정된 밀리초만큼 작업 스레드가 지금 처리중인 작업을 끝내지 못하면 새로운 스레드를 생성해서 스레드 그룹에 추가합니다. (thread_pool_max_threads 크기를 넘을수는 없음)&lt;br&gt;&lt;b&gt;즉 스레드의 새로운 요청이 들어온다면 무조건 thread_pool_stall_limit 만큼은 요청이 대기되기에 적절한 값을 설정하는것이 중요합니다.&lt;/b&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;테이블의 구조정보와 스토어드 프로그램(프로시저, 트리거, 함수 ,,, ) 등 의 정보를 메타데이터 라고합니다.&lt;br&gt;MySQL서버가 동작하기 위해 기본적으로 필요한 테이블을 시스템 테이블 이라고 하는데, MySQL 8.0이후 에서는 시스템 테이블과 메타데이터 정보를 모두 모아 InnoDB 스토리지 엔진의 mysql DB에 저장됩니다. (mysqlDB는 통째로 mysql.ibd라는 테이블 스페이스에 저장됩니다.)&lt;br&gt;그렇기에 테이블 구조가 변경되는 스키마 작업같은 경우에 MySQL서버가 비정상 종료가 되어도 완전한 성공, 완전한 실패로 정리됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;추가로 mysqlDB의 시스템테이블과 메타데이터를 확인하려면 information_schema DB의 TABLES, COLUMN과 같은 VIEW를 통해 조회할 수 있습니다.&lt;br&gt;mysql DB에 실제로 존재하지만 해당 DB가 아닌 다른 DB의 view를 이용해 확인해야합니다.&lt;/p&gt;&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt; 
 &lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;select * from information_schema.tables;
&lt;/code&gt;&lt;/pre&gt; 
&lt;/div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2094&quot; data-origin-height=&quot;1136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgr5Xq/btsLfXEvjwY/HI79ZB9teRQo9IuxxcrRw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgr5Xq/btsLfXEvjwY/HI79ZB9teRQo9IuxxcrRw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgr5Xq/btsLfXEvjwY/HI79ZB9teRQo9IuxxcrRw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgr5Xq%2FbtsLfXEvjwY%2FHI79ZB9teRQo9IuxxcrRw0%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;2094&quot; height=&quot;1136&quot; data-origin-width=&quot;2094&quot; data-origin-height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;</description>
      <category>DB</category>
      <category>DBMS</category>
      <category>MySQL</category>
      <category>mysql 구조</category>
      <category>mysql 아키텍쳐</category>
      <category>mysql 엔진</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/80</guid>
      <comments>https://jaykos96.tistory.com/80#entry80comment</comments>
      <pubDate>Tue, 10 Dec 2024 20:43:19 +0900</pubDate>
    </item>
    <item>
      <title>B-Tree 알고리즘</title>
      <link>https://jaykos96.tistory.com/79</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;B-Tree란?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-tree는 Self-balanced Tree 중 가장 유명한 자료구조입니다. Balanced-tree를 의미하며,&amp;nbsp;&lt;br /&gt;이진트리를&amp;nbsp;확장해&amp;nbsp;하나의&amp;nbsp;노드가&amp;nbsp;가질&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;자식&amp;nbsp;노드의&amp;nbsp;최대&amp;nbsp;숫자가&amp;nbsp;2보다&amp;nbsp;큰&amp;nbsp;트리&amp;nbsp;구조입니다.&lt;br /&gt;최소차수는&amp;nbsp;자식수의&amp;nbsp;하한값을&amp;nbsp;의미하며,&amp;nbsp;최소차수가&amp;nbsp;t라면&amp;nbsp;M=2t&amp;minus;1을&amp;nbsp;만족합니다.&amp;nbsp;&lt;br /&gt;&lt;b&gt;최대&amp;nbsp;M개의&amp;nbsp;자식을&amp;nbsp;가질&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;B-Tree를&amp;nbsp;M차&amp;nbsp;B-Tree&amp;nbsp;라고&amp;nbsp;합니다.&lt;/b&gt;&lt;br /&gt;만약 최소차수 t가 2라면 3차 B트리이며, 데이터(key)의 하한은 1개입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUIPZP/btsKv4DOVn1/qCq6F9W3KgLVQqiMfkB98K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUIPZP/btsKv4DOVn1/qCq6F9W3KgLVQqiMfkB98K/img.png&quot; data-alt=&quot;https://blocksandfiles.com/2022/04/19/b-tree/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUIPZP/btsKv4DOVn1/qCq6F9W3KgLVQqiMfkB98K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUIPZP%2FbtsKv4DOVn1%2FqCq6F9W3KgLVQqiMfkB98K%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;1426&quot; height=&quot;532&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://blocksandfiles.com/2022/04/19/b-tree/&lt;/figcaption&gt;
&lt;/figure&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;B-Tree의 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree는 아래와 같은 특징을 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드의 데이터(key)는 항상 정렬된 상태로 저장됩니다.&lt;/li&gt;
&lt;li&gt;자식 노드의 데이터는 부모 노드의 데이터에 따라 정렬되어 나뉘어집니다.&lt;/li&gt;
&lt;li&gt;노드는 최소 M/2-1 ~ M-1개의 데이터(KEY)를 가집니다, M/2가 소수로 나온다면 올림처리&lt;/li&gt;
&lt;li&gt;노드는 최대 M/2 ~ M개의 자식노드를 가집니다, M/2가 소수로 나온다면 올림처리&lt;/li&gt;
&lt;li&gt;내부노드의 데이터가 k개 라면 자식노드의 수는 언제나 k+1개입니다.&lt;/li&gt;
&lt;li&gt;모든 리프노드의 높이는 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;B-Tree 탐색 과정&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 루트 노드에서 탐색 시작합니다.&lt;br /&gt;2.&amp;nbsp;K를&amp;nbsp;찾았다면&amp;nbsp;탐색을&amp;nbsp;종료합니다.&lt;br /&gt;3.&amp;nbsp;K와&amp;nbsp;노드의&amp;nbsp;데이터(KEY)를&amp;nbsp;비교하여&amp;nbsp;적절한&amp;nbsp;자식노드로&amp;nbsp;내려갑니다.&lt;br /&gt;4.&amp;nbsp;해당&amp;nbsp;과정을&amp;nbsp;리프노드에&amp;nbsp;도달할&amp;nbsp;때&amp;nbsp;까지&amp;nbsp;반복하며&amp;nbsp;리프노드에서도&amp;nbsp;K를&amp;nbsp;찾지&amp;nbsp;못하면&amp;nbsp;값이&amp;nbsp;없다고&amp;nbsp;판단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 루트 노드에서 탐색을 시작합니다. (K : 46)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AyOM3/btsKve1qfLb/YSxxGSjnmUlqK1YZclZcFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AyOM3/btsKve1qfLb/YSxxGSjnmUlqK1YZclZcFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AyOM3/btsKve1qfLb/YSxxGSjnmUlqK1YZclZcFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAyOM3%2FbtsKve1qfLb%2FYSxxGSjnmUlqK1YZclZcFK%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;860&quot; height=&quot;278&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. K는 루트노드 보다 큰 값이기에 왼쪽 자식으로 이동합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fighw/btsKvMwBk4r/XYcN93KZdFFKBD7xapSrI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fighw/btsKvMwBk4r/XYcN93KZdFFKBD7xapSrI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fighw/btsKvMwBk4r/XYcN93KZdFFKBD7xapSrI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFighw%2FbtsKvMwBk4r%2FXYcN93KZdFFKBD7xapSrI1%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;864&quot; height=&quot;282&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;282&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;3. K는 현재노드의 값들 사이에 있는 값이기에 가운데 자식노드로 이동합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qOO1r/btsKv5QeIqn/CzRAicansx4Oy2hl2gKDU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qOO1r/btsKv5QeIqn/CzRAicansx4Oy2hl2gKDU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qOO1r/btsKv5QeIqn/CzRAicansx4Oy2hl2gKDU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqOO1r%2FbtsKv5QeIqn%2FCzRAicansx4Oy2hl2gKDU1%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;896&quot; height=&quot;284&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;284&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;4. 리프노드에서 K을 찾아냅니다. 만약 K가 없다면 찾는 값(K)이 없다고 판단합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/whFVr/btsKxpGIhBz/zsTDlSSzGcse5O0qGsnOc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/whFVr/btsKxpGIhBz/zsTDlSSzGcse5O0qGsnOc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/whFVr/btsKxpGIhBz/zsTDlSSzGcse5O0qGsnOc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwhFVr%2FbtsKxpGIhBz%2FzsTDlSSzGcse5O0qGsnOc0%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;918&quot; height=&quot;296&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;B-Tree 데이터 삽입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 데이터 삽입은 항상 리프노드에서 시작되며&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같은 순서로 B-Tree에 데이터 삽입이 이루어집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1.&amp;nbsp;트리가&amp;nbsp;비어있다면&amp;nbsp;루트노드를&amp;nbsp;할당하고&amp;nbsp;K를&amp;nbsp;삽입합니다.&lt;br /&gt;2.&amp;nbsp;트리가&amp;nbsp;비어있지&amp;nbsp;않다면,&amp;nbsp;데이터를&amp;nbsp;넣을&amp;nbsp;적절한&amp;nbsp;리프노드를&amp;nbsp;탐색합니다.&lt;br /&gt;3.&amp;nbsp;리프&amp;nbsp;노드에&amp;nbsp;데이터를&amp;nbsp;넣고&amp;nbsp;리프&amp;nbsp;노드가&amp;nbsp;적절한&amp;nbsp;상태에&amp;nbsp;있다면&amp;nbsp;종료한다.&lt;br /&gt;4. 리프 노드가 부적절한 상태에 있다면 분할한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가로 B-Tree에 데이터가 삽입 될 때 상황은 2가지로 나뉘어집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노드가 부적절한 상태가 있어 '분할이 일어나는 경우', 적절한 상태에 있어 '분할이 일어나지 않는 경우' 입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;적절한 상태?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree의 조건(특징)을 위배하지 않는 상태를 의미합니다.&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;노드를 분할한다?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드가 가질 수 있는 최대 데이터(KEY) 수(M-1)가 초과되면 가운데 데이터를 기준으로 좌우 데이터를 분할하고 가운데 데이터는 부모 노드로 올립니다.&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;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&amp;nbsp; 먼저 K가 들어갈 리프노드를 탐색합니다. (K : 16) 트리는 3차 B-Tree입니다&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JfARE/btsKvZo87tT/pmX3HobVOhjMI6HgPot021/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JfARE/btsKvZo87tT/pmX3HobVOhjMI6HgPot021/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JfARE/btsKvZo87tT/pmX3HobVOhjMI6HgPot021/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJfARE%2FbtsKvZo87tT%2FpmX3HobVOhjMI6HgPot021%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;282&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;282&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;2. K를 추가하여도 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;해당 리프노드는 적절한 상태에 있기에 그대로 K를 추가합니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBOKaE/btsKvbw4TFa/l8BRAVkGNKsvTz2Zg4RVV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBOKaE/btsKvbw4TFa/l8BRAVkGNKsvTz2Zg4RVV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBOKaE/btsKvbw4TFa/l8BRAVkGNKsvTz2Zg4RVV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBOKaE%2FbtsKvbw4TFa%2Fl8BRAVkGNKsvTz2Zg4RVV1%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;526&quot; height=&quot;294&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;294&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;h4 data-ke-size=&quot;size20&quot;&gt;분할이 일어나는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적절한 위치의 노드로 찾아간 후 데이터가 추가되었을때 노드 분할이 필요 한 경우에는&lt;br /&gt;해당 노드 데이터의 중앙값(median)을 부모 노드로 올립니다.&lt;br /&gt;부모노드에서도 노드 분할이 필요한 경우 동일한 과정을 반복합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&amp;nbsp; K가 들어갈 루트노드를 탐색합니다 (K:13) 트리는 3차 B-Tree입니다&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTs9OX/btsKwONNVhr/MgBUyQxUXGs81BSfoVkJx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTs9OX/btsKwONNVhr/MgBUyQxUXGs81BSfoVkJx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTs9OX/btsKwONNVhr/MgBUyQxUXGs81BSfoVkJx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTs9OX%2FbtsKwONNVhr%2FMgBUyQxUXGs81BSfoVkJx0%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;862&quot; height=&quot;278&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;278&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;2. 해당 루트노드에 K를 추가합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K가 추가되었을 때 리프노드의 데이터는 최소 데이터 수 (M-1)개를 초과하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 부적절한 상태가 되기에 노드분할을 실시합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cszi73/btsKvKyNzpr/CWMyxlD5fJI6PIhlkplkbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cszi73/btsKvKyNzpr/CWMyxlD5fJI6PIhlkplkbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cszi73/btsKvKyNzpr/CWMyxlD5fJI6PIhlkplkbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcszi73%2FbtsKvKyNzpr%2FCWMyxlD5fJI6PIhlkplkbk%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;960&quot; height=&quot;298&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;298&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;3. 노드를 분할하여 중앙값을 부모노드로 올리고 리프노드를 분할합니다.&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;894&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnQFYw/btsKvowaF8W/UyAvcKkonoKZTlq1jpLEF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnQFYw/btsKvowaF8W/UyAvcKkonoKZTlq1jpLEF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnQFYw/btsKvowaF8W/UyAvcKkonoKZTlq1jpLEF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnQFYw%2FbtsKvowaF8W%2FUyAvcKkonoKZTlq1jpLEF1%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;894&quot; height=&quot;292&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;B-Tree 데이터 삭제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 데이터 삭제 또한 항상 리프노드에서 시작됩니다.&lt;br /&gt;&lt;b&gt;삭제 후 노드의 데이터 수가 최소 데이터수(M/2-1)보다 적어졌다면 트리를 재구조화 합니다.&lt;/b&gt;&lt;br /&gt;루트 노드의 데이터 수는 최소 KEY수(M/2-1)를 만족하지 않아도 재구조화 하지 않습니다.&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;&lt;b&gt;데이터를 삭제했을때 노드의 '최소 데이터수 (M/2-1)' 보다 작아지게 된다면 트리의 재구조화가 필요합니다.&lt;/b&gt;&lt;br /&gt;이럴때&amp;nbsp;형제,&amp;nbsp;부모&amp;nbsp;노드의&amp;nbsp;값들을&amp;nbsp;빌려와&amp;nbsp;재구조화를&amp;nbsp;진행하게됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 리프노드의 데이터가 삭제 될 때&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리프노드의 삭제에서는 삭제 후 노드가 적절한 상태에 있을 때, 적절하기 않고 형제노드의 지원을 받을 수 있을 때&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;적절하지 않고 형제노드의 지원을 받을 수 없을 때 세가지 경우가 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1-1 삭제 후 노드가 적절한 상태에 있어 재구조화가 필요하지 않는 경우&amp;nbsp;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;노드에 있는 데이터를 삭제합니다. 추후 별도 작업 X&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-2 삭제 후 적절한 상태에 있지 않아 재구조화가 필요하고 형제노드의 지원을 받을 수 있는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 삭제 후 노드가 '최소 데이터수 (M/2-1)'를 만족하지 못한다면 재구조화를 시작합니다.&lt;br /&gt;먼저&amp;nbsp;노드의&amp;nbsp;형제노드에서&amp;nbsp;지원을&amp;nbsp;받을&amp;nbsp;수&amp;nbsp;있는지&amp;nbsp;확인합니다.&lt;br /&gt;&lt;b&gt;형제노드의&amp;nbsp;데이터가&amp;nbsp;여유있다면&amp;nbsp;**부모노드를&amp;nbsp;통해서&amp;nbsp;형제노드의&amp;nbsp;지원을&amp;nbsp;받습니다.**&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-2-1 . 삭제할 데이터의 노드로 이동합니다. K : 50&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EqN1s/btsKvJfCWkb/zwoCoWSjEjbKi2J8icRLY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EqN1s/btsKvJfCWkb/zwoCoWSjEjbKi2J8icRLY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EqN1s/btsKvJfCWkb/zwoCoWSjEjbKi2J8icRLY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEqN1s%2FbtsKvJfCWkb%2FzwoCoWSjEjbKi2J8icRLY1%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;678&quot; height=&quot;274&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-2-2. 데이터를 삭제하게되면 노드의 '최소 데이터수'를 만족하지 못하기에 데이터가 여유있는 형제노드의 지원을 받습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 형제노드가 여유있다면 왼쪽형제 노드에서, 오른쪽 형제노드가 여유있다면 오른쪽 형제 노드에서 지원을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 상황에서는 왼쪽 형제노드가 여유있기에 왼쪽 형제 노드에서 부모노드를 통해 지원을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왼쪽 형제노드의 가장 큰 값(46)을 부모 노드로 올리고, 부모노드에서 삭제할 K(50)와 형제노드에서 올라온 값(46) 사이의 값(47)을 K가 있던 노드에 지원받습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gqxf4/btsKwzb7yKW/KnPtND5qzBKkMhYfHTkMUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gqxf4/btsKwzb7yKW/KnPtND5qzBKkMhYfHTkMUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gqxf4/btsKwzb7yKW/KnPtND5qzBKkMhYfHTkMUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGqxf4%2FbtsKwzb7yKW%2FKnPtND5qzBKkMhYfHTkMUK%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;670&quot; height=&quot;290&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;290&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8qGrP/btsKwoouYJg/7vFsycpDAnQCNgkbCZvcx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8qGrP/btsKwoouYJg/7vFsycpDAnQCNgkbCZvcx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8qGrP/btsKwoouYJg/7vFsycpDAnQCNgkbCZvcx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8qGrP%2FbtsKwoouYJg%2F7vFsycpDAnQCNgkbCZvcx0%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;578&quot; height=&quot;284&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1-2-3 삭제 후 적절한 상태에 있지 않아 재구조화가 필요하고 형제노드의 지원을 받을 수 없는 경우 -&amp;gt; 부모노드의 지원을 받는다&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 형제노드가 여유가 없어 지원을 받을 수 없다면, 부모노드의 지원을 받아야합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 크게 왼쪽 노드가 존재할때 오른쪽 노드와 존재할때 두가지로 구분됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부모노드의 지원을 받은 후에는 형제노드와 합칩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 왼쪽 형제 노드가 존재할때&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왼쪽 형제 노드가 있다면 왼쪽 형제 노드의 데이터와 나 사이 데이터를 부모노드로 부터 받습니다.&lt;br /&gt;부모로 받은 데이터와 왼쪽 형제 노드의 데이터를 왼쪽 형제 노드에 합칩니다.&lt;br /&gt;나의&amp;nbsp;노드를&amp;nbsp;삭제합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 오른쪽 형제 노드가 &lt;b&gt;존재할때&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오른쪽 형제 노드가 있다면 오른쪽 형제 노드의 데이터와 나 사이 데이터를 부모로부터 받습니다.&lt;br /&gt;부모로 받은 데이터와 오른쪽 형제 노드의 데이터를 내 노드에 합칩니다.&lt;br /&gt;오른쪽의 형제 노드를 삭제합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 만약 부모노드로 부터 지원을 받다가 부모노드가 적절한 상태가 아니개 된다면?&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자식노드의 재조정으로 인해 부모노드가 적절한 상태가 아니게 된다면,&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자식노드의 재조정 처리 후 부모노드에서도 동일한 재조정 과정을 진행합니다. &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 그 부모노드가 루트 노드고 적절한 상태가 아니게 되면 부모 노드, 즉 루트 노드를 삭제합니다.&lt;br /&gt;아래 합쳐진 노드가 루트 노드가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 아래와 같은 B-Tree에서 '데이터 5'를 삭제한다면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8a17a/btsKwzb9aig/kIUw1Z6nezkoxijvjdQ1Y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8a17a/btsKwzb9aig/kIUw1Z6nezkoxijvjdQ1Y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8a17a/btsKwzb9aig/kIUw1Z6nezkoxijvjdQ1Y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8a17a%2FbtsKwzb9aig%2FkIUw1Z6nezkoxijvjdQ1Y0%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;564&quot; height=&quot;290&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;290&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;K 삭제 후 형제노드의 지원을 받아야하지만, 형제 노드가 여유가 없어 부모노드의 지원을 받아야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3ACYQ/btsKv5bGNt4/KhTK1Ke4ycTsxs6oOclrTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3ACYQ/btsKv5bGNt4/KhTK1Ke4ycTsxs6oOclrTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3ACYQ/btsKv5bGNt4/KhTK1Ke4ycTsxs6oOclrTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3ACYQ%2FbtsKv5bGNt4%2FKhTK1Ke4ycTsxs6oOclrTk%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;564&quot; height=&quot;276&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 작업을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 왼쪽 형제 노드가 존재하기에 왼쪽 형제 노드의 데이터와 K 사이 데이터(3) 를 부모노드로 부터 받습니다.&lt;br /&gt;2. 부모로 받은 데이터와 왼쪽 형제 노드의 데이터를 왼쪽 형제 노드에 합칩니다.&lt;br /&gt;3. 나의 노드를 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 부모노드로 부터 값을 받아오게 되니 이번엔 부모노드가 부적절한 상태가 되었습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS68mc/btsKvpBSTxH/wWfcYCsZY21i62dAukx6V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS68mc/btsKvpBSTxH/wWfcYCsZY21i62dAukx6V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS68mc/btsKvpBSTxH/wWfcYCsZY21i62dAukx6V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS68mc%2FbtsKvpBSTxH%2FwWfcYCsZY21i62dAukx6V1%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;576&quot; height=&quot;306&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모노드 기준 오른쪽 형제노드의 데이터가 여유가 있기에 형제 노드로부터 값을 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오른쪽 형제노드의 가장 작은 값(40)을 부모 노드로 올리고, 부모노드에서 사라진 값(3)와 오른쪽 형제노드에서 올라온 값(40) 사이의 값(20)을 지원받습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl5atS/btsKva5Yrka/m1H6w3oqvnOqDl7RPo9Rg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl5atS/btsKva5Yrka/m1H6w3oqvnOqDl7RPo9Rg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl5atS/btsKva5Yrka/m1H6w3oqvnOqDl7RPo9Rg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl5atS%2FbtsKva5Yrka%2Fm1H6w3oqvnOqDl7RPo9Rg0%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;574&quot; height=&quot;300&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 재구조화가 완료되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXVDlU/btsKwPTvA6a/1w4RwgvQffcatuQPKSm6l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXVDlU/btsKwPTvA6a/1w4RwgvQffcatuQPKSm6l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXVDlU/btsKwPTvA6a/1w4RwgvQffcatuQPKSm6l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXVDlU%2FbtsKwPTvA6a%2F1w4RwgvQffcatuQPKSm6l0%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;564&quot; height=&quot;290&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 내부 노드의 데이터 삭제 시&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;내부노드에서 삭제가 일어나는 경우.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제하려는&amp;nbsp;내부노드를&amp;nbsp;리프노드의&amp;nbsp;데이터와&amp;nbsp;위치를&amp;nbsp;바꾼&amp;nbsp;후&amp;nbsp;리프노드의&amp;nbsp;삭제&amp;nbsp;방식과&amp;nbsp;동일하게&amp;nbsp;진행합니다.&lt;br /&gt;내부노드와&amp;nbsp;리프노드의&amp;nbsp;위치를&amp;nbsp;변경&amp;nbsp;할&amp;nbsp;때는&amp;nbsp;리프노드는&amp;nbsp;LMAX,&amp;nbsp;RMIN&amp;nbsp;두&amp;nbsp;값중&amp;nbsp;하나를&amp;nbsp;선택하여&amp;nbsp;변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;- LMAX : 나보다 작은 데이터 중 가장 큰 데이터&lt;br /&gt;- RMIN : 나보다 큰 데이터 중 가장 작은 데이터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 상황의 3차 B-Tree가 있을때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부노드의 데이터 K (20)을 삭제하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/njFA6/btsKDZVSrG9/EZsKuJIHCZR8Skkw8YkaVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/njFA6/btsKDZVSrG9/EZsKuJIHCZR8Skkw8YkaVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/njFA6/btsKDZVSrG9/EZsKuJIHCZR8Skkw8YkaVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnjFA6%2FbtsKDZVSrG9%2FEZsKuJIHCZR8Skkw8YkaVk%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;570&quot; height=&quot;304&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;304&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;삭제할 데이터 K(20)와 LMAX(3)값의 위치를 변경합니다. (K와 RMIN을 변경해도 상관없습니다)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 리프노드에서 K(20)을 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 리프노드에서 K삭제 이후 재구조화가 필요한 상태가 된다면, 리프노드에서의 삭제에서 사용했던 방법과 동일한 방법으로 재구조화를 진행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GfCUX/btsKDQLrgNV/7s2KYDVO05mj03CP5AdbL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GfCUX/btsKDQLrgNV/7s2KYDVO05mj03CP5AdbL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GfCUX/btsKDQLrgNV/7s2KYDVO05mj03CP5AdbL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGfCUX%2FbtsKDQLrgNV%2F7s2KYDVO05mj03CP5AdbL1%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;582&quot; height=&quot;310&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 재구조화가 완료되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;496&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sbaAt/btsKEwlnl8P/JaqCRCKSkJ7WoJnUIFbve1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sbaAt/btsKEwlnl8P/JaqCRCKSkJ7WoJnUIFbve1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sbaAt/btsKEwlnl8P/JaqCRCKSkJ7WoJnUIFbve1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsbaAt%2FbtsKEwlnl8P%2FJaqCRCKSkJ7WoJnUIFbve1%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;496&quot; height=&quot;278&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>DB</category>
      <category>B-tree</category>
      <category>인덱스</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/79</guid>
      <comments>https://jaykos96.tistory.com/79#entry79comment</comments>
      <pubDate>Wed, 30 Oct 2024 21:06:50 +0900</pubDate>
    </item>
    <item>
      <title>MySQL(InnoDB)의 Lock에 대해 알아보자</title>
      <link>https://jaykos96.tistory.com/78</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;MySQL과 InnoDB의 Lock에 대해 알아보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 운영하다보면 서비스의 기능들이 갑자기 멈춰버리는 순간들이 찾아오게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때 사무실에서는 '일단 Lock 걸린거 찾아봐' 라는 말을 먼저 듣게되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 서비스에 Lock이 걸려있었다면 Lock을 풀자마자 서비스가 정상적으로 동작하게되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 이러한 현상을 보았을때 세삼 놀랐던 기억이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발자들이 일하다보면 한번쯤은 꼭 보게되는 RDB의 Lock에 대해 알아보려고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션이랑 Lock이랑 뭐가 달라?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트랜잭션은 '테이터베이스의 상태 변경 작업 완정성을 보장해주는 것' 입니다 &lt;/b&gt;&lt;br /&gt;논리적인&amp;nbsp;작업을&amp;nbsp;모두&amp;nbsp;완벽하게&amp;nbsp;처리(COMMIT)하도록&amp;nbsp;하거나,&amp;nbsp; &lt;br /&gt;처리하지&amp;nbsp;못할&amp;nbsp;경우(ROLLBACK)&amp;nbsp;원&amp;nbsp;상태로&amp;nbsp;복구되어&amp;nbsp;작업의&amp;nbsp;일부만&amp;nbsp;적용되는&amp;nbsp;현상이&amp;nbsp;발생하지&amp;nbsp;않게&amp;nbsp;만들어주는&amp;nbsp;기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉 트랜젝션은 데이터의 정합성을 보장하기 위함입니다.&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;&lt;b&gt;반면 Lock은 동시성을 제어하기 위한 기능입니다.&lt;/b&gt;&lt;br /&gt;예를&amp;nbsp;들어&amp;nbsp;하나의&amp;nbsp;회원정보&amp;nbsp;레코드를&amp;nbsp;여러&amp;nbsp;커넥션에서&amp;nbsp;동시에&amp;nbsp;변경하려&amp;nbsp;할&amp;nbsp;때&amp;nbsp;Lock이&amp;nbsp;없으면&amp;nbsp;하나의&amp;nbsp;데이터를&amp;nbsp;여러&amp;nbsp;커넥션에서&amp;nbsp;동시에&amp;nbsp;변경이&amp;nbsp;가능해집니다. &lt;br /&gt;결과적으로&amp;nbsp;레코드의&amp;nbsp;결과값을&amp;nbsp;예상&amp;nbsp;할&amp;nbsp;수&amp;nbsp;없게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그렇기에 &lt;b&gt;Lock은 여러 커넥션에서 동시에 동일한 자원(레코드나 테이블)을 요청 할 경우 순서대로 한 시점에는 하나의 커넥션만 접근하여 변경 할 수 있게 해주는 역할을 합니다.&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스와 Lock의 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL의 인덱스는 B-Tree 알고리즘으로 만들어져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;MySQL의 InnoDB는 레코드 자체가 아닌 인덱스에 Lock을 설정합니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;B-Tree Index&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;MySQL에서 &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;데이터를 찾을 때는&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Index와 클러스터링 인덱스 (pk)의 &lt;/span&gt;B-Tree를 탐색해서 데이터를 찾게됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa;&quot;&gt;아래 그림은 B-Tree Index의 구조입니다. (Index, PK 생성시 B-Tree가 만들어집니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BNEZ0/btsJUuDJsXx/wDXMRAhAWAzdbOBUk2a3LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BNEZ0/btsJUuDJsXx/wDXMRAhAWAzdbOBUk2a3LK/img.png&quot; data-alt=&quot;출처 : https://baekjungho.github.io/wiki/database/mysql-index/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BNEZ0/btsJUuDJsXx/wDXMRAhAWAzdbOBUk2a3LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBNEZ0%2FbtsJUuDJsXx%2FwDXMRAhAWAzdbOBUk2a3LK%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;741&quot; height=&quot;437&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://baekjungho.github.io/wiki/database/mysql-index/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Index와 클러스터링 인덱스(pk)는 각각 다른 B-Tree를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;위 그림은 Index의 B-Tree이고 &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;클러스터링 인덱스(pk)도 동일한 구조를 가지지만&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Index의 리프노드는 테이블의 pk값을, 클러스터링 인덱스(pk)의 리프노드는 레코드에 대한 테이블 데이터를 가진다는 차이가 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;즉 Index 키로 특정 레코드를 검색하면 인덱스의 루트 노드에서 시작해 인덱스의 리프노드로 이동하고 인덱스의 리프노드의 값은 pk값이기에 클러스터링 인덱스의 키(pk값)를 찾은 후&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;리프노드로 이동하여 실제 데이터를 조회하게 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그렇기에 &lt;b&gt;특정 ROW들에 Lock이 설정되면 그 ROW범위 만큼 인덱스에 Lock이 걸리게 되고 그 후 PK까지 Lock이 걸리게 됩니다.&amp;nbsp;&lt;/b&gt; &lt;br /&gt;(참고로 SELECT로 인덱스가 걸려있는 컬럼만 조회하면 pk까지 조회하지 않고 인덱스가 가지고 있는 값만으로 반환합니다.) &lt;br /&gt;&lt;br /&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;풀스캔하며&amp;nbsp;작업을&amp;nbsp;하게되어 &lt;br /&gt;성능&amp;nbsp;및&amp;nbsp;동시성에&amp;nbsp;영향을&amp;nbsp;미치게&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 Index에 Lock이 걸리는지 데이터를 통해 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;first_name 컬럼에 인덱스가 있는 Employee 테이블이 있고 테이블에 12명의 사원이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv8RIR/btsJWiaRjm2/GYFGojFqhJkMAs0h4Rggy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv8RIR/btsJWiaRjm2/GYFGojFqhJkMAs0h4Rggy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv8RIR/btsJWiaRjm2/GYFGojFqhJkMAs0h4Rggy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv8RIR%2FbtsJWiaRjm2%2FGYFGojFqhJkMAs0h4Rggy0%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;738&quot; height=&quot;429&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qF0EZ/btsJVio9xbj/MhEUnkvbMqXFASTTEIGqgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qF0EZ/btsJVio9xbj/MhEUnkvbMqXFASTTEIGqgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qF0EZ/btsJVio9xbj/MhEUnkvbMqXFASTTEIGqgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqF0EZ%2FbtsJVio9xbj%2FMhEUnkvbMqXFASTTEIGqgK%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;655&quot; height=&quot;339&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션1번에서 first_name이 John인 레코드에 대한 UPDATE쿼리가 실행됩니다. (John은 테이블에 3명 있습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yMOth/btsJVgygk6z/aISLDDxsasKYgtujhhxxrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yMOth/btsJVgygk6z/aISLDDxsasKYgtujhhxxrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yMOth/btsJVgygk6z/aISLDDxsasKYgtujhhxxrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyMOth%2FbtsJVgygk6z%2FaISLDDxsasKYgtujhhxxrK%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;1098&quot; height=&quot;126&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;126&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;pre id=&quot;code_1728033766279&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- Lock 조회 명령어
SELECT * FROM performance_schema.data_locks;&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;위의 명령어를 통해 다른 세션에서 Lock을 확인해보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NFnug/btsJUXeC2Je/WtWJDi5tfUfQxtZk4GH2Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NFnug/btsJUXeC2Je/WtWJDi5tfUfQxtZk4GH2Xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NFnug/btsJUXeC2Je/WtWJDi5tfUfQxtZk4GH2Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNFnug%2FbtsJUXeC2Je%2FWtWJDi5tfUfQxtZk4GH2Xk%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;1766&quot; height=&quot;506&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;506&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;세션1에서 실행시킨 UPDATE문은 idx_first_name 인덱스를 통해 first_name이 'John' 데이터를 찾고 Lock을 걸게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;b&gt;first_name 조건 외 다른 조건이 있어 변경되는 레코드가 1건이라 하더라도 idx_first_name를 사용한다면 Lock은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;first_name이 'John'인 모든 데이터에 걸리게 됩니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Update는 X-Lock을 거는 DML이기에 범위 내 인덱스(John)에 X-Lock이 걸리게되고, Intension Lock이 동작해 테이블에는 IX-Lock도 걸립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 데이터가 존재하는 클러스터링 인덱스(pk)의 데이터(John)에도 동일하게 X-Lock이 걸리는 모습을 확인 할 수 있으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 레코드만 잠그는 X,REC_NOT_GAP 과 Next Key Lock을 의미하는 X도 확인 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션1의 트랜잭션 중 인덱스의 새로운 값 추가를 방지하기 위한 GAP Lock도 걸려있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Lock의 설정 범위와 종류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 데이터를 통해 설명하면서 IX-Lock , GAP-Lock, Next Key Lock이 언급되었는데, 이는 락의 종류와 범위에 관한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lock의 범위와 종류를 알아보겠습니다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Lock의 범위&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- ROW (행)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1개의&amp;nbsp;행에&amp;nbsp;대해&amp;nbsp;Lock을&amp;nbsp;설정합니다.&amp;nbsp;&lt;/b&gt;일반&amp;nbsp;적인&amp;nbsp;DML(Update,&amp;nbsp;Insert,&amp;nbsp;Delete)&amp;nbsp;사용&amp;nbsp;시&amp;nbsp;적용되는&amp;nbsp;범위&amp;nbsp;입니다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- Column (열)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1개의&amp;nbsp;열을&amp;nbsp;기준으로&amp;nbsp;Lock을&amp;nbsp;설정합니다.&lt;/b&gt; &lt;br /&gt;컬럼에 대한 접근을 막게되며 설정 및 해제 시 많은 리소스가 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 페이지, 블럭 (자주 사용 X)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터페이스의 데이터들은 모두 스토리지 엔진에서 파일 형태로 저장되며 페이지 단위로 관리됩니다. &lt;br /&gt;MySQL&amp;nbsp;페이지의&amp;nbsp;경우&amp;nbsp;보통&amp;nbsp;16KB로&amp;nbsp;설정되어&amp;nbsp;있습니다. &lt;br /&gt;페이지,블록에&amp;nbsp;대해서도&amp;nbsp;Lock을&amp;nbsp;설정&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있으며&amp;nbsp;이&amp;nbsp;또한&amp;nbsp;많은&amp;nbsp;리소스가&amp;nbsp;사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 테이블&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 테이블에 대한 락을 설정합니다. &lt;/b&gt;&lt;br /&gt;LOCK&amp;nbsp;TABLES&amp;nbsp;table_name&amp;nbsp;[&amp;nbsp;READ&amp;nbsp;|&amp;nbsp;WRITE&amp;nbsp;]&amp;nbsp;명령으로&amp;nbsp;명시적으로&amp;nbsp;락을&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;MEMORY, MyISAM 테이블에서는 묵시적으로 사용될 때도 있습니다.&amp;nbsp;&lt;br /&gt;InnoDB의&amp;nbsp;경우&amp;nbsp;레코드&amp;nbsp;기반의&amp;nbsp;잠금을&amp;nbsp;제공하기에&amp;nbsp;단순&amp;nbsp;데이터&amp;nbsp;변경&amp;nbsp;쿼리(DML)로&amp;nbsp;테이블락이&amp;nbsp;설정되지는&amp;nbsp;않고 &lt;br /&gt;스키마를&amp;nbsp;변경하는&amp;nbsp;DDL&amp;nbsp;구문(create,&amp;nbsp;alter,&amp;nbsp;drop)에는&amp;nbsp;테이블락이&amp;nbsp;설정됩니다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 글로벌 락 (자주 사용 X)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 데이터베이스에 대해 Lock을 설정합니다. &lt;br /&gt;명시적으로&amp;nbsp;FLUSH&amp;nbsp;TABLES&amp;nbsp;WITH&amp;nbsp;READ&amp;nbsp;LOCK&amp;nbsp;명령어를&amp;nbsp;사용합니다. &lt;br /&gt;Lock이&amp;nbsp;걸리게&amp;nbsp;되면&amp;nbsp;단&amp;nbsp;하나의&amp;nbsp;트랜잭션&amp;nbsp;즉&amp;nbsp;하나의&amp;nbsp;세션만이&amp;nbsp;DB접근이&amp;nbsp;가능하게&amp;nbsp;됩니다. &lt;br /&gt;&lt;br /&gt;DB전체&amp;nbsp;업데이트와&amp;nbsp;같은&amp;nbsp;상황,&amp;nbsp;여러&amp;nbsp;데이터베이스에&amp;nbsp;존재하는&amp;nbsp;MyISAM이나&amp;nbsp;MEMORY테이블에&amp;nbsp;대해&amp;nbsp;mysqldump로&amp;nbsp;일괄된&amp;nbsp;백업을&amp;nbsp;받고자&amp;nbsp;할때&amp;nbsp;사용&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;하지만&amp;nbsp;MySQL&amp;nbsp;8.0이상부터는&amp;nbsp;백업Lock이&amp;nbsp;도입되어서&amp;nbsp;백업시에는&amp;nbsp;백업Lock을&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;락의 유형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 공유 락 (Shared Lock, S-Lock)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기&amp;nbsp;Lock(Read&amp;nbsp;Lock)&amp;nbsp;이라고도&amp;nbsp;합니다. &lt;br /&gt;공유&amp;nbsp;Lock이&amp;nbsp;걸린&amp;nbsp;데이터에&amp;nbsp;대해서는&amp;nbsp;SELECT만&amp;nbsp;가능하며&amp;nbsp;다른&amp;nbsp;작업은&amp;nbsp;불가능합니다. &lt;br /&gt;공유&amp;nbsp;Lock이&amp;nbsp;걸린&amp;nbsp;데이터에&amp;nbsp;대해서&amp;nbsp;다른&amp;nbsp;트랜잭션에서도&amp;nbsp;똑같이&amp;nbsp;공유&amp;nbsp;Lock을&amp;nbsp;획득&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있으나,&amp;nbsp;배타&amp;nbsp;Lock은&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;없습니다. &lt;br /&gt;즉 공유 Lock이 걸려도 읽기 작업은 가능합니다. &lt;br /&gt;&lt;br /&gt;공유 Lock을 사용하여 조회한 데이터가 트랜잭션 안에서 변경되지 않음을 보장 할 수 있습니다. &lt;br /&gt;MySQL에서 일반적인 SELECT은 공유 Lock을 걸지 않으나, 서브 쿼리와 SELECT FOR UPDATE구문에는 공유 Lcok을 겁니다.&lt;br /&gt;명시적으로&amp;nbsp;공유&amp;nbsp;Lock을&amp;nbsp;걸기&amp;nbsp;위해서는&amp;nbsp;FOR&amp;nbsp;SHARE&amp;nbsp;문법을&amp;nbsp;사용&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728097074036&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select (열) from (테이블) for share&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;&lt;b&gt;- 배타 락 (Exclusive Lock, X-Lock)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기&amp;nbsp;Lock&amp;nbsp;(Write&amp;nbsp;Lock)이라고도&amp;nbsp;합니다. &lt;br /&gt;데이터에&amp;nbsp;대해&amp;nbsp;배타&amp;nbsp;Lock을&amp;nbsp;획득한&amp;nbsp;트랜잭션은&amp;nbsp;읽기,&amp;nbsp;쓰기&amp;nbsp;모두&amp;nbsp;실행&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;다른&amp;nbsp;트랜잭션은&amp;nbsp;배타&amp;nbsp;Lock이&amp;nbsp;걸린&amp;nbsp;데이터에&amp;nbsp;대해&amp;nbsp;읽기쓰기작업&amp;nbsp;모두&amp;nbsp;수행&amp;nbsp;할&amp;nbsp;수&amp;nbsp;없습니다. &lt;br /&gt;즉&amp;nbsp;베타&amp;nbsp;Lock이&amp;nbsp;걸려있다면&amp;nbsp;다른&amp;nbsp;트랜잭션은&amp;nbsp;공유&amp;nbsp;Lock,&amp;nbsp;배타&amp;nbsp;Lock&amp;nbsp;어느것도&amp;nbsp;획득&amp;nbsp;할&amp;nbsp;수&amp;nbsp;없습니다. &lt;br /&gt;&lt;br /&gt;명시적으로 베타 Lock을 걸기 위해서는 FOR UPDATE구문을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728097087586&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select (열) from (테이블) for update&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;&lt;b&gt;- Intention Lock (Intention Lock)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB에서는&amp;nbsp;multiple&amp;nbsp;granularity&amp;nbsp;lock(MGL)을&amp;nbsp;지원합니다.&amp;nbsp;(다중잠금) &lt;br /&gt;즉&amp;nbsp;Row&amp;nbsp;Lock과&amp;nbsp;Table&amp;nbsp;Lock의&amp;nbsp;공존이&amp;nbsp;가능합니다. &lt;br /&gt;&lt;br /&gt;Intention&amp;nbsp;Lock은&amp;nbsp;2종류가&amp;nbsp;있으며&amp;nbsp;테이블에&amp;nbsp;Lock을&amp;nbsp;거는&amp;nbsp;'테이블&amp;nbsp;Lock'입니다. &lt;br /&gt;-&amp;nbsp;Intention&amp;nbsp;Shared&amp;nbsp;Lock(IS-Lock) &lt;br /&gt;-&amp;nbsp;Intention&amp;nbsp;Exclusive&amp;nbsp;Lock(IX-Lock) &lt;br /&gt;&lt;br /&gt;SELECT&amp;nbsp;FOR&amp;nbsp;SHARE&amp;nbsp;쿼리가&amp;nbsp;실행되면&amp;nbsp;테이블에&amp;nbsp;IS-Lock을&amp;nbsp;먼저&amp;nbsp;설정&amp;nbsp;후&amp;nbsp;Row에&amp;nbsp;S-Lock을&amp;nbsp;설정합니다. &lt;br /&gt;SELECT&amp;nbsp;FOR&amp;nbsp;UPDATE,&amp;nbsp;INSERT,&amp;nbsp;DELETE&amp;nbsp;쿼리가&amp;nbsp;실행되면&amp;nbsp;테이블에&amp;nbsp;IX-Lock먼저&amp;nbsp;설정&amp;nbsp;후&amp;nbsp;Row에&amp;nbsp;X-Lock이&amp;nbsp;설정됩니다. &lt;br /&gt;즉&amp;nbsp;Intention&amp;nbsp;Lock은&amp;nbsp;Row에&amp;nbsp;Lock을&amp;nbsp;건다는&amp;nbsp;'의도'를&amp;nbsp;알려주는&amp;nbsp;테이블Lock입니다. &lt;br /&gt;&lt;br /&gt;Intetsion&amp;nbsp;Lock(IS-Lock,&amp;nbsp;IX-Lock)은&amp;nbsp;여러&amp;nbsp;트랜잭션에서&amp;nbsp;발생하는&amp;nbsp;새로운&amp;nbsp;IS,IX&amp;nbsp;Lock의&amp;nbsp;동시&amp;nbsp;접근이&amp;nbsp;허용됩니다. &lt;br /&gt;하지만&amp;nbsp;IS,IX-Lock&amp;nbsp;이후&amp;nbsp;발생하는&amp;nbsp;X-Lock,&amp;nbsp;S-Lock을&amp;nbsp;걸게되는&amp;nbsp;시점&amp;nbsp;즉&amp;nbsp;Row&amp;nbsp;Lock시점에&amp;nbsp;다른&amp;nbsp;트랜잭션의&amp;nbsp;동시접근이&amp;nbsp;막히게&amp;nbsp;됩니다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;왜 Intension Lock이 필요할까??&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;예를들어&amp;nbsp;트랙잭션A가&amp;nbsp;특정Row들에&amp;nbsp;Update쿼리를&amp;nbsp;시작하고&amp;nbsp;Row들에&amp;nbsp;X-Lock을&amp;nbsp;걸려있는&amp;nbsp;상황일때 &lt;br /&gt;트랜잭션B가&amp;nbsp;테이블스키마&amp;nbsp;변경과&amp;nbsp;같이&amp;nbsp;테이블&amp;nbsp;자체에&amp;nbsp;X-Lock을&amp;nbsp;설정하는&amp;nbsp;쿼리를&amp;nbsp;실행시킨다면 &lt;br /&gt;Row&amp;nbsp;변경&amp;nbsp;중&amp;nbsp;테이블&amp;nbsp;스키마가&amp;nbsp;변경되면&amp;nbsp;안되기에&amp;nbsp;트랜잭션A가&amp;nbsp;끝나기&amp;nbsp;전까지&amp;nbsp;트랜잭션&amp;nbsp;B는&amp;nbsp;대기해야합니다. &lt;br /&gt;&lt;br /&gt;트랜잭션B는&amp;nbsp;테이블의&amp;nbsp;전체&amp;nbsp;Row데이터를&amp;nbsp;확인하며&amp;nbsp;Row-Lock이&amp;nbsp;걸려있는지&amp;nbsp;확인해야하지만 &lt;br /&gt;트랜잭션A가&amp;nbsp;테이블에&amp;nbsp;미리&amp;nbsp;설정해둔&amp;nbsp;IS-Lock이&amp;nbsp;있기에&amp;nbsp;트랜잭션B에서&amp;nbsp;테이블에&amp;nbsp;S-Lock이&amp;nbsp;존재한다는걸&amp;nbsp;Row-Level가&amp;nbsp;아닌&amp;nbsp;Table-Level&amp;nbsp;에서&amp;nbsp;확인&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있기에 &lt;br /&gt;트랜잭션B가&amp;nbsp;굳이&amp;nbsp;Row를&amp;nbsp;확인하지&amp;nbsp;않을&amp;nbsp;수&amp;nbsp;있어&amp;nbsp;성능상의&amp;nbsp;이점이&amp;nbsp;존재합니다. &lt;br /&gt;&lt;br /&gt;추가로 Lock TABLES, ALTER TABLE, DROP TABLE 같은 쿼리가실행되면 IS,IX Lock 모두 Block하는 별도의 테이블 Lock(Sch-M 락 등)이 설정되며 IS,&amp;nbsp;IX&amp;nbsp;Lock을&amp;nbsp;얻으려는&amp;nbsp;트랜잭션은&amp;nbsp;대기상태가&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;락 종류간 호환 여부 표 (갈등 = 충돌)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byxLf8/btsJU9TM7gU/gqaEmMWl41pHViQHcuI4C0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byxLf8/btsJU9TM7gU/gqaEmMWl41pHViQHcuI4C0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byxLf8/btsJU9TM7gU/gqaEmMWl41pHViQHcuI4C0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyxLf8%2FbtsJU9TM7gU%2FgqaEmMWl41pHViQHcuI4C0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;352&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;InnoDB의 Lock&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 레코드 Lock&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;레코드를 잠그는 Lock입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;performance_schema.data_locks 테이블에서는 &lt;b&gt;REC_NOT_GAP&lt;/b&gt; 으로 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약&amp;nbsp;인덱스가&amp;nbsp;하나도&amp;nbsp;없는&amp;nbsp;테이블이라면&amp;nbsp;내부적으로&amp;nbsp;자동&amp;nbsp;생성된&amp;nbsp;클러스터링&amp;nbsp;인덱스(PK)를&amp;nbsp;통해&amp;nbsp;잠금을&amp;nbsp;설정하며 &lt;br /&gt;모든 테이블의 레코드에 락을 걸고, 풀스캔하며 적절한 레코드를 찾아 Lock을 걸어주어야 하기에 조회 뿐만 아니라 동시성 성능에 큰 영향을 미치게 됩니다.&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;&lt;b&gt;- 갭 Lock (사진)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 레코드가 아닌 '&lt;b&gt;레코드와 인접한 레코드 사이의 간격'만을 잠그는 Lock입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;performance_schema.data_locks 테이블에서는&lt;span&gt; &lt;/span&gt;&lt;b&gt;GAP&lt;/b&gt; 으로 표시됩니다. &lt;br /&gt;&lt;b&gt;갭락을 사용함으로서 레코드와 레코드 사이에 새로운 레코드가 생성(insert)되는것을 제어하며 펜텀리드를 방지합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 넥스트 키 Lock의 일부로서 사용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터로 갭락을 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직원 테이블에 급여에 관한 인덱스 idx_second_name을 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션1에서 FOR UPDATE구문을 사용해 2개의 레코드(조건 : 급여가 52000 ~ 55000)에 X-Lock을 걸은 후&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2088&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp1A0z/btsJVOO3Vtk/1y95ObhWGrHGJ5bkVFBZr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp1A0z/btsJVOO3Vtk/1y95ObhWGrHGJ5bkVFBZr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp1A0z/btsJVOO3Vtk/1y95ObhWGrHGJ5bkVFBZr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp1A0z%2FbtsJVOO3Vtk%2F1y95ObhWGrHGJ5bkVFBZr0%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;2088&quot; height=&quot;618&quot; data-origin-width=&quot;2088&quot; data-origin-height=&quot;618&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;이후 세션2에서 세션1에서 SELECT 범위 내에 값을 추가하는 INSERT문(급여 : 55000)을 실행하고 Lock을 확인해보면 아래와 같이 INSERT가 실패하는걸 확인 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유가 바로 GAP-Lock때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션1에서 Lock을 건 범위가 아닌 부분에 INSERT를 하게되면 정상적으로 수행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2052&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lZzDi/btsJVOuKgzV/ZCe8ocDZ9ai1npD3q7Yhik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lZzDi/btsJVOuKgzV/ZCe8ocDZ9ai1npD3q7Yhik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lZzDi/btsJVOuKgzV/ZCe8ocDZ9ai1npD3q7Yhik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlZzDi%2FbtsJVOuKgzV%2FZCe8ocDZ9ai1npD3q7Yhik%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;2052&quot; height=&quot;592&quot; data-origin-width=&quot;2052&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 넥스트 키 Lock&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;레코드 락과 갭락을 합친 형태입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;performance_schema.data_locks 테이블에서 X락 뒤에 GAP,REC_NOT_GAP 과 같은 키워드가&amp;nbsp;없다면 넥스트 키 Lock&lt;/b&gt;이라고 생각하시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728205992912&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM table WHERE pk &amp;gt; 99;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MI7qO/btsJVLryvWk/dv84XmCjzbNUSQPvXGxJT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MI7qO/btsJVLryvWk/dv84XmCjzbNUSQPvXGxJT1/img.png&quot; data-alt=&quot;출처 : https://stackoverflow.com/questions/74965677/next-key-lock-explication-primary-key-for-range&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MI7qO/btsJVLryvWk/dv84XmCjzbNUSQPvXGxJT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMI7qO%2FbtsJVLryvWk%2Fdv84XmCjzbNUSQPvXGxJT1%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;735&quot; height=&quot;300&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://stackoverflow.com/questions/74965677/next-key-lock-explication-primary-key-for-range&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위와 같은 쿼리가 실행되었을때 RDB는 클러스터링 인덱스(pk)를 스캔하면서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pk가 99보다 큰 최초의 레코드 101을 찾아냅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;101의 앞뒤 레코드 &lt;b&gt;97, 101 사이에 갭 Lock을 설정&lt;/b&gt;하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이후 pk &amp;gt; 99인 모든 레코드에 갭에 Lock 설정 후, 각 레코드 데이터에 대해서도 레코드 Lock을 설정합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;InnoDB의 갭 락이나 넥스트 키 락은 바이너리 로그에 기록되는 쿼리가 레플리카 서버에서 실행될 때 소스 서버와 동일한 결과를 만들도록 보장해줍니다. &lt;br /&gt;그런데 넥스트 키 락과 갭 락으로 인해 데드락이 발생하거나 다른 트랜잭션을 기다리는 일이 자주 발생 할 수 있어 바이너리 로그 포맷을 ROW형태로 바꾸어서 넥스트 키 락과 갭락을 줄이는것이 좋다고 합니다.&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;&lt;b&gt;- 자동 증가 Lock (Auto Increment Lock)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;MySQL은 자동 증가하는 숫자값을 채번하기 위해 AUTO_INCREMENT라는 컬럼 속성을 제공하며, 이는 주로 FK에 사용됩니다.&amp;nbsp; &lt;br /&gt;AUTO_INCREMENT&amp;nbsp;컬럼은&amp;nbsp;여러&amp;nbsp;레코드가&amp;nbsp;동시에&amp;nbsp;INSERT&amp;nbsp;되더라도&amp;nbsp;중복되지&amp;nbsp;않고&amp;nbsp;순차적으로&amp;nbsp;증가하는&amp;nbsp;일련번호를&amp;nbsp;제공하기&amp;nbsp;위해&amp;nbsp; &lt;br /&gt;내부적으로 테이블 수준의 잠금인 자동 증가 락(Auto Increment Lock)을 사용합니다. (짧은 순간만 걸렸다가 즉시 해제)&lt;br /&gt;&lt;br /&gt;INSERT와 REPLACE와 같이 새로운 레코드를 저장하는 쿼리에서만 사용되며, &lt;br /&gt;트랜잭션과&amp;nbsp;관계없이&amp;nbsp;INSERT나&amp;nbsp;REPLACE&amp;nbsp;문장에서&amp;nbsp;AUTO_INCREMENT&amp;nbsp;값을&amp;nbsp;가져오는&amp;nbsp;순간에&amp;nbsp;락이&amp;nbsp;걸립니다. &lt;br /&gt;자동 증가 락은 테이블에 1개만 존재하기 때문에, 한 쿼리에서 락을 획득하여 채번중이라면 다음 쿼리는 대기합니다.&lt;br /&gt;자동 증가 락은 잠금을 최소화하기 위해 한 번 증가하면 절대 자동으로 줄어들지 않으며 트랜잭션과도 무관합니다. &lt;br /&gt;&lt;br /&gt;자동 증가값 채번에 성공 후, 이후 쿼리에서 실패하여 트랜잭션이 롤백되어도 자동 증가값은 복구되지 않고 그대로 남습니다.&lt;/p&gt;</description>
      <category>DB</category>
      <category>DB Lock</category>
      <category>innoDB lock</category>
      <category>ix is lock</category>
      <category>lock</category>
      <category>RDS</category>
      <category>x lock s lock</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/78</guid>
      <comments>https://jaykos96.tistory.com/78#entry78comment</comments>
      <pubDate>Fri, 20 Sep 2024 17:58:02 +0900</pubDate>
    </item>
    <item>
      <title>OOP(객체 지향 프로그래밍)에 대해 쉽게 알아보자!</title>
      <link>https://jaykos96.tistory.com/77</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;OOP(object-oriented programming)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OOP란 '현실 세계에 가까운 방식으로 프로그램을 만드는 프로그래밍 방법론'입니다.&lt;br /&gt;&lt;br /&gt;OOP를&amp;nbsp;통해&amp;nbsp;현실&amp;nbsp;세계의&amp;nbsp;개념들을,&amp;nbsp;프로그램&amp;nbsp;내에서&amp;nbsp;&lt;b&gt;'객체'&lt;/b&gt;로&amp;nbsp;바라보며&lt;br /&gt;객체들간의&amp;nbsp;상호작용을&amp;nbsp;통해&amp;nbsp;프로그램이&amp;nbsp;동작하도록&amp;nbsp;합니다.&lt;br /&gt;&lt;br /&gt;이러한&amp;nbsp;객체들의&amp;nbsp;상호작용을&amp;nbsp;&lt;b&gt;'협력'&lt;/b&gt;이라고&amp;nbsp;하며,&lt;br /&gt;각&amp;nbsp;&lt;b&gt;객체들은&amp;nbsp;'협력'안에서&amp;nbsp;'메세지'를&amp;nbsp;보내&amp;nbsp;서로&amp;nbsp;소통하면서&amp;nbsp;자신의&amp;nbsp;'역할'을&amp;nbsp;'책임'을&amp;nbsp;가지고&amp;nbsp;수행해야합니다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;OOP방식으로&amp;nbsp;프로그램을&amp;nbsp;설계하면&amp;nbsp;유지보수와&amp;nbsp;확장에&amp;nbsp;유리한&amp;nbsp;프로그램을&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있습니다.&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;OOP의 철학 (협력, 역할, 책임 그리고 메세지)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서&amp;nbsp;OOP를&amp;nbsp;설명하며&amp;nbsp;중요한&amp;nbsp;키워드들을&amp;nbsp;따옴표&amp;nbsp;안에&amp;nbsp;넣어서&amp;nbsp;언급하였습니다.&lt;br /&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;OOP설계를&amp;nbsp;해보려고합니다.&lt;br /&gt;&lt;br /&gt;'커피&amp;nbsp;주문'을&amp;nbsp;위해&amp;nbsp;일어나야하는&amp;nbsp;일들을&amp;nbsp;순서대로&amp;nbsp;정리하면&amp;nbsp;아래와&amp;nbsp;같습니다.&lt;br /&gt;&lt;br /&gt;1.&amp;nbsp;손님은&amp;nbsp;메뉴를&amp;nbsp;바리스타에게&amp;nbsp;주문한다.&lt;br /&gt;2.&amp;nbsp;바리스타는&amp;nbsp;손님에게&amp;nbsp;주문받은&amp;nbsp;메뉴에&amp;nbsp;맞는&amp;nbsp;커피를&amp;nbsp;제조한다.&lt;br /&gt;3.&amp;nbsp;바리스타는&amp;nbsp;완성된&amp;nbsp;커피를&amp;nbsp;손님에게&amp;nbsp;전달한다.&lt;br /&gt;&lt;br /&gt;커피를&amp;nbsp;주문하는&amp;nbsp;프로그램을&amp;nbsp;OOP방식으로&amp;nbsp;설계한다면&amp;nbsp;프로그램의&amp;nbsp;최종&amp;nbsp;목적&amp;nbsp;커피&amp;nbsp;주문은&amp;nbsp;'협력'을&amp;nbsp;의미허며&lt;br /&gt;&amp;lsquo;손님&amp;rsquo;, &amp;lsquo;커피&amp;rsquo;, '바리스타' 등은 각각의 객체로 정의합니다.&lt;br /&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;고유한&amp;nbsp;'역할'과&amp;nbsp;'책임'부여받습니다.&lt;br /&gt;&lt;br /&gt;객체들은&amp;nbsp;서로&amp;nbsp;'메세지'를&amp;nbsp;통해&amp;nbsp;상호작용하며&amp;nbsp;프로그램의&amp;nbsp;목적인&amp;nbsp;커피&amp;nbsp;주문이&amp;nbsp;성공하도록&amp;nbsp;'협력'해야합니다.&lt;br /&gt;위의&amp;nbsp;'커피주문&amp;nbsp;프로그램'에&amp;nbsp;대한&amp;nbsp;객체들간의&amp;nbsp;협력&amp;nbsp;구조를&amp;nbsp;OOP적&amp;nbsp;관점에서&amp;nbsp;설명하면&amp;nbsp;아래와&amp;nbsp;같습니다.&lt;br /&gt;&lt;br /&gt;손님객체는&amp;nbsp;바리스타객체에게&amp;nbsp;커피를&amp;nbsp;만들어달라는&amp;nbsp;요청(request)을&amp;nbsp;메시지를&amp;nbsp;통해&amp;nbsp;전달합니다.&lt;br /&gt;이때&amp;nbsp;메세지를&amp;nbsp;보내는&amp;nbsp;손님객체는&amp;nbsp;송신자(sender)가&amp;nbsp;되고&amp;nbsp;메세지를&amp;nbsp;받는&amp;nbsp;바리스타객체는&amp;nbsp;수신자(receiver)가&amp;nbsp;됩니다.&lt;br /&gt;&lt;br /&gt;요청(request)를&amp;nbsp;받은&amp;nbsp;바리스타객체는&amp;nbsp;요청에&amp;nbsp;응답할&amp;nbsp;'책임'이&amp;nbsp;있기에&amp;nbsp;스스로&amp;nbsp;요청을&amp;nbsp;처리해야합니다.&lt;br /&gt;이때&amp;nbsp;객체가&amp;nbsp;요청을&amp;nbsp;처리하는&amp;nbsp;방법을&amp;nbsp;메서드(method)라고&amp;nbsp;합니다.&lt;br /&gt;&lt;br /&gt;바리스타객체는&amp;nbsp;커피를&amp;nbsp;만들기&amp;nbsp;위해&amp;nbsp;커피객체에&amp;nbsp;요청(request)을&amp;nbsp;메세지를&amp;nbsp;통해&amp;nbsp;전달합니다.&lt;br /&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;(OOP의&amp;nbsp;객체는&amp;nbsp;완전히&amp;nbsp;현실세계의&amp;nbsp;개념과&amp;nbsp;동일&amp;nbsp;할&amp;nbsp;수는&amp;nbsp;없습니다)&lt;br /&gt;&lt;br /&gt;커피객체가&amp;nbsp;만들어졌다면&amp;nbsp;바리스타객체에게&amp;nbsp;요청(request)에&amp;nbsp;대한&amp;nbsp;응답을&amp;nbsp;보내고,&amp;nbsp;응답을&amp;nbsp;받은&amp;nbsp;바리스타객체는&amp;nbsp;손님객체에게&amp;nbsp;응답을&amp;nbsp;보냄으로서&amp;nbsp;협력이&amp;nbsp;마무리됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OOP의 요소&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OOP에서는&amp;nbsp;캡슐화, 추상화, 상속, 다형성 크게 4가지 특징이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LNMBQ/btsJbvcxyR6/Aq8TpK603uQWMFXBNFVVFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LNMBQ/btsJbvcxyR6/Aq8TpK603uQWMFXBNFVVFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LNMBQ/btsJbvcxyR6/Aq8TpK603uQWMFXBNFVVFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLNMBQ%2FbtsJbvcxyR6%2FAq8TpK603uQWMFXBNFVVFK%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;736&quot; height=&quot;397&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캡슐화(Encapsulation)&lt;/h3&gt;
&lt;pre id=&quot;code_1724330097762&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'손님이 커피를 주문했을때 손님은 바리스타가 커피를 만드는 과정을 알 수 없다'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바리스타가 커피를 만드는 과정은 바리스타의 역할이자 책임이기에 손님이 그 과정에 관여 할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캡슐화는 구현에 대한 메서드를 외부에서 확인하지 못하도록 은닉하는 행위를 말합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mamnd/btsJbJOZa6b/hJKHKRou3Wm01hyIGokhd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mamnd/btsJbJOZa6b/hJKHKRou3Wm01hyIGokhd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mamnd/btsJbJOZa6b/hJKHKRou3Wm01hyIGokhd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmamnd%2FbtsJbJOZa6b%2FhJKHKRou3Wm01hyIGokhd0%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;474&quot; height=&quot;177&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&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;스스로&amp;nbsp;자신의&amp;nbsp;상태를&amp;nbsp;하며&amp;nbsp;자율적인&amp;nbsp;객체를&amp;nbsp;구현&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;자율적인&amp;nbsp;책임을&amp;nbsp;가진&amp;nbsp;객채들이&amp;nbsp;협력함으로서&amp;nbsp;객체끼리의&amp;nbsp;협력을&amp;nbsp;단순하게&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&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;br /&gt;&lt;br /&gt;자바에서는&amp;nbsp;접근&amp;nbsp;제어자&amp;nbsp;public,&amp;nbsp;default,&amp;nbsp;protected,&amp;nbsp;private를&amp;nbsp;통해&amp;nbsp;외부&amp;nbsp;객체에&amp;nbsp;대핸&amp;nbsp;접근범위를&amp;nbsp;제한&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추상화 (Abstraction)&lt;/h3&gt;
&lt;pre id=&quot;code_1724330195499&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'손님이 느낄때는 1급 바리스타와 2급 바리스타 모두 그냥 내게 커피를 만들어주는 바리스타일 뿐이다'&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1급 바리스타와 2급 바리스타는 두 객체는 '바리스타'라는 하나의 형태로 손님에게 추상화 되어 보여집니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;추상화란 객체의&amp;nbsp;공통적인&amp;nbsp;속성과&amp;nbsp;기능을&amp;nbsp;추출하여&amp;nbsp;정의하는&amp;nbsp;것을&amp;nbsp;의미합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;여러 프로그래밍 언어들은 현실세계의 개념을 어플리케이션의 type으로 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자들을 string type, 숫자들을 int 타입으로 규정하듯이 현실세계의 개념은 어플리케이션 안에서 객체들은 '행동'을 기준으로 하여 각각의 type으로서 정의됩니다.&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;객체들이 가진 내부의 타입들과 값들이 다르더라도 동일한 '행동'을 한다면 동일한 type으로 정의 할 수 있고 이는 동일한 '책임'을 가지고 있다는걸 의미합니다.&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;추가로 OOP관점에서 Type은 크게 두가지로 나눌 수 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;슈퍼타입과 서브타입&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떤 타입이 다른 타입보다 일반적이라면 슈퍼타입, 특수하다면 서브타입&lt;/b&gt;이라고 하며, &quot;일반화와 특수화&quot;라고 이야기하기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 타입이 다른 타입의 서브타입이 되기 위해서는 'Is-a 규칙'을 준수해야합니다.&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;ls-a 규칙이란 흔히 말하는 일반화 관계입니다.&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;아메리카노는 커피다.&quot; 라는 명제를 예시로 들면&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;물론 아메리카노는 음료의 서브타입이기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇든 슈퍼타입과 서브타입의 관계는 &quot;SubType is SuperType&quot; 이라는 조건을 만족시켜야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상화를 함으로서 우리는 수신을 받는 객체에 대해 다형성을 줄 수 있고, 추상화된 객체에 메세지를 보냄으로서 유연한 협력을 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는&amp;nbsp;추상클래스(abstract)와&amp;nbsp;인터페이스(interface)를&amp;nbsp;통해 추상화를 구현&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상속 (Inheritance)&lt;/h3&gt;
&lt;pre id=&quot;code_1724330236652&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'1급 바리스타와 2급 바리스타는 동일한 바리스타 자격증을 취득하였으며 커피를 만들 수 있는 기술이 있다'&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;두 바리스타는 동일하게 커피를 만들 수 있는 방법(method)를 알고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반화와 특수화를 객체로 구현하기 가장 좋은 방법은 상속입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 인터페이스 상속(interface inheritiance)과 서브클래싱 구현 상속(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;implemantaion inheritiance)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 두가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 서브타이핑과 서브클래싱이라고도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 상속은 설계의 유연성을 위해 사용되며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브클래싱 구현 상속은 주로 코드의 중복제거와 재사용성을 높히기 위해 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 상속과 서브클래싱 구현 상속을 구분하기 위해서는 클라이언트 관점에서 상속과 관련된 객체들이 어떻게 사용되고 있는지 확인해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&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;상속을 받은 어떤 객체가 이해할 수 없는 메시지가 있다면 자신의 상위 클래스에게 책임을 위임(delegration) 합니다.&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;자바에서는 implements와 extand키워드를 사용해 각각 서브타이핑과 서브클래싱을 구현 할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다형성 (Polymorphism)&lt;/h3&gt;
&lt;pre id=&quot;code_1724330246145&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'&quot;아메리카노를 만들어 줘&quot; 라는 요청을 받은 1급 바리스타와 2급 바리스타는 각각 다른 방법으로 커피를 만들 수 있다'&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 data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;서로 다른 유형의 객체가 동일한 메시지에 대해 서로 다르게 반응하는 것을 말합니다.&lt;/b&gt;&lt;br /&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;이용해&amp;nbsp;메서드를&amp;nbsp;처리&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있다는&amp;nbsp;의미입니다.&lt;br /&gt;&lt;br /&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;수&amp;nbsp;있습니다.&lt;br /&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;받을&amp;nbsp;수&amp;nbsp;있기에&amp;nbsp;협력이&amp;nbsp;유연해질&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;&lt;br /&gt;자바에서는 오버라이딩(Overriding), 오버로딩(&lt;span&gt;Overloading&lt;/span&gt;)을 통해 다형성을 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>객체지향</category>
      <category>OOP</category>
      <category>객체지향</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/77</guid>
      <comments>https://jaykos96.tistory.com/77#entry77comment</comments>
      <pubDate>Thu, 22 Aug 2024 21:30:53 +0900</pubDate>
    </item>
    <item>
      <title>고루틴에 대해 알아보자</title>
      <link>https://jaykos96.tistory.com/76</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp;동시성(Concurrency)과&amp;nbsp;병렬성(Parallelism)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성과&amp;nbsp;병렬성은&amp;nbsp;cpu의&amp;nbsp;작업&amp;nbsp;처리방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성은 싱글코어 프로세서에서 멀티 스레드 환경을 구성하여 &lt;b&gt;프로그램이 '동시에 실행되는 것 처럼'(논리적)&lt;/b&gt; 보이게 하는 방법이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬성은 멀티코어 프로세서에서 여러개의 스레드, 프로세스를 병렬로 돌려 &lt;b&gt;프로그램을 '실제로 동시에 실행'(물리적)&lt;/b&gt; 시키는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성으로&amp;nbsp;작업하는걸&amp;nbsp;멀티&amp;nbsp;태스킹,&amp;nbsp;병렬성으로&amp;nbsp;작업하는걸&amp;nbsp;멀티&amp;nbsp;프로세싱이라고도&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnDZFv/btsH9WtxJ6b/t71liRgCla3KVKTfero2Wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnDZFv/btsH9WtxJ6b/t71liRgCla3KVKTfero2Wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnDZFv/btsH9WtxJ6b/t71liRgCla3KVKTfero2Wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnDZFv%2FbtsH9WtxJ6b%2Ft71liRgCla3KVKTfero2Wk%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;375&quot; height=&quot;361&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위에서 동시성은 싱글코어, 벙렬성은 멀티코어에서 사용되는 것 처럼 이야기 했지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 운영체제는 두 방법 중 하나를 선택하여 사용하는것이 아닌 섞어서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를들어 6코어 12쓰레드의 프로세서가 있다면 6개의 프로세스를 병렬로 실행함과 동시에&lt;br /&gt;6개의 프로세스에서 생성한 스레드 n개를 동시성으로 CPU에서 작업하게됩니다.&lt;br /&gt;&lt;br /&gt;동시성으로&amp;nbsp;프로그램을&amp;nbsp;실행시키게&amp;nbsp;되면&amp;nbsp;'Context&amp;nbsp;Switching으로&amp;nbsp;인해&amp;nbsp;발생하는&amp;nbsp;스위칭&amp;nbsp;오버헤드','Race&amp;nbsp;Condition','Dead&amp;nbsp;Lock'과&amp;nbsp;같은&amp;nbsp;문제들이&amp;nbsp;발생&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;하지만&amp;nbsp;그럼에도&amp;nbsp;동시성은&amp;nbsp;제한된&amp;nbsp;하드웨어에서&amp;nbsp;성능을&amp;nbsp;향상&amp;nbsp;시키기&amp;nbsp;위한&amp;nbsp;효율적인&amp;nbsp;방법입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.&amp;nbsp;CSP&amp;nbsp;-&amp;nbsp;GO에서&amp;nbsp;동시성을&amp;nbsp;다루는&amp;nbsp;방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로는 동시성을 다루기 위해 '뮤택스', '세마포어' 와 같은 방법으로 동시성문제를 해결하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GO에서 동시성을 다루기 위한 방법으로는 &lt;b&gt;CSP모델&lt;/b&gt;을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;CSP의 핵심 철학은 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;'공유메모리의 변수에 접근할때 Lock을 걸지 말고,스레드간 통신을 통해 메모리를 공유하라'&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Go에서는 '고루틴'이라는 일종의 경량의 가상 스레드를 만들어 Lock이 아닌 고루틴간의 통신을 통해 메모리를 공유합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;고루틴에서 고루틴끼리 데이터를 공유할 떄는 'Channel'이라는 객체를 사용하여 send하면서 각 고루틴끼리 communicate 하게됩니다. (OS에서 스레드끼리 공유자원을 이용하는것을 'communicate'한다고 합니다)&lt;br /&gt;&lt;br /&gt;고루틴1이 고루틴2에게 데이터를 Channel을 통해 send하면 고루틴2는 고루틴1에게서 데이터가 도착할때 까지 다른 행위를 하지않고 기다립니다. 고루틴2의&amp;nbsp;이러한&amp;nbsp;멈춤&amp;nbsp;상태를&amp;nbsp;'안정적인&amp;nbsp;상태'라고&amp;nbsp;말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;Lock이나,&amp;nbsp;sync를&amp;nbsp;맞출&amp;nbsp;필요없이&amp;nbsp;&lt;br /&gt;Channel을&amp;nbsp;통해&amp;nbsp;공유되는&amp;nbsp;자원에&amp;nbsp;대한&amp;nbsp;동기화를&amp;nbsp;보장&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 고루틴 또한 일종의 스레드 기반이기에 동시성의 위험을 완전히 해결한것은 아니며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 고루틴이 하나의 값에 대해 접근하는 경우에는 여전히 동기화(sync)가 필요합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 고루틴 이란?&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-1 프로세스와 스레드&lt;/b&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;스레드는 프로세스 내에서 실행되는 작업의 단위이며, 하나의 프로세스는 여러 스레드를 가질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로세스의 메모리 구조&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zH2il/btsH87CEN31/mk3b557pvpmYeu3IPkydmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zH2il/btsH87CEN31/mk3b557pvpmYeu3IPkydmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zH2il/btsH87CEN31/mk3b557pvpmYeu3IPkydmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzH2il%2FbtsH87CEN31%2Fmk3b557pvpmYeu3IPkydmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;405&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;720&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;Text 영역 (Code 영역)&lt;/b&gt; : 실행 가능한 명령이 포함된 파일이나, 메모리에 존재하는 프로그램의 함수들의 코드가 CPU가 해석 할 수 있는 기계어 형태로 저장되어 있습니다. 컴파일 시점에 결정되고 Read-Only로 되어있습니다.&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;데이터 영역(Data) :&lt;/b&gt; 전역 변수, static 변수 등 프로그램이 사용하는 데이터들이 저장되어 있고. data, rodata, bss의 영역으로 나뉘어져 있습니다. rodata영역은 상수선언된 변수, 문자열이 저장됩니다.&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;초기화된 데이터 세그먼트&lt;/b&gt; : &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;프로그래머가 초기화하는 전역 변수와  static 변수가 저장됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;초기화 되지 않은 데이터 세그먼트 (bss)&lt;/b&gt; :&amp;nbsp; 블록 정적 저장소라고도 불리며, 초기값 없는 전역 변수와 static 변수가 저장됩니다. 프로그램이 시작되기 전 커널에 의해 초기값으로 초기화 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스택 영역(Stack) : &lt;/b&gt;호출된 함수가 종료되고 복귀할 주소와 데이터(변수, 리턴값 등)를 임시로 저장하는 공간입니다. 함수의 호출과 동시에 할당되고, 호출이 완료되면 소멸합니다. 너무 많은 양의 함수의 호출되거나 저장되는 데이터의 크기가 영역의 크기를 초과하면 stack overflow가 발생합니다.&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;b&gt;힙 영역 (Heap) : &lt;/b&gt;생성자나 인스턴스와 같이 동적으로 할당되는 데이터가 저장되는 영역입니다. 프로세스가 실행되는 동안 크기가 동적으로 변화 하게됩니다. 스택 영역의 함수에서 포인터를 사용하게 되면 힙 영역의 메모리에 엑세스하는데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택과 동일하게 힙 영역도 크기가 동적으로 변화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티스레드 메모리 구조&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blJ17F/btsH8Lmp9Q1/fUmlzimpjOcYFarbbhhdD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blJ17F/btsH8Lmp9Q1/fUmlzimpjOcYFarbbhhdD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blJ17F/btsH8Lmp9Q1/fUmlzimpjOcYFarbbhhdD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblJ17F%2FbtsH8Lmp9Q1%2FfUmlzimpjOcYFarbbhhdD0%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;753&quot; height=&quot;380&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;684&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;스레드도 OS에서 함수로 구현되기에 각 스레드에 대한 지역변수, static변수를 가지며 이를 관리하기 위한 프로세스와의 별도의 stack영역을 가집니다. 이 stack 영역을 thread stack이라고 부릅니다.&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 OS에서는 IPC와 같은 방법들를 사용하지만 IPC의 경우 캐시메모리가 초기화 되기에 비용이 크다는 단점이 있습니다.&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;반면 스레드는 stack 외 프로세스의 영역 Text, Data, Heap 영역을 공유하기에 각 스레드간 통신이 가능합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇기에 프로그램에서 동시성 작업이 필요할 때에는 멀티스레드를 사용하는 경우가 일반적입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-2 스케줄링과 컨텍스트 스위칭&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로세스 컨텍스트 스위칭&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPrZIF/btsIkeugYV8/7c2VqTNEZTgtO2BnaDly5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPrZIF/btsIkeugYV8/7c2VqTNEZTgtO2BnaDly5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPrZIF/btsIkeugYV8/7c2VqTNEZTgtO2BnaDly5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPrZIF%2FbtsIkeugYV8%2F7c2VqTNEZTgtO2BnaDly5k%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;670&quot; height=&quot;438&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;748&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;물리적으로 하나의 CPU에는 하나의 프로세스만 할당되어 실행 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 OS는&amp;nbsp;'동시성'을 위해 모든 프로세스에 우선순위를 할당하여, CPU에서 실행중인 프로세스의 우선순위가 대기중인 프로세스의 우선순위보다 낮아지게 되면 CPU가 실행시킬 프로세스를 변경 하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS의 '스케줄러'가 해당 작업을 수행하고 이를 '스케줄링'이라고 합니다.&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;CPU에 할당된 '프로세스1'이 스케줄링을 통해 다른 프로세스 '프로세스2'로 변경될 때 실행되던 '프로세스1'의 정보를 '프로세스1'의 PCB에 저장하고 '프로세스2'를 CPU에 할당합니다. 이후&amp;nbsp;'프로세스2'의 PCB에 저장되어있던 '프로세스2'의 정보를 다시 불러온 다음, CPU가 '프로세스2'을 실행시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 과정을 Context Switching 이라고 하며, 컨텍스트 스위칭이 일어나면 필연적으로 PCB의 정보가 변경되기에 비용이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU에 할당된 프로세스가 변경되기에 OS는 CPU의 캐시메모리를 무효화(flush)시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 변경된 프로세스는 CPU캐시를 사용하지 못하고 메모리에 직접 접근해야하고 프로세스가 변경되며 참조하는 메모리 주소를 변경하는 등 성능문제도 발생합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 컨텍스트 스위칭이 일어나며 발생하는 성능 저하를 Context Switching Overhead라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스레드 컨텍스트 스위칭&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프로세스가 동시에 실행되기 위해 Context Switching이 일어나듯, 다중 스레드 환경에서도 동일하게 스레드의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 컨텍스트 스위칭&lt;/span&gt;&lt;/span&gt;과 스케줄링이 일어납니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 프로세스의 스레드끼리 컨텍스트 스위칭이 발생하게 되면 프로세스의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 컨텍스트 스위칭&lt;/span&gt;&lt;/span&gt;보다 비용이 훨신 저렴합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(다른 프로세스의 스레드간 컨텍스트 스위칭이 발생하면, 프로세스 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 컨텍스트 스위칭&lt;/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;스레드간 컨텍스트 스위칭이 일어나서 현재 상태를 백업할때에도 불러올 때에도 동일한 프로세스 이기에 PCB가 아닌 PCB안의 'TCB'에 해당 스레드의 정보를 저장하고 불러오게 됩니다.&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: #212529; text-align: start;&quot;&gt;TCB는 PCB에 비해 적은 정보만 필요하므로 스레드의 컨텍스트 스위칭은 프로세스의 컨텍스트 스위칭보다 더 빠르게 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;(고루틴은 TCB보다 더 작은 정보가 필요하여 TCB보다 더 빠릅니다!)&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-3 PCB와 TCB&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PCB(프로세스 제어 블록)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sukSZ/btsIhx2vIX9/7K2GK6eGnDclv2ySeZ2XP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sukSZ/btsIhx2vIX9/7K2GK6eGnDclv2ySeZ2XP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sukSZ/btsIhx2vIX9/7K2GK6eGnDclv2ySeZ2XP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsukSZ%2FbtsIhx2vIX9%2F7K2GK6eGnDclv2ySeZ2XP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;508&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;1168&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;OS가 프로세스를 관리하기 위해 프로세스의 정보를 저장해 놓은 구조체 입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로세스의 상태, 프로그램 카운터(PC), 레지스터에 들어있는 정보, 프로세스의 현재 포인터 와 같은 데이터들이 들어있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;컨텍스트 스위칭이 일어나게 되면 레지스터에 존재하는 현재 실행중인 프로세스의 정보(프로그램 카운터, 스택 포인터 등)를 어딘가에 저장해 두어야 하는데, 이때 OS는 해당 프로세스의 PCB를 사용합니다. PCB는 프로세스 생성과 동시에 생성되고 프로세스가 완료되면 사라집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;PCB는 아래와 같은 정보를 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스 식별자(Process ID) : 프로세스 고유 번호&lt;/li&gt;
&lt;li&gt;프로세스 상태(Process State) : 프로세스의 현재 상태 정보입니다. (생성, 준비, 실행, 대기, 완료)&lt;/li&gt;
&lt;li&gt;프로그램 카운터(Program Counter) : 프로세스가 다음에 실행할 명령어의 주소&lt;/li&gt;
&lt;li&gt;CPU 레지스터 및 일반 레지스터 : 프로세스가 실행 중에 사용하는 레지스터의 값들이 저장&lt;/li&gt;
&lt;li&gt;CPU 스케줄링 정보 : 우선 순위, 최종 실행시각, CPU 점유시간과 같은 값들이 저장&lt;/li&gt;
&lt;li&gt;메모리 관리 정보 : 해당 프로세스의 주소 공간, 메모리 크기 등과 같은 값들이 저장&lt;/li&gt;
&lt;li&gt;프로세스 계정 정보 : 페이지 테이블, 스케줄링 큐 포인터, 프로세스 소유자, 부모 프로세스 등과 같은 값들이 저장&lt;/li&gt;
&lt;li&gt;입출력 상태 정보 : 프로세스에 할당된 입출력장치 목록, 열린 파일 목록들이 저장&lt;/li&gt;
&lt;li&gt;포인터 : 프로세스에서 사용하는 데이터(입출력장치, 파일목록 등)의 메모리 주소정보가 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TCB (스레드 제어 블록)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6cauB/btsIhuLEwJK/egkQRtH2SuIbLvLlF9zFEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6cauB/btsIhuLEwJK/egkQRtH2SuIbLvLlF9zFEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6cauB/btsIhuLEwJK/egkQRtH2SuIbLvLlF9zFEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6cauB%2FbtsIhuLEwJK%2FegkQRtH2SuIbLvLlF9zFEK%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;807&quot; height=&quot;444&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;582&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;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;TCB는 PCB안에 속해있는 구조체 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;PCB처럼 스레드의 정보가 저장되어 있으며 스레드가 생성될 때 생성되고 종료되면 같이 사라집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;multi thread, multi process 환경에서는 프로그램 카운터(PC)를 PCB가 아닌 TCB에 저장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드와 관련된 데이터만 저장되기에 PCB보다 적은 데이터를 가집니다. (고루틴은 TCB보다 더 작은 데이터를 가집니다)&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;TCB는 아래와 같은 데이터를 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 식별자(Thread ID)&amp;nbsp; : 스레드가 생성될 때 운영 체제에서 스레드에 할당하는 고유 식별자&lt;/li&gt;
&lt;li&gt;스레드 상태: 스레드가 시스템을 통해 진행됨에 따라 변경되는 스레드의 상태 (New, Runnable, Blocked, Terminated)&lt;/li&gt;
&lt;li&gt;CPU 정보: 스레드가 얼마나 진행되었는지, 어떤 데이터가 사용되고 있는지 등 OS가 알아야 하는 모든 정보가 포함스레드&lt;/li&gt;
&lt;li&gt;우선 순위: 스레드 스케줄러가 작업을 시작해야 하는 스레드 결정을 위한 우선순위 값&lt;/li&gt;
&lt;li&gt;포인터 : 스레드를 생성한 프로세스의 포인터 주소, 스레드가 생성 한 스레드의 포인터 주소, 이 스레드의 stack의 포인터 주소&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-4 스레드 모델&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드는 크게 유저 스레드(User Threads)와 커널 스레드(Kernel Thread)로 나뉩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 스레드는 사용자 수준의 라이브러리에서 관리하는 스레드이고 커널스레드는 OS에 의해 직접 관리되는 스레드입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XMqUi/btsIs4MxzIC/zFEoaAtdPZGyUBz3jIgjh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XMqUi/btsIs4MxzIC/zFEoaAtdPZGyUBz3jIgjh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XMqUi/btsIs4MxzIC/zFEoaAtdPZGyUBz3jIgjh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXMqUi%2FbtsIs4MxzIC%2FzFEoaAtdPZGyUBz3jIgjh0%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;462&quot; height=&quot;301&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;유저 스레드 (User Threads)&lt;/b&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;라이브러리를 통해서 생성, 삭제, 스케줄링이 되기에 커널영역에서 유저 스레드를 인지하지 못하며 커널입장에서는 많은 유저 스레드가 있어도 하나의 커널스레드로서 인식하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 OS 단계의 컨텍스트 스위칭이 일어나지 않아 커널스레드에 비해 비용이 저렴하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 콜과 같은 이유로 하나의 유저 스레드가 블록된다면 모든 유저스레드가 블록되는 상황이 발생하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유저 스레드 정리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로세스 1개에 커널 스레드 1개가 할당되나, 유저 스레드는 여러개 존재 할 수 있습니다.&amp;nbsp; (커널 입장에서 유저 스레드는 하나의 커널스레드)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&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;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;스케줄링을 담당한다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유저스레드의 TCB&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 프로세스 내에서 관리됩니다. (커널모드 전환 불필요, 프로그램의 시스템 콜이 일어나면 커널모드 전환)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;커널 스레드 ( Kernel Threads)&lt;/b&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;OS에 의해 생성, 삭제, 스케줄링이 일어나게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS에서 직접 관리하기에 안정적이지만 대부분의 프로그램들은 사용자 영역에서 동작하기에 프로그램에서 커널스레드를 자주 사용하게 되면 '유저 영역 -&amp;gt; 커널영역'간 호출(시스템콜)이 많아지므로 성능저하가 발생 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커널 스레드 정리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;커널 스레드를 사용하면 프로세스 내의 사용자 스레드 1개 당 커널 스레드 1개가 할당된다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;커널이 전체 TCB와 PCB를 관리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;실행중인 스레드가 시스템 콜로인해 블락되어도 해도 해당 프로세스 내 다른 스레드가 CPU에서 실행 될 수 있습니다. (유저 스레드는 커널 입장에서 하나의 커널 스레드이므로 불가능)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;OS레벨의 스케쥴링과 시스템콜로 인한 성능저하가 존재합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스레드 모델&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;크게 3가지 방법이 있는데 간단히 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고루틴은 Many To Many 방식을 사용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Many To One (M:1)&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXZ1n/btsIvytmypz/N42p88Xb3V1HJUdXYzbCf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXZ1n/btsIvytmypz/N42p88Xb3V1HJUdXYzbCf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXZ1n/btsIvytmypz/N42p88Xb3V1HJUdXYzbCf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXZ1n%2FbtsIvytmypz%2FN42p88Xb3V1HJUdXYzbCf0%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;411&quot; height=&quot;592&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 커널스레드에 N개의 유저 스레드가 연결되어 있는 형태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번에 하나의 유저 스레드만 커널에 접근가능하기에 멀티코어 시스템에서 충분한 활용이 어렵다는 큰 단점이 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Many To Many (M:N)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w46by/btsIuRAvds4/VRCkR6gwbmCo3hkpVCePok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w46by/btsIuRAvds4/VRCkR6gwbmCo3hkpVCePok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w46by/btsIuRAvds4/VRCkR6gwbmCo3hkpVCePok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw46by%2FbtsIuRAvds4%2FVRCkR6gwbmCo3hkpVCePok%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;342&quot; height=&quot;560&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 유저 스레드가 여러 커널스레드와 연결되는 형태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;M:N 모델이라고도 하며, 오늘날의 멀티코어 환경에서 활용하기 좋은 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고루틴에서 사용하는 스레드모델 방식입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;One To One (1:1)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/culHXy/btsIvU31uLK/KCGH5oQaLiqrksBMFSUFG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/culHXy/btsIvU31uLK/KCGH5oQaLiqrksBMFSUFG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/culHXy/btsIvU31uLK/KCGH5oQaLiqrksBMFSUFG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FculHXy%2FbtsIvU31uLK%2FKCGH5oQaLiqrksBMFSUFG0%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;311&quot; height=&quot;497&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 하나의 유저스레드에 하나의 커널 스레드가 연결되는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티코어에서 활용 가능하며 컨텍스트 스위칭이 커널레벨에서만 동작하기에&amp;nbsp; Many To Many 모델보다 비용이 높습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;고루틴이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어! 고루틴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 계속해서 언급되었듯 고루틴은 일종의 경량 스레드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동시성을 만들기 위한 GO의 수단이고, 고루틴 스케줄러에서 M:N모델의 형태로 관리하는 사용자 공간의 스레드입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;고루틴은 스레드 보다 가볍습니다&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;고루틴을 생성하게 되면 2KB의 스택 공간만 생성이 됩니다. (스레드에 비해 500배 가볍습니다.)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;고루틴은 필요에 따라 고루틴간 통신을 통해 Heap영역을 공유 및 사용합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3Z97W/btsIrq3hbCF/zpcUz8MZCKTmXFAP3FjFH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3Z97W/btsIrq3hbCF/zpcUz8MZCKTmXFAP3FjFH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3Z97W/btsIrq3hbCF/zpcUz8MZCKTmXFAP3FjFH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3Z97W%2FbtsIrq3hbCF%2FzpcUz8MZCKTmXFAP3FjFH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;565&quot; height=&quot;346&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp; 쓰레드는 사용할 메모리 공간과 각 메모리 간의 경계 역할을 하는 Guard Page라고 불리는 메모리 영역을 함께 필요로하여 약&amp;nbsp;1Mb의 메모리 공간을 소모하여 생성됩니다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Thread 생성이 많아질 수록 프로세스의 스택영역을 더 많이 차지하게 되고 힙과 스택영역은 크기를 공유하기에 스레드가 많이 생성될수록 힙 영역이 작아지게되고 이에따라 OOM이 발생 할 수 있습니다. 그렇기에 자바와 같은 언어에서는 스레드풀을 만들어서 스레드의 수를 관리하지만 고루틴은 가볍기에 위와 같은 문제에서 비교적 자유롭습니다. (&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;고루틴은 Go 런타임에 의해 생성 및 소멸되며 매우 적은 비용으로 이루어집니다. Go 언어는 고루틴의 수동 관리를 지원하지 않습니다.)&lt;/span&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;고루틴은 Context Switching 비용이 가볍습니다.&lt;/b&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;모든 레지스터를 대상으로 저장과 복구가 진행됩니다.&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;16개의 범용 레지스터, 프로그램 카운터(PC), 스택 포인터(SP), Segment레지스터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16개의 XMN레지스터, FP &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;coprocessor state, 16개의 AVX 레지스터, 모든 MSR 에 저장과 복구가 이루어 지는데&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: #212529; text-align: start;&quot;&gt;&lt;b&gt;고루틴의 컨텍스트 스위칭에서는 3개의 레지스터 프로그램 카운터(PC), 스택 포인터(SP), DX를 대상으로만 저장과 복구 작업이&lt;/b&gt; 이루어지기에 PCB와 TCB에 비해 Context Switching 비용이 훨신 더 가볍습니다. &amp;nbsp;&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;프로그램 카운터(PC) : &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;고루틴 인터럽트 후 복원하기 위한 프로그램 카운터(PC)로 고루틴 내부에 저장&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;스택 포인터(SP) : &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;고루틴의 스택을 가르키는 포인터 (SP)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;DX : CPU 레지스터 중 DX&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Go 스케줄러&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 스케줄러가 여러 쓰레드의 실행순서를 결정하고, 실행시키듯이&amp;nbsp;Go에서는 고루틴의 실행방법과 순서를 담당하는 스케줄러가 있습니다.&lt;br /&gt;Go스케줄러는 m:n 모델 스케줄링 기법을 사용하는데 m은 고루틴의 갯수이고 n은 고루틴과 연결될 커널 스레드 수 입니다.&lt;br /&gt;m개의 고루틴이 구동하면 n개의 커널 스레드에서 스케줄링을 시작합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1 Go 스케줄러 구성 항목&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go스케줄러는 5개의 구성 항목을 가집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wpwVS/btsIwukB6zi/6b9JoXV1NaqIcoOTr2KxZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wpwVS/btsIwukB6zi/6b9JoXV1NaqIcoOTr2KxZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wpwVS/btsIwukB6zi/6b9JoXV1NaqIcoOTr2KxZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwpwVS%2FbtsIwukB6zi%2F6b9JoXV1NaqIcoOTr2KxZ1%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;783&quot; height=&quot;450&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;742&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;OS스레드(M) : 고루틴을 동작시킬 수 있는 실제 OS의 스레드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- P로 부터 G를 할당받아서 실행시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실행중인 G,P의 포인터를 가지고 있습니다.&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;고루틴(G) : Go에서 동시성을 위해 사용되는 경량 스레드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Go런타임이 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컨텍스트 스위칭을 위한 SP, 고루틴 상태정보를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LRQ에서 대기합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;논리적&amp;nbsp;프로세서(P)&amp;nbsp;:&amp;nbsp;OS스레드가&amp;nbsp;특정&amp;nbsp;고루틴을&amp;nbsp;실행&amp;nbsp;시킬&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;매핑&lt;/b&gt;&lt;br /&gt;- Go프로그램에서 사용 가능한 프로세서의 최대 수는 GO의 변수 'GOMAXPROCS' 값에 따라 결정됩니다.&lt;br /&gt;-&amp;nbsp;GOMAXPROCS의&amp;nbsp;기본값은&amp;nbsp;컴퓨터의&amp;nbsp;논리적&amp;nbsp;CPU&amp;nbsp;코어&amp;nbsp;개수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로세스나 스레드의 실행 상태를 저장하고 복원하는 데 필요한 모든 정보(컨텍스트 정보)를 가지고 있으며&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;, LRQ를 가지고 있어서 큐로부터 전달받은&lt;span&gt; G&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;를&lt;span&gt; &lt;/span&gt;&lt;/span&gt;M&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;에 할당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로컬큐(LRQ) :&amp;nbsp;&lt;/b&gt;&lt;br /&gt;- P마다 하나씩 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내부적으로 FIFO, LIFO 2개의 큐를 혼합하여 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- P가 고루틴을 생성하거나 스케줄링할 때, 먼저 자신의 LRQ를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- G를 하나씩 POP하여 논리적 프로세서(P)에 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- GO에는 GRQ와 LRQ 2개의 큐가 존재함으로서 Race Condition과 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;b&gt;글로벌큐(GRQ) :&amp;nbsp;&lt;/b&gt;&lt;br /&gt;- LRQ에 할당되지 못한 큐가 존재합니다.&amp;nbsp;&lt;br /&gt;- 실행중인 G은 한번에 10ms까지 실행되는데 10ms동안 실행된 G은 대기 상태가 되어 글로벌큐로 이동됩니다.&lt;br /&gt;- 고루틴 생성 시점에 LRQ가 가득 차있으면 글로벌큐에 고루틴이 저장됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- GRQ는 P의 LRQ가 비어있을 때만 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로컬큐에서 2개의 큐를 사용하는 이유?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LRQ에서 FIFO와 LIFO를 함께 사용하는 이유는 캐시 지역성(cache locality) 때문입니다.&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;실행중인 고루틴 G에서 새로운 고루틴 G-1를 만든다고 가정해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;G가 G-1의 종료되기를 기다려야하는 작업이라면 G입장에서는 G-1가 다른 고루틴들보다 빨리 종료되어야 하며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;G, G-1 모두 동일한 메모리의 위치를 참조할 가능성이 높기에 G-1가 종료되자마자 G가 실행되어야 캐시를 사용할 가능성이 매우 높아지는 등 여러 성능상 이점이 있습니다.&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;이러한 상황을 고려하여 고루틴은 FIFO, LIFO 2개의 큐를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 고루틴 생성 시 (LIFO 큐 사용)&lt;/b&gt;: 새로운 G가 생성되면 LIFO 큐에 추가됩니다&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;고루틴 G, G2, G-1가 순서대로 생성됩니다. (G-1은 G에서 생성한 고루틴)&lt;/li&gt;
&lt;li&gt;생성된 고루틴들은 LIFO 큐에 들어갑니다.&lt;/li&gt;
&lt;li&gt;LIFO 큐 상태: [G-1, G2, G] (나중에 생성된 고루틴 G-1이 큐의 앞쪽에 위치)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 고루틴 실행 (FIFO 큐 사용)&lt;/b&gt;: FIFO큐에 들어온 G는 스케줄러에 의해 POP되어 실행됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행될 G-1는 LIFO큐에서 POP되어 FIFO큐로 들어옵니다. (가장 마지막에 생성된 G-1이 FIFO 큐에 들어옵니다)&lt;/li&gt;
&lt;li&gt;FIFO 큐 상태: [G-1]&lt;/li&gt;
&lt;li&gt;G-1가 실행됩니다.&lt;/li&gt;
&lt;li&gt;다음으로 G2가 FIFO 큐로 이동하고 실행됩니다.&lt;/li&gt;
&lt;li&gt;FIFO 큐 상태: [G2]&lt;/li&gt;
&lt;li&gt;G2가 실행됩니다.&lt;/li&gt;
&lt;li&gt;마지막으로 G가 FIFO 큐로 이동하고 실행됩니다.&lt;/li&gt;
&lt;li&gt;FIFO 큐 상태: [G1]&lt;/li&gt;
&lt;li&gt;G1이 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 방식을 통해 cache locality를 확보하며 메모리 접근 시간과 프로그램의 실행속도를 줄일 수 있습니다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;P와 M의 초기화&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Go 프로그램이 시작되면, 지정된 GOMAXPROCS 값에 따라 여러 개의 P가 생성됩니다.&lt;/li&gt;
&lt;li&gt;각 P는 실행 대기 중인 고루틴들을 관리하기 위한 LRQ를 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;OS는 M을 생성하고, M은 P와 연결되어 G을 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;고루틴 실행&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;P는 자신의 LRQ에서 G을 꺼내어, 연결된 M에서 실행하도록 합니다.&lt;/li&gt;
&lt;li&gt;M은 P의 컨텍스트 내에서 고루틴을 실행하며, OS가 할당한 시간 동안 이를 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;M의 스피닝(Spinning) 상태 진입&lt;/b&gt;:&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;M이 유휴 상태로 전환되지 않고 계속해서 반복적으로 P의 LRQ에서 다른 고루틴을 찾으며 작업 가능 여부를 확인하며 대기하는 상태를 의미합니다.&lt;/li&gt;
&lt;li&gt;스피닝 상태에서 M은 P의 LRQ에서 다음 고루틴을 가져와 실행합니다.&lt;/li&gt;
&lt;li&gt;스피닝을 통해 고루틴이 곧 실행될 수 있는 상황에서는 컨텍스트 스위칭 없이 빠르게 고루틴을 실행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;스피닝 상태에서는 CPU 사이클을 소비하며 계속해서 LRQ를 확인하므로, 너무 오랫동안 스피닝을 유지하면 CPU 자원을 낭비할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;고루틴 스케줄링 시작&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;P의 LRQ가 비어 있으면, P는 GRQ에서 고루틴을 가져옵니다, 하지만 GRQ도 비어있다면 다른 P에 존재하는 절반의 고루틴을 가져옵니다.(작업 훔치기 전략)&lt;/li&gt;
&lt;li&gt;이러한 과정을 통해 고루틴이 효율적으로 스케줄링되고, CPU 자원이 최대한 활용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;Go 프로그램이 시작되면, GOMAXPROCS가 4로 설정되어 있다고 가정합니다.&lt;/li&gt;
&lt;li&gt;4개의 P가 생성되고, 각각의 P는 G들을 관리하기 위한 LRQ를 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;운영체제는 여러 개의 M을 생성하고, 이들은 각 P와 연결되어 G을 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;P1의 LRQ&lt;/b&gt;에 고루틴 G1, G2, G3가 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;M1이 P1과 연결&lt;/b&gt;되어 있고, G1을 실행 중입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;M1이 할당된 시간&lt;/b&gt; 동안 G1의 작업을 완료하면, M1은 스피닝 상태로 전환됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스피닝 상태의 M1&lt;/b&gt;은 P1의 LRQ에서 G2를 가져와 실행을 시작합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;P1의 LRQ가 비어 있다면&lt;/b&gt;, P1은 GRQ에서 고루틴을 가져오거나 다른 P의 고루틴을 훔쳐올 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;고루틴의 컨텍스트 스위칭 시점은 언제일까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;OS에서 컨텍스트 스위칭은 보통 시스템콜이 일어날때 발생하지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;고루틴은 GO스케줄러가 관리하기에 아래와 같은 상황에서 컨텍스트 스위칭이 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고루틴간 통신 (unbuffered channel에 접근 시)&lt;/li&gt;
&lt;li&gt;시스템 콜&lt;/li&gt;
&lt;li&gt;블로킹 작업 (시스템 I/O 등)&lt;/li&gt;
&lt;li&gt;고루틴 생성 및 종료&lt;/li&gt;
&lt;li&gt;time.Sleep()과 같은 Sleep 함수를 통해 일정시간 대기&lt;/li&gt;
&lt;li&gt;runtime.Gosched()&lt;span&gt;&amp;nbsp;&lt;/span&gt;Function 같은 로직이 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Go</category>
      <category>go</category>
      <category>고랭</category>
      <category>고루틴</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/76</guid>
      <comments>https://jaykos96.tistory.com/76#entry76comment</comments>
      <pubDate>Sun, 23 Jun 2024 11:55:44 +0900</pubDate>
    </item>
    <item>
      <title>AWS 계정간 S3 데이터 옮기기</title>
      <link>https://jaykos96.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;다른 AWS 계정에서 사용하는 S3 데이터를 옮겨야하는 상황이 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 5개 정도의 S3를 옮기면 되어서, 간단하게 AWS CLI를 통해 데이터를 옮기는게 좋다는 생각이 들었습니다.&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;AWS CLI S3 API를 활용한 데이터 이전 흐름도&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;80d47b54-0309-4472-ae00-041ec27bc25b.png&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOO7z8/btsGB1cCFDp/avwQP45AWBHaeWlKejm1i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOO7z8/btsGB1cCFDp/avwQP45AWBHaeWlKejm1i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOO7z8/btsGB1cCFDp/avwQP45AWBHaeWlKejm1i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOO7z8%2FbtsGB1cCFDp%2FavwQP45AWBHaeWlKejm1i0%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;1092&quot; height=&quot;656&quot; data-filename=&quot;80d47b54-0309-4472-ae00-041ec27bc25b.png&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;오른쪽 : 계정 A (목적지 버킷)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;왼쪽 : 계정 B (소스 버킷)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.&amp;nbsp; 계정 A에서 S3에 대한 정책 생성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정A에서 목적지 버킷에 대한 GET,PUT,LIST 권한,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스버킷에 대한 LIST, GET 권한이 존재하는 &lt;b&gt;정책을 생성 후 계정 A의 사용자에게 부여합니다.&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;s3-migration-policy라는 정책을 생성 아래 JSON을 이용해 생성하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 JSON은 해당 S3 정책에 대한 예시입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2192&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bunjfG/btsGC133O8e/CA5UF0bdWbFevAyhhmt9p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bunjfG/btsGC133O8e/CA5UF0bdWbFevAyhhmt9p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bunjfG/btsGC133O8e/CA5UF0bdWbFevAyhhmt9p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbunjfG%2FbtsGC133O8e%2FCA5UF0bdWbFevAyhhmt9p0%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;2192&quot; height=&quot;644&quot; data-origin-width=&quot;2192&quot; data-origin-height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1713008124048&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;s3:ListBucket&quot;,
                &quot;s3:GetObject&quot;,
                &quot;s3:GetObjectTagging&quot;,
                &quot;s3:GetObjectVersion&quot;,
                &quot;s3:GetObjectVersionTagging&quot;
            ],
            &quot;Resource&quot;: [
                &quot;arn:aws:s3:::awsexamplesourcebucket&quot;,
                &quot;arn:aws:s3:::awsexamplesourcebucket/*&quot;
            ]
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;s3:ListBucket&quot;,
                &quot;s3:PutObject&quot;,
                &quot;s3:PutObjectAcl&quot;,
                &quot;s3:PutObjectTagging&quot;,
                &quot;s3:GetObjectTagging&quot;,
                &quot;s3:GetObjectVersion&quot;,
                &quot;s3:GetObjectVersionTagging&quot;
            ],
            &quot;Resource&quot;: [
                &quot;arn:aws:s3:::awsexampledestinationbucket&quot;,
                &quot;arn:aws:s3:::awsexampledestinationbucket/*&quot;
            ]
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2.&amp;nbsp; 계정A에서 생성한 S3 정책을 계정A의 사용자에게 연결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정A의 사용자에게 생성한 정책을 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2106&quot; data-origin-height=&quot;1144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZGtux/btsGBgBoLrL/cT0PNqJmh4Qkh1BqgdN20K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZGtux/btsGBgBoLrL/cT0PNqJmh4Qkh1BqgdN20K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZGtux/btsGBgBoLrL/cT0PNqJmh4Qkh1BqgdN20K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZGtux%2FbtsGBgBoLrL%2FcT0PNqJmh4Qkh1BqgdN20K%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;2106&quot; height=&quot;1144&quot; data-origin-width=&quot;2106&quot; data-origin-height=&quot;1144&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2270&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC9GCf/btsGA0yPNKI/p2jvmyoAuPEYFiaEWNGDKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC9GCf/btsGA0yPNKI/p2jvmyoAuPEYFiaEWNGDKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC9GCf/btsGA0yPNKI/p2jvmyoAuPEYFiaEWNGDKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC9GCf%2FbtsGA0yPNKI%2Fp2jvmyoAuPEYFiaEWNGDKk%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;2270&quot; height=&quot;926&quot; data-origin-width=&quot;2270&quot; data-origin-height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.&amp;nbsp; 계정B의 소스버킷에 정책 설정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정B의 소스 버킷 정책에 계정A에 대한 S3정책을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 Statement에 있는 Principal, Resource를 계정A, 목적지 버킷에 대한 정보로 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713008884539&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;{
	&quot;Version&quot;: &quot;2012-10-17&quot;,
	&quot;Statement&quot;: [
            {
                &quot;Sid&quot;: &quot;AllowForS3Access&quot;,
                &quot;Effect&quot;: &quot;Allow&quot;,
                &quot;Principal&quot;: {&quot;AWS&quot;: &quot;Account A AccountID&quot;},
                &quot;Action&quot;: [
                    &quot;s3:ListBucket&quot;,
                    &quot;s3:GetObject&quot;
                ],
                &quot;Resource&quot;: [
                        &quot;arn:aws:s3:::awsexamplesourcebucket/*&quot;,
                        &quot;arn:aws:s3:::awsexamplesourcebucket&quot;
                ]
            }
	]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. S3권한 정책이 부여된 계정A의 사용자 계정으로 CLI 사용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정A의 사용자에 대한 AccessKey를 활용하여 CLI를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 위치에 AWS CLI 사용을 위한 자격증명 파일이 존재합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #16191f; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Windows:&lt;span&gt;&amp;nbsp;&lt;/span&gt;C:\Users\username\.aws\credentials&lt;/li&gt;
&lt;li&gt;Mac/Linux:&lt;span&gt;&amp;nbsp;&lt;/span&gt;~/.aws/credentials&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 자격증명 파일이 없다면 명령어를 사용하여 추가 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713011023669&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Set-AWSCredential `
                 -AccessKey 사용자AccessKey `
                 -SecretKey 사용자SecretKey
                 -StoreAs 프로파일명지정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자격증명이 정상적으로 되어있다면 아래와 같은 명령어로 소스버킷의 모든 폴더와 파일을 목적지 버킷으로 옮길 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713009202798&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws s3 sync s3://source-s3-bucket s3://target-s3-bucket&lt;/code&gt;&lt;/pre&gt;</description>
      <category>aws</category>
      <category>s3</category>
      <category>s3 sync</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/75</guid>
      <comments>https://jaykos96.tistory.com/75#entry75comment</comments>
      <pubDate>Sat, 13 Apr 2024 21:25:12 +0900</pubDate>
    </item>
    <item>
      <title>Session Manager를 이용해 Private DB 접속하기</title>
      <link>https://jaykos96.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 DB는 Private Subnet 즉 인터넷 엑세스가 불가능한 서브넷에 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 로컬에서 Private Subnet에 있는 DB에 대한 엑세스가 필요하기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Session Manager와 Bastian Host 이용하여 DB에 접속하는 방법을 정리하려 합니다.&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;EC2 Session Manager 연결&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. EC2 UserData에 스크립트 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bastian Host UserData에 Session Manager 설치 스크립트를 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;1148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GFchF/btsB4VaC9uf/F2DFaRJkcL8cAe2bg8KiG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GFchF/btsB4VaC9uf/F2DFaRJkcL8cAe2bg8KiG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GFchF/btsB4VaC9uf/F2DFaRJkcL8cAe2bg8KiG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGFchF%2FbtsB4VaC9uf%2FF2DFaRJkcL8cAe2bg8KiG0%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;1402&quot; height=&quot;1148&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;1148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1702639778781&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/bash

yum update -y
yun install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
yum install -y amazon-ssm-agent

echo &quot;session required pam_limits.so&quot; &amp;gt;&amp;gt; /etc/pam.d/su
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. IAM 권한 및 역할 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2에 SSM권한이 있는 IAM 역할을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 2가지 ssm, ssmmessages 두 정책이 SSM관련 권한입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ssm:* (EC2가 SSM서비스에 엑세스 하기위한 권한)&lt;/li&gt;
&lt;li&gt;ssmmessages:* (SSM에서 EC2에 요청을 전달하기 위한 권한)&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 AWS에서 관리하는 정책인 AmazonEC2RoleforSSM 정책이나 SSM에 필요한 권한이 연결된 Role을 생성해 EC2에 연결합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VgU6g/btsB6hqKjZM/ntfn9ofdVriSlBdYKipgCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VgU6g/btsB6hqKjZM/ntfn9ofdVriSlBdYKipgCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VgU6g/btsB6hqKjZM/ntfn9ofdVriSlBdYKipgCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVgU6g%2FbtsB6hqKjZM%2Fntfn9ofdVriSlBdYKipgCK%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;2232&quot; height=&quot;686&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Session Manager 연결 확인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 두 조건이 만족되면 Session Manager 연결이 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgH2BM/btsB5EzKFon/UbNRkyKWxurTkgVKgqRdxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgH2BM/btsB5EzKFon/UbNRkyKWxurTkgVKgqRdxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgH2BM/btsB5EzKFon/UbNRkyKWxurTkgVKgqRdxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgH2BM%2FbtsB5EzKFon%2FUbNRkyKWxurTkgVKgqRdxk%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;1640&quot; height=&quot;806&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로컬CMD(윈도우)에서 Session Manager 접속&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Session Manager 플러그인을 설치합니다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Session Manager 설치 프로그램 URL (윈도우, 맥)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-windows.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-windows.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1702639361825&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Windows에 Session Manager 플러그 인 설치 - AWS Systems Manager&quot; data-og-description=&quot;최상의 결과를 위해 Windows PowerShell 버전 5 이상을 사용하여 Windows에서 세션을 시작하는 것이 좋습니다. Windows 10의 Command 쉘을 사용할 수도 있습니다. Session Manager 플러그인에서는 PowerShell 및 Command&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-windows.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-windows.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-windows.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-windows.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Windows에 Session Manager 플러그 인 설치 - AWS Systems Manager&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최상의 결과를 위해 Windows PowerShell 버전 5 이상을 사용하여 Windows에서 세션을 시작하는 것이 좋습니다. Windows 10의 Command 쉘을 사용할 수도 있습니다. Session Manager 플러그인에서는 PowerShell 및 Command&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-macos-overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-macos-overview.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1702639386740&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;macOS에 Session Manager 플러그 인 설치 - AWS Systems Manager&quot; data-og-description=&quot;플러그인에 Python 2.6.5 이상 또는 Python 3.3 이상이 필요합니다. 기본적으로 설치 스크립트는 시스템 기본 버전의 Python에서 실행됩니다. 대체 버전의 Python을 설치하고 이를 사용하여 Session Manager 플&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-macos-overview.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-macos-overview.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-macos-overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/install-plugin-macos-overview.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;macOS에 Session Manager 플러그 인 설치 - AWS Systems Manager&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;플러그인에 Python 2.6.5 이상 또는 Python 3.3 이상이 필요합니다. 기본적으로 설치 스크립트는 시스템 기본 버전의 Python에서 실행됩니다. 대체 버전의 Python을 설치하고 이를 사용하여 Session Manager 플&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 터널링 명령어 실행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssm에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;bastian host를 이용한&lt;span&gt;&amp;nbsp;터널링 명령어&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702641699937&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws ssm start-session --target &amp;lt;bastian-host-id&amp;gt; --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters &quot;{\&quot;host\&quot;:[\&quot;private subnet의 DB엔드포인트\&quot;],\&quot;portNumber\&quot;:[\&quot;실제 DB포트\&quot;],\&quot;localPortNumber\&quot;:[\&quot;로컬에서 사용할 포트\&quot;]}&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여러 DB에 대한 터널링을 한번에 실행되도록 저는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;bat파일을 만들어&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;아래 bat파일을 실행하게되면 총 3개의 세션 터미널이 열리게됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702641541461&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@echo off

:: start session
start cmd /c aws ssm start-session --target bastian-host-id --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters &quot;{\&quot;host\&quot;:[\&quot;private subnet의 DB1엔드포인트\&quot;],\&quot;portNumber\&quot;:[\&quot;3306\&quot;],\&quot;localPortNumber\&quot;:[\&quot;13306\&quot;]}&quot;

start cmd /c aws ssm start-session --target bastian-host-id --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters &quot;{\&quot;host\&quot;:[\&quot;private subnet의 DB2엔드포인트\&quot;],\&quot;portNumber\&quot;:[\&quot;3306\&quot;],\&quot;localPortNumber\&quot;:[\&quot;13306\&quot;]}&quot;

start cmd /c aws ssm start-session --target bastian-host-id --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters &quot;{\&quot;host\&quot;:[\&quot;private subnet의 DB3엔드포인트\&quot;],\&quot;portNumber\&quot;:[\&quot;3306\&quot;],\&quot;localPortNumber\&quot;:[\&quot;13306\&quot;]}&quot;

pause&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bat 파일 실행 후 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFuXrg/btsB6NbP9nD/JMy122X0eCNfKnSszu2S2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFuXrg/btsB6NbP9nD/JMy122X0eCNfKnSszu2S2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFuXrg/btsB6NbP9nD/JMy122X0eCNfKnSszu2S2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFuXrg%2FbtsB6NbP9nD%2FJMy122X0eCNfKnSszu2S2k%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;1628&quot; height=&quot;822&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SSM 터널링을 이용해 private DB 접속하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 session 터널링 터미널이 열려있는 동안 로컬(인터넷망)에서 bastian host를 통과해 private DB 접속이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 -&amp;gt; bastian host -&amp;gt; DB로 접속이 진행되기에 host에는 루프백 주소를, 포트는 터널링 포트를 지정해줍니다.&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;916&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xdjMT/btsB6nkbdEJ/8yyrMiMMWwOthTVV1nifU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xdjMT/btsB6nkbdEJ/8yyrMiMMWwOthTVV1nifU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xdjMT/btsB6nkbdEJ/8yyrMiMMWwOthTVV1nifU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxdjMT%2FbtsB6nkbdEJ%2F8yyrMiMMWwOthTVV1nifU1%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;916&quot; height=&quot;390&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;390&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;session이 연결된 상태에서 Test Connection을 시도하면 성공합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgFvsB/btsB6Oohonn/1GopTpTjZKqUdBKj4fWmM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgFvsB/btsB6Oohonn/1GopTpTjZKqUdBKj4fWmM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgFvsB/btsB6Oohonn/1GopTpTjZKqUdBKj4fWmM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgFvsB%2FbtsB6Oohonn%2F1GopTpTjZKqUdBKj4fWmM1%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;890&quot; height=&quot;518&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>aws</category>
      <category>BastianHost</category>
      <category>sessionManager</category>
      <category>SessionManager 터널링</category>
      <category>SSM</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/74</guid>
      <comments>https://jaykos96.tistory.com/74#entry74comment</comments>
      <pubDate>Fri, 15 Dec 2023 21:11:23 +0900</pubDate>
    </item>
    <item>
      <title>AWS S3  Gateway Endpoint, Interface Endpoint를 이용하여Private하게 접속하기</title>
      <link>https://jaykos96.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;S3는 기본적으로 Public리소스라서 Private하게 접근하려면 VPC엔드포인트를 이용하여 접근해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gateway Endpoint, Interface Endpoint 2가지 방법으로 Private하게 접속 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2900&quot; data-origin-height=&quot;1096&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNoEnQ/btszKfh8d6a/ofaALgH74iYiLXkATUTc40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNoEnQ/btszKfh8d6a/ofaALgH74iYiLXkATUTc40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNoEnQ/btszKfh8d6a/ofaALgH74iYiLXkATUTc40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNoEnQ%2FbtszKfh8d6a%2FofaALgH74iYiLXkATUTc40%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;2900&quot; height=&quot;1096&quot; data-origin-width=&quot;2900&quot; data-origin-height=&quot;1096&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;h2 data-ke-size=&quot;size26&quot;&gt;Interface Endpoint 와 Gateway Endpoint&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2가지 방법으로 S3와 private한 연결을 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 Endpoint모두 S3와 Private하게 통신을 할 수 있는 방식이지만 몇가지 차이점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로는 Interface Endpoint는 On-Prem등 외부 접속을 허용하고, Gateway Endpoint는 허용하지 않는 차이점이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2zomd/btszKz10XYl/j7XX440zkO3B5kwKaICk70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2zomd/btszKz10XYl/j7XX440zkO3B5kwKaICk70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2zomd/btszKz10XYl/j7XX440zkO3B5kwKaICk70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2zomd%2FbtszKz10XYl%2Fj7XX440zkO3B5kwKaICk70%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;1582&quot; height=&quot;706&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Interface Endpoint란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 S3에 private하게 접속하려면 Gateway Endpoint만을 사용해야했지만, 지금은 interface Endpoint로도 접속이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;On-Prem, 다른 Region 등 외부 리소스와 통신할때 주로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SG를 사용하여 접근을 제한 할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Interface Endpoint 생성&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범주는 AWS서비스, 서비스 이름에서는 Interface유형의 com.amazonaws.ap-northeast-2.s3을 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 Interface Endpoint가 생성될 VPC, Subnet, 보안그룹, 엑세스 정책이 필요합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2764&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLzWgE/btszNQ9b8zO/h0g7Qf0VtUFBUFjFnZHhC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLzWgE/btszNQ9b8zO/h0g7Qf0VtUFBUFjFnZHhC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLzWgE/btszNQ9b8zO/h0g7Qf0VtUFBUFjFnZHhC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLzWgE%2FbtszNQ9b8zO%2Fh0g7Qf0VtUFBUFjFnZHhC1%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;2764&quot; height=&quot;1090&quot; data-origin-width=&quot;2764&quot; data-origin-height=&quot;1090&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2920&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqL69g/btszL9HWuyl/VDyZiriy6VssKyg2OAbX7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqL69g/btszL9HWuyl/VDyZiriy6VssKyg2OAbX7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqL69g/btszL9HWuyl/VDyZiriy6VssKyg2OAbX7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqL69g%2FbtszL9HWuyl%2FVDyZiriy6VssKyg2OAbX7k%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;2920&quot; height=&quot;490&quot; data-origin-width=&quot;2920&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Interface Endpoint의 도메인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들어진 Endpoint의 상세 페이지에서 도메인을 확인 할 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 도메인을 통해 S3로 Private하게 접속 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 주소를 통해 외부 (On-Prem, 다른 Region)에서 접속이 가능합니다.&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;1654&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj01Bx/btszIZGZxdd/i2PlTw2AwN8ey3YpKUD4EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj01Bx/btszIZGZxdd/i2PlTw2AwN8ey3YpKUD4EK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj01Bx/btszIZGZxdd/i2PlTw2AwN8ey3YpKUD4EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj01Bx%2FbtszIZGZxdd%2Fi2PlTw2AwN8ey3YpKUD4EK%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;1654&quot; height=&quot;912&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;Interface Endpoint로 private하게 접속하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interface Endpoint가 생성되면 만들어진 도메인 주소로 nslookup해서 확인해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PrivateIP를 확인 할 수 있습니다.&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;954&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kOhJ6/btszONqYJ8x/oY4wL0bYZYgptuwGuXHUD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kOhJ6/btszONqYJ8x/oY4wL0bYZYgptuwGuXHUD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kOhJ6/btszONqYJ8x/oY4wL0bYZYgptuwGuXHUD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkOhJ6%2FbtszONqYJ8x%2FoY4wL0bYZYgptuwGuXHUD0%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;954&quot; height=&quot;314&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;추가로 아래 2가지 방법으로 Private하게 통신이 가능합니다.&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. URL로 접속하기 : https://bucket.&amp;lt;interface-endpoint 도메인&amp;gt;/버킷명/S3 데이터 Prefix&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. cli로 접속하기 : aws s3 --endpoint-url &amp;lt;interface-endpoint 도메인&amp;gt; &amp;lt;S3 cli 명령어&amp;gt;&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;Gateway Endpoint란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interface Endpoint와 다르게 On-Prem을 포함한 VPC외부의 접속과 다른 Region에서의 접속을 허용하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성 시 VPC내부의 Routing Table의 규칙이 만들어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM 같은 정책을 사용하여 리소스를 제한합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Gateway Endpoint 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에서 Gateway유형의 서비스를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interface Endpoint는&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;VPC, 엑세스 정책이 필요합니다. Subnet이 아닌 VPC에 지정되는 리소스이며 SG를 사용하지 않습니다.&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;2898&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baDx9I/btszKIxFgS0/RPDZuS9KcWJyfZDvFcx8QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baDx9I/btszKIxFgS0/RPDZuS9KcWJyfZDvFcx8QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baDx9I/btszKIxFgS0/RPDZuS9KcWJyfZDvFcx8QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaDx9I%2FbtszKIxFgS0%2FRPDZuS9KcWJyfZDvFcx8QK%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;2898&quot; height=&quot;502&quot; data-origin-width=&quot;2898&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;Gateway Endpoint로 private하게 접속하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성이 성공하면 서브넷의 라우팅 테이블에 라우팅이 추가되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interface Endpoint와 다르게 nslookup을 통해 S3를 질의하여도 privateIP를 확인할수는 없지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC 내부에서 통신 시 IGW가 아닌 VPC의 Gateway Endpoint를 통해 private하게 통신 할 수 있습니다.&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;2250&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baBYDG/btszHEiDkT7/3fi2gfTH2GXJttatAlxTXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baBYDG/btszHEiDkT7/3fi2gfTH2GXJttatAlxTXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baBYDG/btszHEiDkT7/3fi2gfTH2GXJttatAlxTXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaBYDG%2FbtszHEiDkT7%2F3fi2gfTH2GXJttatAlxTXK%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;2250&quot; height=&quot;384&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;384&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;실제로 S3의 PublicIP를 이용하여 traceroute를 진행해보면 모두 마스킹 된 값이지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IGW나 NAT를 타고 나가는 기록은 보이지 않습니다.&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;1390&quot; data-origin-height=&quot;1138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGcWsG/btszM0jOSrb/ePj6pKW4KoB72UNyK0ZNA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGcWsG/btszM0jOSrb/ePj6pKW4KoB72UNyK0ZNA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGcWsG/btszM0jOSrb/ePj6pKW4KoB72UNyK0ZNA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGcWsG%2FbtszM0jOSrb%2FePj6pKW4KoB72UNyK0ZNA1%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;1390&quot; height=&quot;1138&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1390&quot; data-origin-height=&quot;1138&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;</description>
      <category>aws</category>
      <category>s3</category>
      <category>S3 Gateway Endpoint</category>
      <category>S3 Interface Endpoint</category>
      <category>S3 PrivateLink</category>
      <category>S3 private통신</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/73</guid>
      <comments>https://jaykos96.tistory.com/73#entry73comment</comments>
      <pubDate>Sat, 4 Nov 2023 20:39:39 +0900</pubDate>
    </item>
    <item>
      <title>AWS S3 Replication과 S3 Batch에 대해 알아보자</title>
      <link>https://jaykos96.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;S3 안의 데이터에 대한 백업이나 동기화가 필요한 순간이 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴때 S3 Replication과 S3 Batch 작업을 통해 구현 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉 S3 Batch와 S3 Replication 을 이용하면 S3에 대한 복제와 지속적인 동기화가 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS S3 복제 (Replication) 란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 복제를 사용하면 CRP (교차 리전 복제) 와 SRR (동일 리전 복제) 두가지 방법으로 S3 데이터를 복제 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 계정간 S3 복제 또한 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 백업을 생성하거나, 더 효율적인 S3 스토리지 클래스를 사용하는 등 다양한 목적을 위해 사용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOO6Uk/btszuGOGtO3/RVDQhrg8PIrkzp2kJzsohK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOO6Uk/btszuGOGtO3/RVDQhrg8PIrkzp2kJzsohK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOO6Uk/btszuGOGtO3/RVDQhrg8PIrkzp2kJzsohK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOO6Uk%2FbtszuGOGtO3%2FRVDQhrg8PIrkzp2kJzsohK%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;802&quot; height=&quot;320&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;902&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;h2 data-ke-size=&quot;size26&quot;&gt;S3 Replication 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 복제를 하기 위해선 원본 버킷에 복제 규칙을 생성해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 -&amp;gt; 관리 -&amp;gt; 복제 규칙 생성에서 생성 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6VfMv/btszySz0dph/htGUryAVdKMUwqR6q7ngK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6VfMv/btszySz0dph/htGUryAVdKMUwqR6q7ngK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6VfMv/btszySz0dph/htGUryAVdKMUwqR6q7ngK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6VfMv%2FbtszySz0dph%2FhtGUryAVdKMUwqR6q7ngK1%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;1694&quot; height=&quot;934&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w7KiG/btszwfv29Bc/i3D19Gty1A5ioxKzkwcpgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w7KiG/btszwfv29Bc/i3D19Gty1A5ioxKzkwcpgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w7KiG/btszwfv29Bc/i3D19Gty1A5ioxKzkwcpgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw7KiG%2Fbtszwfv29Bc%2Fi3D19Gty1A5ioxKzkwcpgk%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;1608&quot; height=&quot;620&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;1532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ncstd/btszvB7pHiG/dKoVkYeQfEpbkZWvgry2t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ncstd/btszvB7pHiG/dKoVkYeQfEpbkZWvgry2t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ncstd/btszvB7pHiG/dKoVkYeQfEpbkZWvgry2t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fncstd%2FbtszvB7pHiG%2FdKoVkYeQfEpbkZWvgry2t0%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;1890&quot; height=&quot;1532&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;1532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;S3 Role&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iam 역할은 원본 버킷과 목적지 버킷에 대한 권한이 모두 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 S3 Batch 작업을 위해 batchoperation에 대한 값도 추가하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2174&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvnuTR/btszzG68Gxt/G6QHlMURDqvCJbPfGSksD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvnuTR/btszzG68Gxt/G6QHlMURDqvCJbPfGSksD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvnuTR/btszzG68Gxt/G6QHlMURDqvCJbPfGSksD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvnuTR%2FbtszzG68Gxt%2FG6QHlMURDqvCJbPfGSksD1%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;2174&quot; height=&quot;872&quot; data-origin-width=&quot;2174&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;대상객체가 사용하는 KMS키를 사용하시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;1082&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmNTel/btszzXgvAtV/7KlD5TtTbFHMAXYb5XNWB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmNTel/btszzXgvAtV/7KlD5TtTbFHMAXYb5XNWB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmNTel/btszzXgvAtV/7KlD5TtTbFHMAXYb5XNWB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmNTel%2FbtszzXgvAtV%2F7KlD5TtTbFHMAXYb5XNWB1%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;1570&quot; height=&quot;1082&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;1082&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- RTC : 추가 금액을 지불하고 복제 시간을 제어 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 복제 지표 : 두 버킷간 복제중 메트릭을 모니터링 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 삭제 마커 복제 : 원본 버킷에서 삭제한 객체에 대한 삭제마커를 목적지 버킷에서도 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 복제본 수정 동기화 : 원본 버킷에서 삭제한 객체에 대한 수정를 목적지 버킷에서도 적용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;1514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LXvWJ/btszvJqu1Q5/DsGCO8CKadbSVTLuzDpF0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LXvWJ/btszvJqu1Q5/DsGCO8CKadbSVTLuzDpF0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LXvWJ/btszvJqu1Q5/DsGCO8CKadbSVTLuzDpF0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLXvWJ%2FbtszvJqu1Q5%2FDsGCO8CKadbSVTLuzDpF0k%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;1704&quot; height=&quot;1514&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;1514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2610&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl5cqW/btszyJQQdPj/zbuUiCs1wicZKQerd3Jf10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl5cqW/btszyJQQdPj/zbuUiCs1wicZKQerd3Jf10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl5cqW/btszyJQQdPj/zbuUiCs1wicZKQerd3Jf10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl5cqW%2FbtszyJQQdPj%2FzbuUiCs1wicZKQerd3Jf10%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;2610&quot; height=&quot;712&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2610&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복제본 생성 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제 규칙이 활성화된 버킷에 객체가 추가되면 관리 구성의 상태가 Pending이 되고 시간이 지나면 Completed 상태가 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2600&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJnh4s/btszwauTxdW/tKSVbpoI4I2CtX9Akd1A4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJnh4s/btszwauTxdW/tKSVbpoI4I2CtX9Akd1A4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJnh4s/btszwauTxdW/tKSVbpoI4I2CtX9Akd1A4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJnh4s%2FbtszwauTxdW%2FtKSVbpoI4I2CtX9Akd1A4K%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;2600&quot; height=&quot;974&quot; data-origin-width=&quot;2600&quot; data-origin-height=&quot;974&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제 구성이 Completed가 되면 목적지 버킷에도 해당 객체가 생성됩니다.&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;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHxigw/btszA0489hS/RtWZ5hkwkriovvOWQHMDy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHxigw/btszA0489hS/RtWZ5hkwkriovvOWQHMDy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHxigw/btszA0489hS/RtWZ5hkwkriovvOWQHMDy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHxigw%2FbtszA0489hS%2FRtWZ5hkwkriovvOWQHMDy0%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;400&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;400&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;복제된 객체에는 Replica 라는 형태로 표시됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2108&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SIysP/btszxtnc8Xd/xyp8lCG5y21sN5fE0Ecs0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SIysP/btszxtnc8Xd/xyp8lCG5y21sN5fE0Ecs0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SIysP/btszxtnc8Xd/xyp8lCG5y21sN5fE0Ecs0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSIysP%2Fbtszxtnc8Xd%2Fxyp8lCG5y21sN5fE0Ecs0k%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;2108&quot; height=&quot;1022&quot; data-origin-width=&quot;2108&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;원본 S3에서 객체를 삭제하면 복제본에서도 동일하게 객체가 삭제됩니다.&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;2608&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP7gio/btszAmgh151/ZLHImJjmXzxifvUHkM1nb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP7gio/btszAmgh151/ZLHImJjmXzxifvUHkM1nb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP7gio/btszAmgh151/ZLHImJjmXzxifvUHkM1nb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP7gio%2FbtszAmgh151%2FZLHImJjmXzxifvUHkM1nb1%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;2608&quot; height=&quot;396&quot; data-origin-width=&quot;2608&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제본에서 조회&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n6LEp/btszy5lOHT7/Cy4nsSwWYqU2X71Xgdtj61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n6LEp/btszy5lOHT7/Cy4nsSwWYqU2X71Xgdtj61/img.png&quot; data-alt=&quot;ㅂ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n6LEp/btszy5lOHT7/Cy4nsSwWYqU2X71Xgdtj61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn6LEp%2Fbtszy5lOHT7%2FCy4nsSwWYqU2X71Xgdtj61%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;2136&quot; height=&quot;378&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ㅂ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;AWS S3 배치 작업 (Batch) 란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제규칙을 만들어도 기존의 소스 버킷의 데이터들은 목적지 버킷에 존재하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 S3 배치 작업을 통해 기존 데이터를 한번에 옮기는 작업이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 Batch 작업은 람다를 통해서도 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2288&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m5DWh/btszCkELQqT/Mllk9ZgF0vRASbDinZNrW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m5DWh/btszCkELQqT/Mllk9ZgF0vRASbDinZNrW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m5DWh/btszCkELQqT/Mllk9ZgF0vRASbDinZNrW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm5DWh%2FbtszCkELQqT%2FMllk9ZgF0vRASbDinZNrW0%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;2288&quot; height=&quot;936&quot; data-origin-width=&quot;2288&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배치 작업 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3의 배치에서 작업 생성으로 배치작업을 생성 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2222&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mcl04/btszC0yMuSW/EI4uR2CChNkjcMXtFuaMJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mcl04/btszC0yMuSW/EI4uR2CChNkjcMXtFuaMJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mcl04/btszC0yMuSW/EI4uR2CChNkjcMXtFuaMJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmcl04%2FbtszC0yMuSW%2FEI4uR2CChNkjcMXtFuaMJK%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;2222&quot; height=&quot;538&quot; data-origin-width=&quot;2222&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;매니페스트에는 파일의 경로, S3 버킷 및 키, 파일&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&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;2422&quot; data-origin-height=&quot;1190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ds1cTo/btszHFGOBxF/m4EKb7dFHlEBMhtfNOHG0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ds1cTo/btszHFGOBxF/m4EKb7dFHlEBMhtfNOHG0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ds1cTo/btszHFGOBxF/m4EKb7dFHlEBMhtfNOHG0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fds1cTo%2FbtszHFGOBxF%2Fm4EKb7dFHlEBMhtfNOHG0K%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;2422&quot; height=&quot;1190&quot; data-origin-width=&quot;2422&quot; data-origin-height=&quot;1190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2106&quot; data-origin-height=&quot;1192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfCuwj/btszGN6bQrN/Iwy5jtIT1UDt5fpBZmH5G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfCuwj/btszGN6bQrN/Iwy5jtIT1UDt5fpBZmH5G1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfCuwj/btszGN6bQrN/Iwy5jtIT1UDt5fpBZmH5G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfCuwj%2FbtszGN6bQrN%2FIwy5jtIT1UDt5fpBZmH5G1%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;2106&quot; height=&quot;1192&quot; data-origin-width=&quot;2106&quot; data-origin-height=&quot;1192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작업 선택&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 복제 구성을 사용하였기에 매니페스트를 만들때는 '복제'만 사용 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2824&quot; data-origin-height=&quot;1500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BQPOq/btszGViO8On/xbkKsltBYjqVB5c7O5XRWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BQPOq/btszGViO8On/xbkKsltBYjqVB5c7O5XRWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BQPOq/btszGViO8On/xbkKsltBYjqVB5c7O5XRWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBQPOq%2FbtszGViO8On%2FxbkKsltBYjqVB5c7O5XRWk%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;2824&quot; height=&quot;1500&quot; data-origin-width=&quot;2824&quot; data-origin-height=&quot;1500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;권한 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 복제 규칙에 사용했던 원본과 대상 S3에 대해 권한이 있는 권한을 지정합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2890&quot; data-origin-height=&quot;1278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yzL9e/btszGEaECYM/w7magk2PlVQDXhuc75YYG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yzL9e/btszGEaECYM/w7magk2PlVQDXhuc75YYG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yzL9e/btszGEaECYM/w7magk2PlVQDXhuc75YYG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyzL9e%2FbtszGEaECYM%2Fw7magk2PlVQDXhuc75YYG1%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;2890&quot; height=&quot;1278&quot; data-origin-width=&quot;2890&quot; data-origin-height=&quot;1278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배치 작업 실행 및 성공&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 배치 작업 페이지에서 작업을 선택하고 실행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2654&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAIPG7/btszGUjVyQ7/giF3kEfl34QKMdaMKxNL7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAIPG7/btszGUjVyQ7/giF3kEfl34QKMdaMKxNL7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAIPG7/btszGUjVyQ7/giF3kEfl34QKMdaMKxNL7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAIPG7%2FbtszGUjVyQ7%2FgiF3kEfl34QKMdaMKxNL7k%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;2654&quot; height=&quot;498&quot; data-origin-width=&quot;2654&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2722&quot; data-origin-height=&quot;1258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu5oSw/btszC66PPfW/sx37AJUz7nQ6reDsRn5tTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu5oSw/btszC66PPfW/sx37AJUz7nQ6reDsRn5tTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu5oSw/btszC66PPfW/sx37AJUz7nQ6reDsRn5tTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu5oSw%2FbtszC66PPfW%2Fsx37AJUz7nQ6reDsRn5tTk%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;2722&quot; height=&quot;1258&quot; data-origin-width=&quot;2722&quot; data-origin-height=&quot;1258&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;</description>
      <category>aws</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/72</guid>
      <comments>https://jaykos96.tistory.com/72#entry72comment</comments>
      <pubDate>Tue, 31 Oct 2023 21:06:51 +0900</pubDate>
    </item>
    <item>
      <title>DNS존과 네임서버, AWS Resolver로 On-Prem DNS서버 질의하기</title>
      <link>https://jaykos96.tistory.com/71</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;DNS Zone이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS Zone은 도메인 네임스페이스라고도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS는 트리구조로 되어있는데 &lt;b&gt;최상위에 root 도메인, TLD(Top Level Domain), 서브 도메인으로 이루어져있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 도메인 영역마다 별도의 Zone을 만들어서 관리 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 TLD인 google.com, 서브도메인 a.google.com , b.google.com 마다 각각의 Zone을 가질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;영역에 대한 모든 정보는 DNS 영역 파일이라는 이름으로 저장됩니다.&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;1004&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cS6HpC/btsyLPk6GSl/qi9zkNnccY4XzBIsPiFMs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cS6HpC/btsyLPk6GSl/qi9zkNnccY4XzBIsPiFMs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cS6HpC/btsyLPk6GSl/qi9zkNnccY4XzBIsPiFMs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcS6HpC%2FbtsyLPk6GSl%2Fqi9zkNnccY4XzBIsPiFMs0%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;788&quot; height=&quot;615&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네임서버와 DNS 레코드란?&lt;/h2&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 도메인을 질의하거나, 도메인을 통해 통신할때, 도메인의 네임서버에 질의를 하여 설정된 값을 찾아 통신을 하게 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이때, 네임서버에 설정된 값들을 DNS레코드라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 DNS Zone 마다 DNS레코드를 가지며, Root DNS 서버는 전 세계에 13개가 존재합니다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AWS의 Route53에서도 네임서버를 확인 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;2086&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdth49/btsyUgu5mij/qTUT2LEB8Y5HL0o9yziCsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdth49/btsyUgu5mij/qTUT2LEB8Y5HL0o9yziCsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdth49/btsyUgu5mij/qTUT2LEB8Y5HL0o9yziCsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcdth49%2FbtsyUgu5mij%2FqTUT2LEB8Y5HL0o9yziCsk%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;2086&quot; height=&quot;534&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;2086&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;DNS 레코드 값&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;도메인 명 : example.com&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DNS 존의 주 도메인 이름을 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SOA 레코드 (Start of Authority)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주 존에 대한 권한을 설정하는 레코드로, DNS 존의 시작을 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NS 레코드 (Name Server)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주 DNS 서버와 관련된 네임서버의 정보를 포함하며, 해당 도메인의 DNS를 관리하는 네임서버를 지정합니다.&lt;/li&gt;
&lt;li&gt;도메인에 대한 네임서버의 권한을 가지고 있는지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;A 레코드 (Address)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트 이름(예: www)을 IPv4 주소로 매핑하는 레코드입니다.&lt;/li&gt;
&lt;li&gt;하나의 도메인에 여러 A레코드를 사용 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AAAA 레코드 (IPv6 Address)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트 이름(예: www)을 IPv6 주소로 매핑하는 레코드입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CNAME 레코드 (Canonical Name)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트 이름(예: www)을 다른 도메인 이름(예: another-example.com)에 별칭으로 매핑하는 레코드입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MX 레코드 (Mail Exchanger)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이메일을 수신하는 메일 서버의 우선순위 및 호스트 이름을 지정하는 레코드입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TXT 레코드 (Text)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;텍스트 정보를 포함하며, SPF(Sender Policy Framework) 레코드와 같이 이메일 스팸 방지 정책과 같은 정보를 포함할 때 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SRV 레코드 (Service)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스의 위치 및 우선순위를 지정하며, 주로 VoIP 및 SIP 등과 같은 서비스에서 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PTR 레코드 (Pointer)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IP 주소를 호스트 이름으로 역으로 해석하는데 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DNS Resolver란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS Resolver는 클라이언트(사용자)가 DNS 서버에 보내는 요청을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 요청을 DNS Query라고 하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS Resolver의 동작 순서는 아래와 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트에서 DNS 쿼리를 발생시킵니다.&lt;/li&gt;
&lt;li&gt;DNS 쿼리가 Resolver에 도착하면 도메인 트리구조(Root -&amp;gt; TLD -&amp;gt; SubDomain)에 따라 각 네임서버에 요청하여 응답을 받습니다.&lt;/li&gt;
&lt;li&gt;전달받은 요청 DNS의 IP를 클라이언트에 반환합니다.&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;1032&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biyX8Q/btsyXY1hhvo/Xn5v86KvRwn5l4KtVp5e2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biyX8Q/btsyXY1hhvo/Xn5v86KvRwn5l4KtVp5e2k/img.png&quot; data-alt=&quot;출처 : AWS&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biyX8Q/btsyXY1hhvo/Xn5v86KvRwn5l4KtVp5e2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiyX8Q%2FbtsyXY1hhvo%2FXn5v86KvRwn5l4KtVp5e2k%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;1032&quot; height=&quot;790&quot; data-origin-width=&quot;1032&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : AWS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS Resolver로 On-Prem과 DNS 질의하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS와 On-Prem 이 합쳐진 하이브리드 환경에서는 두 환경간 DNS 질의가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DX를 통해 두 인프라의 통신이 가능하다면 AWS Resolver의 Inbound Endpoint, Outbound Endpoint를 이용하여 이를 구현 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 EndPoint들은 VPC와 연동되며, 1초의 약 10000번의 DNS쿼리를 지원합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Inbound Endpoint (On-Prem -&amp;gt; AWS)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;외부(On-Prem) 네트워크가 DNS쿼리를 AWS의 Resolver로 보내게되는 엔드포인트입니다.&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Outbound Endpoint (AWS -&amp;gt; On-Prem)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AWS의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;네트워크가 DNS쿼리를 외부(On-Prem)의 Resolver로 보내게되는 엔드포인트입니다.&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;1464&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G7Kk0/btsyQPle9so/B2tssBZ5jLczkx70YJJHs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G7Kk0/btsyQPle9so/B2tssBZ5jLczkx70YJJHs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G7Kk0/btsyQPle9so/B2tssBZ5jLczkx70YJJHs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG7Kk0%2FbtsyQPle9so%2FB2tssBZ5jLczkx70YJJHs0%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;1464&quot; height=&quot;924&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS &amp;lt;-&amp;gt; On-Prem Resolver 생성하기&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Route53의 Resolver에서 인바운드 엔드포인트를 생성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;1458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WuNq6/btsyVj5VO5u/QUn7kiq6S82yCCvZCvcM80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WuNq6/btsyVj5VO5u/QUn7kiq6S82yCCvZCvcM80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WuNq6/btsyVj5VO5u/QUn7kiq6S82yCCvZCvcM80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWuNq6%2FbtsyVj5VO5u%2FQUn7kiq6S82yCCvZCvcM80%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;2340&quot; height=&quot;1458&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;1458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. 엔드포인트와 VPC, SG 연결&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2614&quot; data-origin-height=&quot;1490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EgW8a/btsyYm8SDxT/XinjsW5Yumflthh5Ai4Gb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EgW8a/btsyYm8SDxT/XinjsW5Yumflthh5Ai4Gb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EgW8a/btsyYm8SDxT/XinjsW5Yumflthh5Ai4Gb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEgW8a%2FbtsyYm8SDxT%2FXinjsW5Yumflthh5Ai4Gb0%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;2614&quot; height=&quot;1490&quot; data-origin-width=&quot;2614&quot; data-origin-height=&quot;1490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. 엔드포인트의 IP 주소 지정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HA를 위해 2개의 가용영역에 IP를 지정해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 생성된 IP를 이용하여 On-Prem -&amp;gt; AWS&amp;nbsp; DNS질의를 하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 인프라 간의 DNS 질의를 위한 NAT라고 생각하셔도 될 것 같습니다.&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;1636&quot; data-origin-height=&quot;1494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tv22J/btsyTr42UpL/2IK3AVNoTmFMCD99IXN4lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tv22J/btsyTr42UpL/2IK3AVNoTmFMCD99IXN4lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tv22J/btsyTr42UpL/2IK3AVNoTmFMCD99IXN4lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftv22J%2FbtsyTr42UpL%2F2IK3AVNoTmFMCD99IXN4lK%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;1636&quot; height=&quot;1494&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.&amp;nbsp; Outbound Endpoint의 VPC, SG를 연결합니다.&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/elQfww/btsyX0rgvTa/UF6lqqyzTzRkXnsXnQiksk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/elQfww/btsyX0rgvTa/UF6lqqyzTzRkXnsXnQiksk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/elQfww/btsyX0rgvTa/UF6lqqyzTzRkXnsXnQiksk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FelQfww%2FbtsyX0rgvTa%2FUF6lqqyzTzRkXnsXnQiksk%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;2530&quot; height=&quot;1480&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. Outbound Endpoint IP 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Inbound Endpoint와 동일하게 2개의 IP를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 생성된 IP는 AWS -&amp;gt; On-Prem으로 DNS 쿼리를 전달할때 사용되는 NAT라고 생각하시면 됩니다.&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;1622&quot; data-origin-height=&quot;1488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca6M6s/btsyTrjAWu2/DR5EV4zD9LnmMlRKiESA50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca6M6s/btsyTrjAWu2/DR5EV4zD9LnmMlRKiESA50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca6M6s/btsyTrjAWu2/DR5EV4zD9LnmMlRKiESA50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca6M6s%2FbtsyTrjAWu2%2FDR5EV4zD9LnmMlRKiESA50%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;1622&quot; height=&quot;1488&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;1488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. 규칙(Rule) 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Resolver의 규칙을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Resolver에서 해석을 위한 도메인을 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 IP주소에는 On-Prem의 NameServer IP와 포트를 지정해주시면됩니다.&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;2008&quot; data-origin-height=&quot;1538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kPqyz/btsyUeRB8Mb/yKtx78RO9y05gUwVoVPKiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kPqyz/btsyUeRB8Mb/yKtx78RO9y05gUwVoVPKiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kPqyz/btsyUeRB8Mb/yKtx78RO9y05gUwVoVPKiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkPqyz%2FbtsyUeRB8Mb%2FyKtx78RO9y05gUwVoVPKiK%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;2008&quot; height=&quot;1538&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2008&quot; data-origin-height=&quot;1538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2180&quot; data-origin-height=&quot;1386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnTWIe/btsyU7q9Lzk/fRzX0NKrX8mWokVyjkJbg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnTWIe/btsyU7q9Lzk/fRzX0NKrX8mWokVyjkJbg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnTWIe/btsyU7q9Lzk/fRzX0NKrX8mWokVyjkJbg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnTWIe%2FbtsyU7q9Lzk%2FfRzX0NKrX8mWokVyjkJbg1%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;2180&quot; height=&quot;1386&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2180&quot; data-origin-height=&quot;1386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성한 Resolver 확인&lt;/h3&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;2176&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWZhFU/btsyUfbUr7f/ejAS567r0tkZmZqxY2Edck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWZhFU/btsyUfbUr7f/ejAS567r0tkZmZqxY2Edck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWZhFU/btsyUfbUr7f/ejAS567r0tkZmZqxY2Edck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWZhFU%2FbtsyUfbUr7f%2FejAS567r0tkZmZqxY2Edck%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;2176&quot; height=&quot;604&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2176&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIhYi2/btsyYpxMggY/9T0AauMdkDEnmEMS9UsKM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIhYi2/btsyYpxMggY/9T0AauMdkDEnmEMS9UsKM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIhYi2/btsyYpxMggY/9T0AauMdkDEnmEMS9UsKM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIhYi2%2FbtsyYpxMggY%2F9T0AauMdkDEnmEMS9UsKM1%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;2118&quot; height=&quot;398&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>aws</category>
      <category>AWS Resolver</category>
      <category>dns</category>
      <category>DNS Resolver</category>
      <category>Route53 Endpoint</category>
      <category>Route53 Resolver</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/71</guid>
      <comments>https://jaykos96.tistory.com/71#entry71comment</comments>
      <pubDate>Thu, 19 Oct 2023 22:25:52 +0900</pubDate>
    </item>
    <item>
      <title>Direct Connect로 On-Prem, AWS VPC 연결하기 private VIF</title>
      <link>https://jaykos96.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;업무중 AWS VPC와 On-Prem사이 DX를 통해 전용선 연결이 필요하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DX를 연결하며 진행하였던 방법을 공유하려합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Direct Connect (DX)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;on-prem의 리소스와 AWS 내의 VPC를 전용선으로 연결하기 위한 AWS의 서비스입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전용선으로 연결하기에 인터넷을 통한 연결보다 안전하게 통신 할 수 있습니다.&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;AS와 ASN&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS DX를 이해하기 위해서는 AS와 ASN, BGP프로토콜을 알아야합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AS&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Antonomous System의 약자로 인터넷에서 라우팅을 관리하고 제어하는 단위이며, 네트워크의 도메인입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ISP, 기업의 on-prem 네트워크, 클라우드 제공자 (AWS,GCP)등이 AS를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 AS는 ASN이라는 고유 번호를 가지며, 각각의 AS끼리는 BGP프로토콜을 사용하여 서로의 라우팅 정보를 교환합니다.&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;BGP 프로토콜&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AS간의 라우팅 정보를 교환하고 인터넷 트래픽을 전달하는데 사용됩니다,&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대규모 네트워크에서 라우팅을 관리하며, AS간 라우팅 정보를 전달하는데도 사용됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ASN&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ASN, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Autonomous System Number이란 인터넷에서 라우팅을 관리 및 제어하기 위해 사용되는 AS의 고유 번호입니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;BGP프로토콜을 아용하여 각 AS끼리 라우팅 정보를 교환할때 각각의 AS를 식별하는 번호로써도 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;DX를 이용해 AWS와 On-Prem을 연결하기위해 ASN을 작성 할 때 VIF와 VGW는 동일한 ASN을 가져야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가상 인터페이스 (Virtual Interface)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VIF, Virtual Interface는 DX에서 AWS와 온프레미스 환경과 전용 연결을 설정하기 위한 리소스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DX와 AWS의 논리적 연결 포인트라고 생각하시면 편할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;VIF의 인터페이스 유형은 다음과 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;public : s3와 같은 퍼블릭 리소스와 연결&lt;/li&gt;
&lt;li&gt;private : VCP같은 pirvate 리소스와 연결&lt;/li&gt;
&lt;li&gt;transit : transiant gateway에 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 VIF를 생성하면 상태가 confriming, VPC상태는 pending, BGP 상태가 down되어 있습니다.&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;1618&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/luug9/btsvdvp81OP/TxkqeZFeg3Kt1DILQGKRlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/luug9/btsvdvp81OP/TxkqeZFeg3Kt1DILQGKRlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/luug9/btsvdvp81OP/TxkqeZFeg3Kt1DILQGKRlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fluug9%2Fbtsvdvp81OP%2FTxkqeZFeg3Kt1DILQGKRlK%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;1618&quot; height=&quot;722&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가상 프라이빗 게이트웨이 (Virtual Private Gateway)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VGW (Virtual Private Gateway)는 VPC와 연동하기 위한 DX의 리소스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름과 VIF의 ASN을 입력하여 생성 후 VPC와 연결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 VPC와 연동되기에 DX와 연동할 VPC 갯수 만큼 생성해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 DXGW, Direct Connect Gateway와 연동하여 on-prem과 AWS의 여러 VGW를 연결 하여 사용 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;1044&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgdrsE/btsvfzTKWIK/pknJulD9q86EuRj3I5fP6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgdrsE/btsvfzTKWIK/pknJulD9q86EuRj3I5fP6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgdrsE/btsvfzTKWIK/pknJulD9q86EuRj3I5fP6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgdrsE%2FbtsvfzTKWIK%2FpknJulD9q86EuRj3I5fP6k%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;1718&quot; height=&quot;1044&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;1044&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC 연동 후 모습&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;2380&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b94f8q/btsvlx8gMg0/BNDWe8Np4bDfKakmLbQ7pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b94f8q/btsvlx8gMg0/BNDWe8Np4bDfKakmLbQ7pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b94f8q/btsvlx8gMg0/BNDWe8Np4bDfKakmLbQ7pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb94f8q%2Fbtsvlx8gMg0%2FBNDWe8Np4bDfKakmLbQ7pk%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;674&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2380&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Direct Connect Gateway생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DXGW, Direct Connect Gateway를 통해 VIF와 연결후 VGW를 추가로 연동하여 DX를 통한 전용선 통신을 할 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름과 VIF의 ASN을 이용해 생성 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1676&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbuRYo/btsvx2lJ88R/QG4lhzc10SiObbkVLoKUX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbuRYo/btsvx2lJ88R/QG4lhzc10SiObbkVLoKUX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbuRYo/btsvx2lJ88R/QG4lhzc10SiObbkVLoKUX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbuRYo%2Fbtsvx2lJ88R%2FQG4lhzc10SiObbkVLoKUX1%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;1676&quot; height=&quot;1002&quot; data-origin-width=&quot;1676&quot; data-origin-height=&quot;1002&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cw8Xhl/btsvlyTB28J/kcQGO3mBADZpXh49Sk8Su1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cw8Xhl/btsvlyTB28J/kcQGO3mBADZpXh49Sk8Su1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cw8Xhl/btsvlyTB28J/kcQGO3mBADZpXh49Sk8Su1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcw8Xhl%2FbtsvlyTB28J%2FkcQGO3mBADZpXh49Sk8Su1%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;2340&quot; height=&quot;252&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DXGW로 연결할 게이트웨이를 지정 할 수 있습니다.&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;VIF가 transit이면 transiant gateway를, private이라면 VGW를 연결 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VIF가 private이면 transiant gateway를 연결 할 수 없습니다.&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;접두사는 VGW에 연결된 AWS의 리소스 접두사를 입력해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) VPC면 VPC의 cidr주소를 입력하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;1078&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IrIRe/btsvjgsQZEJ/PS0ooJitWzaHkzrPZnosX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IrIRe/btsvjgsQZEJ/PS0ooJitWzaHkzrPZnosX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IrIRe/btsvjgsQZEJ/PS0ooJitWzaHkzrPZnosX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIrIRe%2FbtsvjgsQZEJ%2FPS0ooJitWzaHkzrPZnosX0%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;1698&quot; height=&quot;1078&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DXGW에는 여러 VGW를 연결 할 수 있습니다.&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;2304&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diD28F/btsvDkT3tnc/2OLVIjgpJtxoLMJ0a4bfqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diD28F/btsvDkT3tnc/2OLVIjgpJtxoLMJ0a4bfqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diD28F/btsvDkT3tnc/2OLVIjgpJtxoLMJ0a4bfqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiD28F%2FbtsvDkT3tnc%2F2OLVIjgpJtxoLMJ0a4bfqk%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;2304&quot; height=&quot;598&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2304&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가상 인터페이스(VIF)와 DXGW 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 인터페이스 상세화면에서 이전에 생성한 DXGW와의 연결을 수락합니다.&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;1628&quot; data-origin-height=&quot;966&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/234Fe/btsvlKmmGQA/UAXyoomhHnSNss0t9uKAA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/234Fe/btsvlKmmGQA/UAXyoomhHnSNss0t9uKAA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/234Fe/btsvlKmmGQA/UAXyoomhHnSNss0t9uKAA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F234Fe%2FbtsvlKmmGQA%2FUAXyoomhHnSNss0t9uKAA0%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;1628&quot; height=&quot;966&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;966&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수락이 성공하면 VIF 상세화면에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결은 성공해도 BGP연결은 down이 되게됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VIF 상태&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VIF 상태는 1,2 계층의 연결이 성공적으로 수락되었음을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGcraa/btsvk2nftXa/qKllsnpqxTsrHX1DXiSK9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGcraa/btsvk2nftXa/qKllsnpqxTsrHX1DXiSK9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGcraa/btsvk2nftXa/qKllsnpqxTsrHX1DXiSK9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGcraa%2Fbtsvk2nftXa%2FqKllsnpqxTsrHX1DXiSK9K%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;224&quot; height=&quot;134&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BGP 상태&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BGP상태는 3 계층에 대한 연결을 나타내는데,&lt;b&gt; VIF 수락 후 추가적인 작업(라우팅 전파) 후 BGP가 UP이 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;122&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CfGXv/btsvlI9VecB/R4gKYjVpeJClwterBue4Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CfGXv/btsvlI9VecB/R4gKYjVpeJClwterBue4Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CfGXv/btsvlI9VecB/R4gKYjVpeJClwterBue4Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCfGXv%2FbtsvlI9VecB%2FR4gKYjVpeJClwterBue4Kk%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;122&quot; height=&quot;80&quot; data-origin-width=&quot;122&quot; data-origin-height=&quot;80&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;라우팅 전파속성 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VGW에 연결된 VPC의 라우팅 테이블의 VGW에 대한 전파속성을 활성화로 변경해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅 테이블에 DX관련 VGW에 대한 CIDR 및 전파 편집 속성이 생기기까지 조금 시간이 걸립니다..!&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;1608&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfSwTZ/btsvjfAGcXE/AU5QBVs0DjaZTBOMmBg261/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfSwTZ/btsvjfAGcXE/AU5QBVs0DjaZTBOMmBg261/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfSwTZ/btsvjfAGcXE/AU5QBVs0DjaZTBOMmBg261/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfSwTZ%2FbtsvjfAGcXE%2FAU5QBVs0DjaZTBOMmBg261%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;1608&quot; height=&quot;836&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BGW UP&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전파 속성이 활성화되면 VIF에서 BGP옵션이 UP이 되며, on-prem과 AWS VPC의 연결이 완료되게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfhDtf/btsvdsNKgrD/X5df6kKACXIcyqKv0uc9Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfhDtf/btsvdsNKgrD/X5df6kKACXIcyqKv0uc9Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfhDtf/btsvdsNKgrD/X5df6kKACXIcyqKv0uc9Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfhDtf%2FbtsvdsNKgrD%2FX5df6kKACXIcyqKv0uc9Q0%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;456&quot; height=&quot;160&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>aws</category>
      <category>as</category>
      <category>ASN</category>
      <category>AWS Direct Connect</category>
      <category>AWS DX</category>
      <category>BGP</category>
      <category>DXGW</category>
      <category>VGW</category>
      <category>VIF</category>
      <category>다이렉트 커넥트</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/70</guid>
      <comments>https://jaykos96.tistory.com/70#entry70comment</comments>
      <pubDate>Sun, 24 Sep 2023 17:38:55 +0900</pubDate>
    </item>
    <item>
      <title>스프링 서블릿(servlet)에 대해 알아보자</title>
      <link>https://jaykos96.tistory.com/69</link>
      <description>&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;업무 중 application.yml에 붙어있던 prefix를 특정 api에서는 제거해야하는 상황이 생겼습니다.&lt;br&gt;그래서 서블릿을 사용해야 할 것 같아, prefix를 제거하고 새로 서블릿을 만들어 라우팅을 하여 해결하였지만 이해가 부족한 것 같아&lt;br&gt;서블릿에 대하여 다시 공부하고 정리하려합니다&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;서블릿이란?&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGOs0i/btsu2bQZKaO/49SwXxF2vux1Y2CU5Gkfvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGOs0i/btsu2bQZKaO/49SwXxF2vux1Y2CU5Gkfvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGOs0i/btsu2bQZKaO/49SwXxF2vux1Y2CU5Gkfvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGOs0i%2Fbtsu2bQZKaO%2F49SwXxF2vux1Y2CU5Gkfvk%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;1270&quot; height=&quot;574&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;b&gt;웹 어플리케이션에서 HTTP 요청과 응답 처리를 위한 스프링에서 제공하는 핵심 기능입니다.&lt;/b&gt;&lt;br&gt;&lt;b&gt;스프링 부트에서는 HttpServlet클래스를 상속을 받아 사용하며, Servlet Container에 의해 실행됩니다.&lt;/b&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;doGet, doPost, doPut, doDelete 등의 메서드를 사용하여 요청을 처리하며&lt;/span&gt;&lt;br&gt;스프링 부트에서는 어노테이션과 HttpServlet 상속을 이용하여 간단하게 생성 할 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;아래 사진은 /apis라는 url이 붙은 요청을 캐치하는 서블릿 예시코드입니다.&lt;br&gt;추가로 생성한 서블릿에는 MultipartConfig 설정이 있어야 해당 서블릿에서 들어오는 멀티파트 요청을 처리 할 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2866&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/efIwQn/btst84tnmBP/aY5qealfOaZBg8hz1HyFr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efIwQn/btst84tnmBP/aY5qealfOaZBg8hz1HyFr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efIwQn/btst84tnmBP/aY5qealfOaZBg8hz1HyFr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefIwQn%2Fbtst84tnmBP%2FaY5qealfOaZBg8hz1HyFr1%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;2866&quot; height=&quot;906&quot; data-origin-width=&quot;2866&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;서블릿 컨테이너란?&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;스프링에서 서블릿을 관리해주는 컨테이너입니다.&lt;br&gt;&lt;b&gt;클라이언트의 요청을 받고 응답하도록 웹서버(nginx 등)와 소켓을 만들어 통신합니다.&lt;/b&gt;&lt;br&gt;&lt;b&gt;서블릿 컨테이너는 쓰레드 풀을 가지며 요청이 도착하면 쓰레드를 가져와 서블릿 인스턴스를 생성하여 요청을 처리합니다.&amp;nbsp;&lt;/b&gt;&lt;br&gt;대표적인 서블릿 컨테이너로 톰캣(Tomcat)이 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;1. 웹서버와의 통신지원&lt;/b&gt; : 소켓기능들을 API로 제공하여 복잡한 과정을 생략하고, 비즈니스 로직에 대해서만 집중하도록 도와줍니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;2. 서블릿 생명주기 관리&lt;/b&gt; : 서블릿 클래스를 로딩하여 인스턴스화하고, 초기화 메소드를 호출하고, 요청이 들어오면 적절한 서블릿 메소드를 호출합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;3. 멀티쓰레드 지원 및 관리&lt;/b&gt; : 서블릿 컨테이너는 요청이 올 때 마다 새로운 자바 쓰레드를 하나 생성하는데, HTTP 서비스 메소드를 실행한 후 쓰레드는 자동으로 됩니다. 이러한 관리를 컨테이너가 알아서 해준다.&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;서블릿 동작 과정 및 생명주기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;추가로 서블릿의 생명주기 관련 메소드 또한 오버라이딩하여&lt;br&gt;비즈니스 로직을 추가 가능합니다.&lt;br&gt;init(), service(), destroy()로 서블릿의 생명주기를 관리 할 수 있습니다.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;서블릿 동작 과정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;1. 클라이언트에서 URL 호출&lt;br&gt;2. &lt;b&gt;웹서버에서 URL을 확인하여 해당 서블릿으로 요청 전달, 없으면 서블릿 클래스의 init()으로 생성&lt;/b&gt;&lt;br&gt;3. 서블릿 컨테이너는 요청정보를 HttpServletRequest, HttpServletResponse 두 객체에 저장&lt;br&gt;4. &lt;b&gt;서블릿 클래스의 service()메소드 호출 (HTTP Method에 따라 서블릿의 doGet, doPost 호출)&lt;/b&gt;&lt;br&gt;5. 요청 처리 후 HttpServletResponse 객체에 HTTP 데이터를 담아 응답&lt;br&gt;6. 웹서버가 클라이언트에 응답처리&lt;br&gt;7. &lt;b&gt;서블릿의 destroy() 호출하여 GC에 의해 서블릿 종료&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;디스패쳐 서블릿이란?&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;서블릿과 함께 스프링을 사용하다보면 항상 듣게되는 디스패쳐 서블릿입니다.&lt;br&gt;서블릿 컨테이너 앞에서 최초로 가장 먼저 요청을 받고 처리하기에 Front Controller라고 부르기도 합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;디스패쳐 서블릿을 이해하기 위해서는 스프링MVC 패턴에 대한 이해가 먼저 필요한데&lt;br&gt;간단하게 정리하자면 아래 3가지 엔티티를 사용하여 만들어내는 스프링의 구조화된 패턴 방식입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Model : 어플리케이션의 비즈니스 로직 및 데이터&lt;br&gt;View : Model의 데이터를 사용하여 클라이언트에게 보여줄 사용자 UI&lt;br&gt;Controller : HTTP 요청을 받아들이고, 모델을 생성 및 가공하여 적절한 뷰를 반환&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKDkkG/btsuZ2AfkJU/unSE2mjyBSpx29sCsbDTz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKDkkG/btsuZ2AfkJU/unSE2mjyBSpx29sCsbDTz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKDkkG/btsuZ2AfkJU/unSE2mjyBSpx29sCsbDTz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKDkkG%2FbtsuZ2AfkJU%2FunSE2mjyBSpx29sCsbDTz0%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;704&quot; height=&quot;455&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;디스패쳐 서블릿은 다음과 같은 순서로 역할을 수행합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;1. 클라이언트에서 최초 요청을 전달받습니다.&lt;br&gt;2. 요청을 처리할 적절한 핸들러(컨트롤러)에 라우팅합니다. (핸들러 매핑을 통해 요청 URI와 컨트롤러를 매핑합니다.)&lt;br&gt;3. 적절한 핸들러를 실행시킬 수 있는 Handler Adapter를 통해 선택된 핸들러를 실행시킵니다.&lt;br&gt;4. 선택된 핸들러(컨트롤러)는 비즈니스 로직을 수행합니다.&lt;br&gt;5. 핸들러(컨트롤러)가 비즈니스 로직 실행을 마치면 처리 결과를 Model에 설정하여 HandlerAdapter에 view name을 반환합니다.&lt;br&gt;6. 전달받은 view name을 뷰 리졸버에 반환하고 view name과 맡는 view객체를 반환합니다.&lt;br&gt;7. 디스패쳐 서블릿은 view에 model을 전달합니다.&lt;br&gt;8. 렌더링하여 클라이언트에 반환합니다.&lt;/p&gt;</description>
      <category>스프링</category>
      <category>서블릿 컨테이너</category>
      <category>스프링</category>
      <author>겅겅겅</author>
      <guid isPermaLink="true">https://jaykos96.tistory.com/69</guid>
      <comments>https://jaykos96.tistory.com/69#entry69comment</comments>
      <pubDate>Sun, 17 Sep 2023 19:32:00 +0900</pubDate>
    </item>
  </channel>
</rss>